1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
75 #define MAX(x, y) ((x) > (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
106 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
108 #define ID_COLS 8
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 #ifndef GIT_CONFIG
117 #define GIT_CONFIG "config"
118 #endif
120 /* Some ASCII-shorthands fitted into the ncurses namespace. */
121 #define KEY_TAB '\t'
122 #define KEY_RETURN '\r'
123 #define KEY_ESC 27
126 struct ref {
127 char id[SIZEOF_REV]; /* Commit SHA1 ID */
128 unsigned int head:1; /* Is it the current HEAD? */
129 unsigned int tag:1; /* Is it a tag? */
130 unsigned int ltag:1; /* If so, is the tag local? */
131 unsigned int remote:1; /* Is it a remote ref? */
132 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
133 char name[1]; /* Ref name; tag or head names are shortened. */
134 };
136 struct ref_list {
137 char id[SIZEOF_REV]; /* Commit SHA1 ID */
138 size_t size; /* Number of refs. */
139 struct ref **refs; /* References for this ID. */
140 };
142 static struct ref_list *get_ref_list(const char *id);
143 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
144 static int load_refs(void);
146 enum format_flags {
147 FORMAT_ALL, /* Perform replacement in all arguments. */
148 FORMAT_DASH, /* Perform replacement up until "--". */
149 FORMAT_NONE /* No replacement should be performed. */
150 };
152 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
154 enum input_status {
155 INPUT_OK,
156 INPUT_SKIP,
157 INPUT_STOP,
158 INPUT_CANCEL
159 };
161 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
163 static char *prompt_input(const char *prompt, input_handler handler, void *data);
164 static bool prompt_yesno(const char *prompt);
166 struct menu_item {
167 int hotkey;
168 const char *text;
169 void *data;
170 };
172 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
174 /*
175 * Allocation helpers ... Entering macro hell to never be seen again.
176 */
178 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
179 static type * \
180 name(type **mem, size_t size, size_t increase) \
181 { \
182 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
183 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
184 type *tmp = *mem; \
185 \
186 if (mem == NULL || num_chunks != num_chunks_new) { \
187 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
188 if (tmp) \
189 *mem = tmp; \
190 } \
191 \
192 return tmp; \
193 }
195 /*
196 * String helpers
197 */
199 static inline void
200 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
201 {
202 if (srclen > dstlen - 1)
203 srclen = dstlen - 1;
205 strncpy(dst, src, srclen);
206 dst[srclen] = 0;
207 }
209 /* Shorthands for safely copying into a fixed buffer. */
211 #define string_copy(dst, src) \
212 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
214 #define string_ncopy(dst, src, srclen) \
215 string_ncopy_do(dst, sizeof(dst), src, srclen)
217 #define string_copy_rev(dst, src) \
218 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
220 #define string_add(dst, from, src) \
221 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
223 static void
224 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
225 {
226 size_t size, pos;
228 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
229 if (src[pos] == '\t') {
230 size_t expanded = tabsize - (size % tabsize);
232 if (expanded + size >= dstlen - 1)
233 expanded = dstlen - size - 1;
234 memcpy(dst + size, " ", expanded);
235 size += expanded;
236 } else {
237 dst[size++] = src[pos];
238 }
239 }
241 dst[size] = 0;
242 }
244 static char *
245 chomp_string(char *name)
246 {
247 int namelen;
249 while (isspace(*name))
250 name++;
252 namelen = strlen(name) - 1;
253 while (namelen > 0 && isspace(name[namelen]))
254 name[namelen--] = 0;
256 return name;
257 }
259 static bool
260 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
261 {
262 va_list args;
263 size_t pos = bufpos ? *bufpos : 0;
265 va_start(args, fmt);
266 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
267 va_end(args);
269 if (bufpos)
270 *bufpos = pos;
272 return pos >= bufsize ? FALSE : TRUE;
273 }
275 #define string_format(buf, fmt, args...) \
276 string_nformat(buf, sizeof(buf), NULL, fmt, args)
278 #define string_format_from(buf, from, fmt, args...) \
279 string_nformat(buf, sizeof(buf), from, fmt, args)
281 static int
282 string_enum_compare(const char *str1, const char *str2, int len)
283 {
284 size_t i;
286 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
288 /* Diff-Header == DIFF_HEADER */
289 for (i = 0; i < len; i++) {
290 if (toupper(str1[i]) == toupper(str2[i]))
291 continue;
293 if (string_enum_sep(str1[i]) &&
294 string_enum_sep(str2[i]))
295 continue;
297 return str1[i] - str2[i];
298 }
300 return 0;
301 }
303 struct enum_map {
304 const char *name;
305 int namelen;
306 int value;
307 };
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
311 static bool
312 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
313 {
314 size_t namelen = strlen(name);
315 int i;
317 for (i = 0; i < map_size; i++)
318 if (namelen == map[i].namelen &&
319 !string_enum_compare(name, map[i].name, namelen)) {
320 *value = map[i].value;
321 return TRUE;
322 }
324 return FALSE;
325 }
327 #define map_enum(attr, map, name) \
328 map_enum_do(map, ARRAY_SIZE(map), attr, name)
330 #define prefixcmp(str1, str2) \
331 strncmp(str1, str2, STRING_SIZE(str2))
333 static inline int
334 suffixcmp(const char *str, int slen, const char *suffix)
335 {
336 size_t len = slen >= 0 ? slen : strlen(str);
337 size_t suffixlen = strlen(suffix);
339 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
340 }
343 static const char *
344 mkdate(const time_t *time)
345 {
346 static char buf[DATE_COLS + 1];
347 struct tm tm;
349 gmtime_r(time, &tm);
350 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
351 }
354 static bool
355 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
356 {
357 int valuelen;
359 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
360 bool advance = cmd[valuelen] != 0;
362 cmd[valuelen] = 0;
363 argv[(*argc)++] = chomp_string(cmd);
364 cmd = chomp_string(cmd + valuelen + advance);
365 }
367 if (*argc < SIZEOF_ARG)
368 argv[*argc] = NULL;
369 return *argc < SIZEOF_ARG;
370 }
372 static void
373 argv_from_env(const char **argv, const char *name)
374 {
375 char *env = argv ? getenv(name) : NULL;
376 int argc = 0;
378 if (env && *env)
379 env = strdup(env);
380 if (env && !argv_from_string(argv, &argc, env))
381 die("Too many arguments in the `%s` environment variable", name);
382 }
385 /*
386 * Executing external commands.
387 */
389 enum io_type {
390 IO_FD, /* File descriptor based IO. */
391 IO_BG, /* Execute command in the background. */
392 IO_FG, /* Execute command with same std{in,out,err}. */
393 IO_RD, /* Read only fork+exec IO. */
394 IO_WR, /* Write only fork+exec IO. */
395 IO_AP, /* Append fork+exec output to file. */
396 };
398 struct io {
399 enum io_type type; /* The requested type of pipe. */
400 const char *dir; /* Directory from which to execute. */
401 pid_t pid; /* Pipe for reading or writing. */
402 int pipe; /* Pipe end for reading or writing. */
403 int error; /* Error status. */
404 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
405 char *buf; /* Read buffer. */
406 size_t bufalloc; /* Allocated buffer size. */
407 size_t bufsize; /* Buffer content size. */
408 char *bufpos; /* Current buffer position. */
409 unsigned int eof:1; /* Has end of file been reached. */
410 };
412 static void
413 reset_io(struct io *io)
414 {
415 io->pipe = -1;
416 io->pid = 0;
417 io->buf = io->bufpos = NULL;
418 io->bufalloc = io->bufsize = 0;
419 io->error = 0;
420 io->eof = 0;
421 }
423 static void
424 init_io(struct io *io, const char *dir, enum io_type type)
425 {
426 reset_io(io);
427 io->type = type;
428 io->dir = dir;
429 }
431 static bool
432 init_io_rd(struct io *io, const char *argv[], const char *dir,
433 enum format_flags flags)
434 {
435 init_io(io, dir, IO_RD);
436 return format_argv(io->argv, argv, flags);
437 }
439 static bool
440 io_open(struct io *io, const char *name)
441 {
442 init_io(io, NULL, IO_FD);
443 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
444 if (io->pipe == -1)
445 io->error = errno;
446 return io->pipe != -1;
447 }
449 static bool
450 kill_io(struct io *io)
451 {
452 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
453 }
455 static bool
456 done_io(struct io *io)
457 {
458 pid_t pid = io->pid;
460 if (io->pipe != -1)
461 close(io->pipe);
462 free(io->buf);
463 reset_io(io);
465 while (pid > 0) {
466 int status;
467 pid_t waiting = waitpid(pid, &status, 0);
469 if (waiting < 0) {
470 if (errno == EINTR)
471 continue;
472 report("waitpid failed (%s)", strerror(errno));
473 return FALSE;
474 }
476 return waiting == pid &&
477 !WIFSIGNALED(status) &&
478 WIFEXITED(status) &&
479 !WEXITSTATUS(status);
480 }
482 return TRUE;
483 }
485 static bool
486 start_io(struct io *io)
487 {
488 int pipefds[2] = { -1, -1 };
490 if (io->type == IO_FD)
491 return TRUE;
493 if ((io->type == IO_RD || io->type == IO_WR) &&
494 pipe(pipefds) < 0)
495 return FALSE;
496 else if (io->type == IO_AP)
497 pipefds[1] = io->pipe;
499 if ((io->pid = fork())) {
500 if (pipefds[!(io->type == IO_WR)] != -1)
501 close(pipefds[!(io->type == IO_WR)]);
502 if (io->pid != -1) {
503 io->pipe = pipefds[!!(io->type == IO_WR)];
504 return TRUE;
505 }
507 } else {
508 if (io->type != IO_FG) {
509 int devnull = open("/dev/null", O_RDWR);
510 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
511 int writefd = (io->type == IO_RD || io->type == IO_AP)
512 ? pipefds[1] : devnull;
514 dup2(readfd, STDIN_FILENO);
515 dup2(writefd, STDOUT_FILENO);
516 dup2(devnull, STDERR_FILENO);
518 close(devnull);
519 if (pipefds[0] != -1)
520 close(pipefds[0]);
521 if (pipefds[1] != -1)
522 close(pipefds[1]);
523 }
525 if (io->dir && *io->dir && chdir(io->dir) == -1)
526 die("Failed to change directory: %s", strerror(errno));
528 execvp(io->argv[0], (char *const*) io->argv);
529 die("Failed to execute program: %s", strerror(errno));
530 }
532 if (pipefds[!!(io->type == IO_WR)] != -1)
533 close(pipefds[!!(io->type == IO_WR)]);
534 return FALSE;
535 }
537 static bool
538 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
539 {
540 init_io(io, dir, type);
541 if (!format_argv(io->argv, argv, FORMAT_NONE))
542 return FALSE;
543 return start_io(io);
544 }
546 static int
547 run_io_do(struct io *io)
548 {
549 return start_io(io) && done_io(io);
550 }
552 static int
553 run_io_bg(const char **argv)
554 {
555 struct io io = {};
557 init_io(&io, NULL, IO_BG);
558 if (!format_argv(io.argv, argv, FORMAT_NONE))
559 return FALSE;
560 return run_io_do(&io);
561 }
563 static bool
564 run_io_fg(const char **argv, const char *dir)
565 {
566 struct io io = {};
568 init_io(&io, dir, IO_FG);
569 if (!format_argv(io.argv, argv, FORMAT_NONE))
570 return FALSE;
571 return run_io_do(&io);
572 }
574 static bool
575 run_io_append(const char **argv, enum format_flags flags, int fd)
576 {
577 struct io io = {};
579 init_io(&io, NULL, IO_AP);
580 io.pipe = fd;
581 if (format_argv(io.argv, argv, flags))
582 return run_io_do(&io);
583 close(fd);
584 return FALSE;
585 }
587 static bool
588 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
589 {
590 return init_io_rd(io, argv, NULL, flags) && start_io(io);
591 }
593 static bool
594 io_eof(struct io *io)
595 {
596 return io->eof;
597 }
599 static int
600 io_error(struct io *io)
601 {
602 return io->error;
603 }
605 static char *
606 io_strerror(struct io *io)
607 {
608 return strerror(io->error);
609 }
611 static bool
612 io_can_read(struct io *io)
613 {
614 struct timeval tv = { 0, 500 };
615 fd_set fds;
617 FD_ZERO(&fds);
618 FD_SET(io->pipe, &fds);
620 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
621 }
623 static ssize_t
624 io_read(struct io *io, void *buf, size_t bufsize)
625 {
626 do {
627 ssize_t readsize = read(io->pipe, buf, bufsize);
629 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
630 continue;
631 else if (readsize == -1)
632 io->error = errno;
633 else if (readsize == 0)
634 io->eof = 1;
635 return readsize;
636 } while (1);
637 }
639 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
641 static char *
642 io_get(struct io *io, int c, bool can_read)
643 {
644 char *eol;
645 ssize_t readsize;
647 while (TRUE) {
648 if (io->bufsize > 0) {
649 eol = memchr(io->bufpos, c, io->bufsize);
650 if (eol) {
651 char *line = io->bufpos;
653 *eol = 0;
654 io->bufpos = eol + 1;
655 io->bufsize -= io->bufpos - line;
656 return line;
657 }
658 }
660 if (io_eof(io)) {
661 if (io->bufsize) {
662 io->bufpos[io->bufsize] = 0;
663 io->bufsize = 0;
664 return io->bufpos;
665 }
666 return NULL;
667 }
669 if (!can_read)
670 return NULL;
672 if (io->bufsize > 0 && io->bufpos > io->buf)
673 memmove(io->buf, io->bufpos, io->bufsize);
675 if (io->bufalloc == io->bufsize) {
676 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
677 return NULL;
678 io->bufalloc += BUFSIZ;
679 }
681 io->bufpos = io->buf;
682 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
683 if (io_error(io))
684 return NULL;
685 io->bufsize += readsize;
686 }
687 }
689 static bool
690 io_write(struct io *io, const void *buf, size_t bufsize)
691 {
692 size_t written = 0;
694 while (!io_error(io) && written < bufsize) {
695 ssize_t size;
697 size = write(io->pipe, buf + written, bufsize - written);
698 if (size < 0 && (errno == EAGAIN || errno == EINTR))
699 continue;
700 else if (size == -1)
701 io->error = errno;
702 else
703 written += size;
704 }
706 return written == bufsize;
707 }
709 static bool
710 io_read_buf(struct io *io, char buf[], size_t bufsize)
711 {
712 char *result = io_get(io, '\n', TRUE);
714 if (result) {
715 result = chomp_string(result);
716 string_ncopy_do(buf, bufsize, result, strlen(result));
717 }
719 return done_io(io) && result;
720 }
722 static bool
723 run_io_buf(const char **argv, char buf[], size_t bufsize)
724 {
725 struct io io = {};
727 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
728 }
730 static int
731 io_load(struct io *io, const char *separators,
732 int (*read_property)(char *, size_t, char *, size_t))
733 {
734 char *name;
735 int state = OK;
737 if (!start_io(io))
738 return ERR;
740 while (state == OK && (name = io_get(io, '\n', TRUE))) {
741 char *value;
742 size_t namelen;
743 size_t valuelen;
745 name = chomp_string(name);
746 namelen = strcspn(name, separators);
748 if (name[namelen]) {
749 name[namelen] = 0;
750 value = chomp_string(name + namelen + 1);
751 valuelen = strlen(value);
753 } else {
754 value = "";
755 valuelen = 0;
756 }
758 state = read_property(name, namelen, value, valuelen);
759 }
761 if (state != ERR && io_error(io))
762 state = ERR;
763 done_io(io);
765 return state;
766 }
768 static int
769 run_io_load(const char **argv, const char *separators,
770 int (*read_property)(char *, size_t, char *, size_t))
771 {
772 struct io io = {};
774 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
775 ? io_load(&io, separators, read_property) : ERR;
776 }
779 /*
780 * User requests
781 */
783 #define REQ_INFO \
784 /* XXX: Keep the view request first and in sync with views[]. */ \
785 REQ_GROUP("View switching") \
786 REQ_(VIEW_MAIN, "Show main view"), \
787 REQ_(VIEW_DIFF, "Show diff view"), \
788 REQ_(VIEW_LOG, "Show log view"), \
789 REQ_(VIEW_TREE, "Show tree view"), \
790 REQ_(VIEW_BLOB, "Show blob view"), \
791 REQ_(VIEW_BLAME, "Show blame view"), \
792 REQ_(VIEW_BRANCH, "Show branch view"), \
793 REQ_(VIEW_HELP, "Show help page"), \
794 REQ_(VIEW_PAGER, "Show pager view"), \
795 REQ_(VIEW_STATUS, "Show status view"), \
796 REQ_(VIEW_STAGE, "Show stage view"), \
797 \
798 REQ_GROUP("View manipulation") \
799 REQ_(ENTER, "Enter current line and scroll"), \
800 REQ_(NEXT, "Move to next"), \
801 REQ_(PREVIOUS, "Move to previous"), \
802 REQ_(PARENT, "Move to parent"), \
803 REQ_(VIEW_NEXT, "Move focus to next view"), \
804 REQ_(REFRESH, "Reload and refresh"), \
805 REQ_(MAXIMIZE, "Maximize the current view"), \
806 REQ_(VIEW_CLOSE, "Close the current view"), \
807 REQ_(QUIT, "Close all views and quit"), \
808 \
809 REQ_GROUP("View specific requests") \
810 REQ_(STATUS_UPDATE, "Update file status"), \
811 REQ_(STATUS_REVERT, "Revert file changes"), \
812 REQ_(STATUS_MERGE, "Merge file using external tool"), \
813 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
814 \
815 REQ_GROUP("Cursor navigation") \
816 REQ_(MOVE_UP, "Move cursor one line up"), \
817 REQ_(MOVE_DOWN, "Move cursor one line down"), \
818 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
819 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
820 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
821 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
822 \
823 REQ_GROUP("Scrolling") \
824 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
825 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
826 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
827 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
828 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
829 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
830 \
831 REQ_GROUP("Searching") \
832 REQ_(SEARCH, "Search the view"), \
833 REQ_(SEARCH_BACK, "Search backwards in the view"), \
834 REQ_(FIND_NEXT, "Find next search match"), \
835 REQ_(FIND_PREV, "Find previous search match"), \
836 \
837 REQ_GROUP("Option manipulation") \
838 REQ_(OPTIONS, "Open option menu"), \
839 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
840 REQ_(TOGGLE_DATE, "Toggle date display"), \
841 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
842 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
843 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
844 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
845 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
846 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
847 \
848 REQ_GROUP("Misc") \
849 REQ_(PROMPT, "Bring up the prompt"), \
850 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
851 REQ_(SHOW_VERSION, "Show version information"), \
852 REQ_(STOP_LOADING, "Stop all loading views"), \
853 REQ_(EDIT, "Open in editor"), \
854 REQ_(NONE, "Do nothing")
857 /* User action requests. */
858 enum request {
859 #define REQ_GROUP(help)
860 #define REQ_(req, help) REQ_##req
862 /* Offset all requests to avoid conflicts with ncurses getch values. */
863 REQ_OFFSET = KEY_MAX + 1,
864 REQ_INFO
866 #undef REQ_GROUP
867 #undef REQ_
868 };
870 struct request_info {
871 enum request request;
872 const char *name;
873 int namelen;
874 const char *help;
875 };
877 static const struct request_info req_info[] = {
878 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
879 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
880 REQ_INFO
881 #undef REQ_GROUP
882 #undef REQ_
883 };
885 static enum request
886 get_request(const char *name)
887 {
888 int namelen = strlen(name);
889 int i;
891 for (i = 0; i < ARRAY_SIZE(req_info); i++)
892 if (req_info[i].namelen == namelen &&
893 !string_enum_compare(req_info[i].name, name, namelen))
894 return req_info[i].request;
896 return REQ_NONE;
897 }
900 /*
901 * Options
902 */
904 /* Option and state variables. */
905 static bool opt_date = TRUE;
906 static bool opt_date_short = FALSE;
907 static bool opt_author = TRUE;
908 static bool opt_line_number = FALSE;
909 static bool opt_line_graphics = TRUE;
910 static bool opt_rev_graph = FALSE;
911 static bool opt_show_refs = TRUE;
912 static int opt_num_interval = 5;
913 static double opt_hscroll = 0.50;
914 static double opt_scale_split_view = 2.0 / 3.0;
915 static int opt_tab_size = 8;
916 static int opt_author_cols = 19;
917 static char opt_path[SIZEOF_STR] = "";
918 static char opt_file[SIZEOF_STR] = "";
919 static char opt_ref[SIZEOF_REF] = "";
920 static char opt_head[SIZEOF_REF] = "";
921 static char opt_head_rev[SIZEOF_REV] = "";
922 static char opt_remote[SIZEOF_REF] = "";
923 static char opt_encoding[20] = "UTF-8";
924 static bool opt_utf8 = TRUE;
925 static char opt_codeset[20] = "UTF-8";
926 static iconv_t opt_iconv = ICONV_NONE;
927 static char opt_search[SIZEOF_STR] = "";
928 static char opt_cdup[SIZEOF_STR] = "";
929 static char opt_prefix[SIZEOF_STR] = "";
930 static char opt_git_dir[SIZEOF_STR] = "";
931 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
932 static char opt_editor[SIZEOF_STR] = "";
933 static FILE *opt_tty = NULL;
935 #define is_initial_commit() (!*opt_head_rev)
936 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
939 /*
940 * Line-oriented content detection.
941 */
943 #define LINE_INFO \
944 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
945 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
946 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
947 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
948 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
949 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
950 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
951 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
952 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
953 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
954 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
955 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
956 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
957 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
958 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
959 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
960 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
961 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
962 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
963 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
964 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
965 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
966 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
967 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
968 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
969 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
970 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
971 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
972 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
973 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
974 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
975 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
976 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
977 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
978 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
979 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
980 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
981 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
982 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
983 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
984 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
985 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
986 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
987 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
988 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
989 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
990 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
991 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
992 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
993 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
994 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
995 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
996 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
997 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
998 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
999 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1000 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1002 enum line_type {
1003 #define LINE(type, line, fg, bg, attr) \
1004 LINE_##type
1005 LINE_INFO,
1006 LINE_NONE
1007 #undef LINE
1008 };
1010 struct line_info {
1011 const char *name; /* Option name. */
1012 int namelen; /* Size of option name. */
1013 const char *line; /* The start of line to match. */
1014 int linelen; /* Size of string to match. */
1015 int fg, bg, attr; /* Color and text attributes for the lines. */
1016 };
1018 static struct line_info line_info[] = {
1019 #define LINE(type, line, fg, bg, attr) \
1020 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1021 LINE_INFO
1022 #undef LINE
1023 };
1025 static enum line_type
1026 get_line_type(const char *line)
1027 {
1028 int linelen = strlen(line);
1029 enum line_type type;
1031 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1032 /* Case insensitive search matches Signed-off-by lines better. */
1033 if (linelen >= line_info[type].linelen &&
1034 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1035 return type;
1037 return LINE_DEFAULT;
1038 }
1040 static inline int
1041 get_line_attr(enum line_type type)
1042 {
1043 assert(type < ARRAY_SIZE(line_info));
1044 return COLOR_PAIR(type) | line_info[type].attr;
1045 }
1047 static struct line_info *
1048 get_line_info(const char *name)
1049 {
1050 size_t namelen = strlen(name);
1051 enum line_type type;
1053 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1054 if (namelen == line_info[type].namelen &&
1055 !string_enum_compare(line_info[type].name, name, namelen))
1056 return &line_info[type];
1058 return NULL;
1059 }
1061 static void
1062 init_colors(void)
1063 {
1064 int default_bg = line_info[LINE_DEFAULT].bg;
1065 int default_fg = line_info[LINE_DEFAULT].fg;
1066 enum line_type type;
1068 start_color();
1070 if (assume_default_colors(default_fg, default_bg) == ERR) {
1071 default_bg = COLOR_BLACK;
1072 default_fg = COLOR_WHITE;
1073 }
1075 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1076 struct line_info *info = &line_info[type];
1077 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1078 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1080 init_pair(type, fg, bg);
1081 }
1082 }
1084 struct line {
1085 enum line_type type;
1087 /* State flags */
1088 unsigned int selected:1;
1089 unsigned int dirty:1;
1090 unsigned int cleareol:1;
1091 unsigned int other:16;
1093 void *data; /* User data */
1094 };
1097 /*
1098 * Keys
1099 */
1101 struct keybinding {
1102 int alias;
1103 enum request request;
1104 };
1106 static const struct keybinding default_keybindings[] = {
1107 /* View switching */
1108 { 'm', REQ_VIEW_MAIN },
1109 { 'd', REQ_VIEW_DIFF },
1110 { 'l', REQ_VIEW_LOG },
1111 { 't', REQ_VIEW_TREE },
1112 { 'f', REQ_VIEW_BLOB },
1113 { 'B', REQ_VIEW_BLAME },
1114 { 'H', REQ_VIEW_BRANCH },
1115 { 'p', REQ_VIEW_PAGER },
1116 { 'h', REQ_VIEW_HELP },
1117 { 'S', REQ_VIEW_STATUS },
1118 { 'c', REQ_VIEW_STAGE },
1120 /* View manipulation */
1121 { 'q', REQ_VIEW_CLOSE },
1122 { KEY_TAB, REQ_VIEW_NEXT },
1123 { KEY_RETURN, REQ_ENTER },
1124 { KEY_UP, REQ_PREVIOUS },
1125 { KEY_DOWN, REQ_NEXT },
1126 { 'R', REQ_REFRESH },
1127 { KEY_F(5), REQ_REFRESH },
1128 { 'O', REQ_MAXIMIZE },
1130 /* Cursor navigation */
1131 { 'k', REQ_MOVE_UP },
1132 { 'j', REQ_MOVE_DOWN },
1133 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1134 { KEY_END, REQ_MOVE_LAST_LINE },
1135 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1136 { ' ', REQ_MOVE_PAGE_DOWN },
1137 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1138 { 'b', REQ_MOVE_PAGE_UP },
1139 { '-', REQ_MOVE_PAGE_UP },
1141 /* Scrolling */
1142 { KEY_LEFT, REQ_SCROLL_LEFT },
1143 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1144 { KEY_IC, REQ_SCROLL_LINE_UP },
1145 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1146 { 'w', REQ_SCROLL_PAGE_UP },
1147 { 's', REQ_SCROLL_PAGE_DOWN },
1149 /* Searching */
1150 { '/', REQ_SEARCH },
1151 { '?', REQ_SEARCH_BACK },
1152 { 'n', REQ_FIND_NEXT },
1153 { 'N', REQ_FIND_PREV },
1155 /* Misc */
1156 { 'Q', REQ_QUIT },
1157 { 'z', REQ_STOP_LOADING },
1158 { 'v', REQ_SHOW_VERSION },
1159 { 'r', REQ_SCREEN_REDRAW },
1160 { 'o', REQ_OPTIONS },
1161 { '.', REQ_TOGGLE_LINENO },
1162 { 'D', REQ_TOGGLE_DATE },
1163 { 'T', REQ_TOGGLE_DATE_SHORT },
1164 { 'A', REQ_TOGGLE_AUTHOR },
1165 { 'g', REQ_TOGGLE_REV_GRAPH },
1166 { 'F', REQ_TOGGLE_REFS },
1167 { 'I', REQ_TOGGLE_SORT_ORDER },
1168 { 'i', REQ_TOGGLE_SORT_FIELD },
1169 { ':', REQ_PROMPT },
1170 { 'u', REQ_STATUS_UPDATE },
1171 { '!', REQ_STATUS_REVERT },
1172 { 'M', REQ_STATUS_MERGE },
1173 { '@', REQ_STAGE_NEXT },
1174 { ',', REQ_PARENT },
1175 { 'e', REQ_EDIT },
1176 };
1178 #define KEYMAP_INFO \
1179 KEYMAP_(GENERIC), \
1180 KEYMAP_(MAIN), \
1181 KEYMAP_(DIFF), \
1182 KEYMAP_(LOG), \
1183 KEYMAP_(TREE), \
1184 KEYMAP_(BLOB), \
1185 KEYMAP_(BLAME), \
1186 KEYMAP_(BRANCH), \
1187 KEYMAP_(PAGER), \
1188 KEYMAP_(HELP), \
1189 KEYMAP_(STATUS), \
1190 KEYMAP_(STAGE)
1192 enum keymap {
1193 #define KEYMAP_(name) KEYMAP_##name
1194 KEYMAP_INFO
1195 #undef KEYMAP_
1196 };
1198 static const struct enum_map keymap_table[] = {
1199 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1200 KEYMAP_INFO
1201 #undef KEYMAP_
1202 };
1204 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1206 struct keybinding_table {
1207 struct keybinding *data;
1208 size_t size;
1209 };
1211 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1213 static void
1214 add_keybinding(enum keymap keymap, enum request request, int key)
1215 {
1216 struct keybinding_table *table = &keybindings[keymap];
1218 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1219 if (!table->data)
1220 die("Failed to allocate keybinding");
1221 table->data[table->size].alias = key;
1222 table->data[table->size++].request = request;
1223 }
1225 /* Looks for a key binding first in the given map, then in the generic map, and
1226 * lastly in the default keybindings. */
1227 static enum request
1228 get_keybinding(enum keymap keymap, int key)
1229 {
1230 size_t i;
1232 for (i = 0; i < keybindings[keymap].size; i++)
1233 if (keybindings[keymap].data[i].alias == key)
1234 return keybindings[keymap].data[i].request;
1236 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1237 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1238 return keybindings[KEYMAP_GENERIC].data[i].request;
1240 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1241 if (default_keybindings[i].alias == key)
1242 return default_keybindings[i].request;
1244 return (enum request) key;
1245 }
1248 struct key {
1249 const char *name;
1250 int value;
1251 };
1253 static const struct key key_table[] = {
1254 { "Enter", KEY_RETURN },
1255 { "Space", ' ' },
1256 { "Backspace", KEY_BACKSPACE },
1257 { "Tab", KEY_TAB },
1258 { "Escape", KEY_ESC },
1259 { "Left", KEY_LEFT },
1260 { "Right", KEY_RIGHT },
1261 { "Up", KEY_UP },
1262 { "Down", KEY_DOWN },
1263 { "Insert", KEY_IC },
1264 { "Delete", KEY_DC },
1265 { "Hash", '#' },
1266 { "Home", KEY_HOME },
1267 { "End", KEY_END },
1268 { "PageUp", KEY_PPAGE },
1269 { "PageDown", KEY_NPAGE },
1270 { "F1", KEY_F(1) },
1271 { "F2", KEY_F(2) },
1272 { "F3", KEY_F(3) },
1273 { "F4", KEY_F(4) },
1274 { "F5", KEY_F(5) },
1275 { "F6", KEY_F(6) },
1276 { "F7", KEY_F(7) },
1277 { "F8", KEY_F(8) },
1278 { "F9", KEY_F(9) },
1279 { "F10", KEY_F(10) },
1280 { "F11", KEY_F(11) },
1281 { "F12", KEY_F(12) },
1282 };
1284 static int
1285 get_key_value(const char *name)
1286 {
1287 int i;
1289 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1290 if (!strcasecmp(key_table[i].name, name))
1291 return key_table[i].value;
1293 if (strlen(name) == 1 && isprint(*name))
1294 return (int) *name;
1296 return ERR;
1297 }
1299 static const char *
1300 get_key_name(int key_value)
1301 {
1302 static char key_char[] = "'X'";
1303 const char *seq = NULL;
1304 int key;
1306 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1307 if (key_table[key].value == key_value)
1308 seq = key_table[key].name;
1310 if (seq == NULL &&
1311 key_value < 127 &&
1312 isprint(key_value)) {
1313 key_char[1] = (char) key_value;
1314 seq = key_char;
1315 }
1317 return seq ? seq : "(no key)";
1318 }
1320 static bool
1321 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1322 {
1323 const char *sep = *pos > 0 ? ", " : "";
1324 const char *keyname = get_key_name(keybinding->alias);
1326 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1327 }
1329 static bool
1330 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1331 enum keymap keymap, bool all)
1332 {
1333 int i;
1335 for (i = 0; i < keybindings[keymap].size; i++) {
1336 if (keybindings[keymap].data[i].request == request) {
1337 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1338 return FALSE;
1339 if (!all)
1340 break;
1341 }
1342 }
1344 return TRUE;
1345 }
1347 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1349 static const char *
1350 get_keys(enum keymap keymap, enum request request, bool all)
1351 {
1352 static char buf[BUFSIZ];
1353 size_t pos = 0;
1354 int i;
1356 buf[pos] = 0;
1358 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1359 return "Too many keybindings!";
1360 if (pos > 0 && !all)
1361 return buf;
1363 if (keymap != KEYMAP_GENERIC) {
1364 /* Only the generic keymap includes the default keybindings when
1365 * listing all keys. */
1366 if (all)
1367 return buf;
1369 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1370 return "Too many keybindings!";
1371 if (pos)
1372 return buf;
1373 }
1375 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1376 if (default_keybindings[i].request == request) {
1377 if (!append_key(buf, &pos, &default_keybindings[i]))
1378 return "Too many keybindings!";
1379 if (!all)
1380 return buf;
1381 }
1382 }
1384 return buf;
1385 }
1387 struct run_request {
1388 enum keymap keymap;
1389 int key;
1390 const char *argv[SIZEOF_ARG];
1391 };
1393 static struct run_request *run_request;
1394 static size_t run_requests;
1396 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1398 static enum request
1399 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1400 {
1401 struct run_request *req;
1403 if (argc >= ARRAY_SIZE(req->argv) - 1)
1404 return REQ_NONE;
1406 if (!realloc_run_requests(&run_request, run_requests, 1))
1407 return REQ_NONE;
1409 req = &run_request[run_requests];
1410 req->keymap = keymap;
1411 req->key = key;
1412 req->argv[0] = NULL;
1414 if (!format_argv(req->argv, argv, FORMAT_NONE))
1415 return REQ_NONE;
1417 return REQ_NONE + ++run_requests;
1418 }
1420 static struct run_request *
1421 get_run_request(enum request request)
1422 {
1423 if (request <= REQ_NONE)
1424 return NULL;
1425 return &run_request[request - REQ_NONE - 1];
1426 }
1428 static void
1429 add_builtin_run_requests(void)
1430 {
1431 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1432 const char *commit[] = { "git", "commit", NULL };
1433 const char *gc[] = { "git", "gc", NULL };
1434 struct {
1435 enum keymap keymap;
1436 int key;
1437 int argc;
1438 const char **argv;
1439 } reqs[] = {
1440 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1441 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1442 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1443 };
1444 int i;
1446 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1447 enum request req;
1449 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1450 if (req != REQ_NONE)
1451 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1452 }
1453 }
1455 /*
1456 * User config file handling.
1457 */
1459 static int config_lineno;
1460 static bool config_errors;
1461 static const char *config_msg;
1463 static const struct enum_map color_map[] = {
1464 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1465 COLOR_MAP(DEFAULT),
1466 COLOR_MAP(BLACK),
1467 COLOR_MAP(BLUE),
1468 COLOR_MAP(CYAN),
1469 COLOR_MAP(GREEN),
1470 COLOR_MAP(MAGENTA),
1471 COLOR_MAP(RED),
1472 COLOR_MAP(WHITE),
1473 COLOR_MAP(YELLOW),
1474 };
1476 static const struct enum_map attr_map[] = {
1477 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1478 ATTR_MAP(NORMAL),
1479 ATTR_MAP(BLINK),
1480 ATTR_MAP(BOLD),
1481 ATTR_MAP(DIM),
1482 ATTR_MAP(REVERSE),
1483 ATTR_MAP(STANDOUT),
1484 ATTR_MAP(UNDERLINE),
1485 };
1487 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1489 static int parse_step(double *opt, const char *arg)
1490 {
1491 *opt = atoi(arg);
1492 if (!strchr(arg, '%'))
1493 return OK;
1495 /* "Shift down" so 100% and 1 does not conflict. */
1496 *opt = (*opt - 1) / 100;
1497 if (*opt >= 1.0) {
1498 *opt = 0.99;
1499 config_msg = "Step value larger than 100%";
1500 return ERR;
1501 }
1502 if (*opt < 0.0) {
1503 *opt = 1;
1504 config_msg = "Invalid step value";
1505 return ERR;
1506 }
1507 return OK;
1508 }
1510 static int
1511 parse_int(int *opt, const char *arg, int min, int max)
1512 {
1513 int value = atoi(arg);
1515 if (min <= value && value <= max) {
1516 *opt = value;
1517 return OK;
1518 }
1520 config_msg = "Integer value out of bound";
1521 return ERR;
1522 }
1524 static bool
1525 set_color(int *color, const char *name)
1526 {
1527 if (map_enum(color, color_map, name))
1528 return TRUE;
1529 if (!prefixcmp(name, "color"))
1530 return parse_int(color, name + 5, 0, 255) == OK;
1531 return FALSE;
1532 }
1534 /* Wants: object fgcolor bgcolor [attribute] */
1535 static int
1536 option_color_command(int argc, const char *argv[])
1537 {
1538 struct line_info *info;
1540 if (argc < 3) {
1541 config_msg = "Wrong number of arguments given to color command";
1542 return ERR;
1543 }
1545 info = get_line_info(argv[0]);
1546 if (!info) {
1547 static const struct enum_map obsolete[] = {
1548 ENUM_MAP("main-delim", LINE_DELIMITER),
1549 ENUM_MAP("main-date", LINE_DATE),
1550 ENUM_MAP("main-author", LINE_AUTHOR),
1551 };
1552 int index;
1554 if (!map_enum(&index, obsolete, argv[0])) {
1555 config_msg = "Unknown color name";
1556 return ERR;
1557 }
1558 info = &line_info[index];
1559 }
1561 if (!set_color(&info->fg, argv[1]) ||
1562 !set_color(&info->bg, argv[2])) {
1563 config_msg = "Unknown color";
1564 return ERR;
1565 }
1567 info->attr = 0;
1568 while (argc-- > 3) {
1569 int attr;
1571 if (!set_attribute(&attr, argv[argc])) {
1572 config_msg = "Unknown attribute";
1573 return ERR;
1574 }
1575 info->attr |= attr;
1576 }
1578 return OK;
1579 }
1581 static int parse_bool(bool *opt, const char *arg)
1582 {
1583 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1584 ? TRUE : FALSE;
1585 return OK;
1586 }
1588 static int
1589 parse_string(char *opt, const char *arg, size_t optsize)
1590 {
1591 int arglen = strlen(arg);
1593 switch (arg[0]) {
1594 case '\"':
1595 case '\'':
1596 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1597 config_msg = "Unmatched quotation";
1598 return ERR;
1599 }
1600 arg += 1; arglen -= 2;
1601 default:
1602 string_ncopy_do(opt, optsize, arg, arglen);
1603 return OK;
1604 }
1605 }
1607 /* Wants: name = value */
1608 static int
1609 option_set_command(int argc, const char *argv[])
1610 {
1611 if (argc != 3) {
1612 config_msg = "Wrong number of arguments given to set command";
1613 return ERR;
1614 }
1616 if (strcmp(argv[1], "=")) {
1617 config_msg = "No value assigned";
1618 return ERR;
1619 }
1621 if (!strcmp(argv[0], "show-author"))
1622 return parse_bool(&opt_author, argv[2]);
1624 if (!strcmp(argv[0], "show-date"))
1625 return parse_bool(&opt_date, argv[2]);
1627 if (!strcmp(argv[0], "date-short"))
1628 return parse_bool(&opt_date_short, argv[2]);
1630 if (!strcmp(argv[0], "show-rev-graph"))
1631 return parse_bool(&opt_rev_graph, argv[2]);
1633 if (!strcmp(argv[0], "show-refs"))
1634 return parse_bool(&opt_show_refs, argv[2]);
1636 if (!strcmp(argv[0], "show-line-numbers"))
1637 return parse_bool(&opt_line_number, argv[2]);
1639 if (!strcmp(argv[0], "line-graphics"))
1640 return parse_bool(&opt_line_graphics, argv[2]);
1642 if (!strcmp(argv[0], "line-number-interval"))
1643 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1645 if (!strcmp(argv[0], "author-width"))
1646 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1648 if (!strcmp(argv[0], "horizontal-scroll"))
1649 return parse_step(&opt_hscroll, argv[2]);
1651 if (!strcmp(argv[0], "split-view-height"))
1652 return parse_step(&opt_scale_split_view, argv[2]);
1654 if (!strcmp(argv[0], "tab-size"))
1655 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1657 if (!strcmp(argv[0], "commit-encoding"))
1658 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1660 config_msg = "Unknown variable name";
1661 return ERR;
1662 }
1664 /* Wants: mode request key */
1665 static int
1666 option_bind_command(int argc, const char *argv[])
1667 {
1668 enum request request;
1669 int keymap = -1;
1670 int key;
1672 if (argc < 3) {
1673 config_msg = "Wrong number of arguments given to bind command";
1674 return ERR;
1675 }
1677 if (set_keymap(&keymap, argv[0]) == ERR) {
1678 config_msg = "Unknown key map";
1679 return ERR;
1680 }
1682 key = get_key_value(argv[1]);
1683 if (key == ERR) {
1684 config_msg = "Unknown key";
1685 return ERR;
1686 }
1688 request = get_request(argv[2]);
1689 if (request == REQ_NONE) {
1690 static const struct enum_map obsolete[] = {
1691 ENUM_MAP("cherry-pick", REQ_NONE),
1692 ENUM_MAP("screen-resize", REQ_NONE),
1693 ENUM_MAP("tree-parent", REQ_PARENT),
1694 };
1695 int alias;
1697 if (map_enum(&alias, obsolete, argv[2])) {
1698 if (alias != REQ_NONE)
1699 add_keybinding(keymap, alias, key);
1700 config_msg = "Obsolete request name";
1701 return ERR;
1702 }
1703 }
1704 if (request == REQ_NONE && *argv[2]++ == '!')
1705 request = add_run_request(keymap, key, argc - 2, argv + 2);
1706 if (request == REQ_NONE) {
1707 config_msg = "Unknown request name";
1708 return ERR;
1709 }
1711 add_keybinding(keymap, request, key);
1713 return OK;
1714 }
1716 static int
1717 set_option(const char *opt, char *value)
1718 {
1719 const char *argv[SIZEOF_ARG];
1720 int argc = 0;
1722 if (!argv_from_string(argv, &argc, value)) {
1723 config_msg = "Too many option arguments";
1724 return ERR;
1725 }
1727 if (!strcmp(opt, "color"))
1728 return option_color_command(argc, argv);
1730 if (!strcmp(opt, "set"))
1731 return option_set_command(argc, argv);
1733 if (!strcmp(opt, "bind"))
1734 return option_bind_command(argc, argv);
1736 config_msg = "Unknown option command";
1737 return ERR;
1738 }
1740 static int
1741 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1742 {
1743 int status = OK;
1745 config_lineno++;
1746 config_msg = "Internal error";
1748 /* Check for comment markers, since read_properties() will
1749 * only ensure opt and value are split at first " \t". */
1750 optlen = strcspn(opt, "#");
1751 if (optlen == 0)
1752 return OK;
1754 if (opt[optlen] != 0) {
1755 config_msg = "No option value";
1756 status = ERR;
1758 } else {
1759 /* Look for comment endings in the value. */
1760 size_t len = strcspn(value, "#");
1762 if (len < valuelen) {
1763 valuelen = len;
1764 value[valuelen] = 0;
1765 }
1767 status = set_option(opt, value);
1768 }
1770 if (status == ERR) {
1771 warn("Error on line %d, near '%.*s': %s",
1772 config_lineno, (int) optlen, opt, config_msg);
1773 config_errors = TRUE;
1774 }
1776 /* Always keep going if errors are encountered. */
1777 return OK;
1778 }
1780 static void
1781 load_option_file(const char *path)
1782 {
1783 struct io io = {};
1785 /* It's OK that the file doesn't exist. */
1786 if (!io_open(&io, path))
1787 return;
1789 config_lineno = 0;
1790 config_errors = FALSE;
1792 if (io_load(&io, " \t", read_option) == ERR ||
1793 config_errors == TRUE)
1794 warn("Errors while loading %s.", path);
1795 }
1797 static int
1798 load_options(void)
1799 {
1800 const char *home = getenv("HOME");
1801 const char *tigrc_user = getenv("TIGRC_USER");
1802 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1803 char buf[SIZEOF_STR];
1805 add_builtin_run_requests();
1807 if (!tigrc_system)
1808 tigrc_system = SYSCONFDIR "/tigrc";
1809 load_option_file(tigrc_system);
1811 if (!tigrc_user) {
1812 if (!home || !string_format(buf, "%s/.tigrc", home))
1813 return ERR;
1814 tigrc_user = buf;
1815 }
1816 load_option_file(tigrc_user);
1818 return OK;
1819 }
1822 /*
1823 * The viewer
1824 */
1826 struct view;
1827 struct view_ops;
1829 /* The display array of active views and the index of the current view. */
1830 static struct view *display[2];
1831 static unsigned int current_view;
1833 #define foreach_displayed_view(view, i) \
1834 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1836 #define displayed_views() (display[1] != NULL ? 2 : 1)
1838 /* Current head and commit ID */
1839 static char ref_blob[SIZEOF_REF] = "";
1840 static char ref_commit[SIZEOF_REF] = "HEAD";
1841 static char ref_head[SIZEOF_REF] = "HEAD";
1843 struct view {
1844 const char *name; /* View name */
1845 const char *cmd_env; /* Command line set via environment */
1846 const char *id; /* Points to either of ref_{head,commit,blob} */
1848 struct view_ops *ops; /* View operations */
1850 enum keymap keymap; /* What keymap does this view have */
1851 bool git_dir; /* Whether the view requires a git directory. */
1853 char ref[SIZEOF_REF]; /* Hovered commit reference */
1854 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1856 int height, width; /* The width and height of the main window */
1857 WINDOW *win; /* The main window */
1858 WINDOW *title; /* The title window living below the main window */
1860 /* Navigation */
1861 unsigned long offset; /* Offset of the window top */
1862 unsigned long yoffset; /* Offset from the window side. */
1863 unsigned long lineno; /* Current line number */
1864 unsigned long p_offset; /* Previous offset of the window top */
1865 unsigned long p_yoffset;/* Previous offset from the window side */
1866 unsigned long p_lineno; /* Previous current line number */
1867 bool p_restore; /* Should the previous position be restored. */
1869 /* Searching */
1870 char grep[SIZEOF_STR]; /* Search string */
1871 regex_t *regex; /* Pre-compiled regexp */
1873 /* If non-NULL, points to the view that opened this view. If this view
1874 * is closed tig will switch back to the parent view. */
1875 struct view *parent;
1877 /* Buffering */
1878 size_t lines; /* Total number of lines */
1879 struct line *line; /* Line index */
1880 unsigned int digits; /* Number of digits in the lines member. */
1882 /* Drawing */
1883 struct line *curline; /* Line currently being drawn. */
1884 enum line_type curtype; /* Attribute currently used for drawing. */
1885 unsigned long col; /* Column when drawing. */
1886 bool has_scrolled; /* View was scrolled. */
1888 /* Loading */
1889 struct io io;
1890 struct io *pipe;
1891 time_t start_time;
1892 time_t update_secs;
1893 };
1895 struct view_ops {
1896 /* What type of content being displayed. Used in the title bar. */
1897 const char *type;
1898 /* Default command arguments. */
1899 const char **argv;
1900 /* Open and reads in all view content. */
1901 bool (*open)(struct view *view);
1902 /* Read one line; updates view->line. */
1903 bool (*read)(struct view *view, char *data);
1904 /* Draw one line; @lineno must be < view->height. */
1905 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1906 /* Depending on view handle a special requests. */
1907 enum request (*request)(struct view *view, enum request request, struct line *line);
1908 /* Search for regexp in a line. */
1909 bool (*grep)(struct view *view, struct line *line);
1910 /* Select line */
1911 void (*select)(struct view *view, struct line *line);
1912 };
1914 static struct view_ops blame_ops;
1915 static struct view_ops blob_ops;
1916 static struct view_ops diff_ops;
1917 static struct view_ops help_ops;
1918 static struct view_ops log_ops;
1919 static struct view_ops main_ops;
1920 static struct view_ops pager_ops;
1921 static struct view_ops stage_ops;
1922 static struct view_ops status_ops;
1923 static struct view_ops tree_ops;
1924 static struct view_ops branch_ops;
1926 #define VIEW_STR(name, env, ref, ops, map, git) \
1927 { name, #env, ref, ops, map, git }
1929 #define VIEW_(id, name, ops, git, ref) \
1930 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1933 static struct view views[] = {
1934 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1935 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1936 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1937 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1938 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1939 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1940 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
1941 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1942 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1943 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1944 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1945 };
1947 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1948 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1950 #define foreach_view(view, i) \
1951 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1953 #define view_is_displayed(view) \
1954 (view == display[0] || view == display[1])
1957 enum line_graphic {
1958 LINE_GRAPHIC_VLINE
1959 };
1961 static chtype line_graphics[] = {
1962 /* LINE_GRAPHIC_VLINE: */ '|'
1963 };
1965 static inline void
1966 set_view_attr(struct view *view, enum line_type type)
1967 {
1968 if (!view->curline->selected && view->curtype != type) {
1969 wattrset(view->win, get_line_attr(type));
1970 wchgat(view->win, -1, 0, type, NULL);
1971 view->curtype = type;
1972 }
1973 }
1975 static int
1976 draw_chars(struct view *view, enum line_type type, const char *string,
1977 int max_len, bool use_tilde)
1978 {
1979 int len = 0;
1980 int col = 0;
1981 int trimmed = FALSE;
1982 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1984 if (max_len <= 0)
1985 return 0;
1987 if (opt_utf8) {
1988 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1989 } else {
1990 col = len = strlen(string);
1991 if (len > max_len) {
1992 if (use_tilde) {
1993 max_len -= 1;
1994 }
1995 col = len = max_len;
1996 trimmed = TRUE;
1997 }
1998 }
2000 set_view_attr(view, type);
2001 if (len > 0)
2002 waddnstr(view->win, string, len);
2003 if (trimmed && use_tilde) {
2004 set_view_attr(view, LINE_DELIMITER);
2005 waddch(view->win, '~');
2006 col++;
2007 }
2009 return col;
2010 }
2012 static int
2013 draw_space(struct view *view, enum line_type type, int max, int spaces)
2014 {
2015 static char space[] = " ";
2016 int col = 0;
2018 spaces = MIN(max, spaces);
2020 while (spaces > 0) {
2021 int len = MIN(spaces, sizeof(space) - 1);
2023 col += draw_chars(view, type, space, len, FALSE);
2024 spaces -= len;
2025 }
2027 return col;
2028 }
2030 static bool
2031 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2032 {
2033 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2034 return view->width + view->yoffset <= view->col;
2035 }
2037 static bool
2038 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2039 {
2040 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2041 int max = view->width + view->yoffset - view->col;
2042 int i;
2044 if (max < size)
2045 size = max;
2047 set_view_attr(view, type);
2048 /* Using waddch() instead of waddnstr() ensures that
2049 * they'll be rendered correctly for the cursor line. */
2050 for (i = skip; i < size; i++)
2051 waddch(view->win, graphic[i]);
2053 view->col += size;
2054 if (size < max && skip <= size)
2055 waddch(view->win, ' ');
2056 view->col++;
2058 return view->width + view->yoffset <= view->col;
2059 }
2061 static bool
2062 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2063 {
2064 int max = MIN(view->width + view->yoffset - view->col, len);
2065 int col;
2067 if (text)
2068 col = draw_chars(view, type, text, max - 1, trim);
2069 else
2070 col = draw_space(view, type, max - 1, max - 1);
2072 view->col += col;
2073 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2074 return view->width + view->yoffset <= view->col;
2075 }
2077 static bool
2078 draw_date(struct view *view, time_t *time)
2079 {
2080 const char *date = mkdate(time);
2081 int cols = opt_date_short ? DATE_SHORT_COLS : DATE_COLS;
2083 return draw_field(view, LINE_DATE, date, cols, FALSE);
2084 }
2086 static bool
2087 draw_author(struct view *view, const char *author)
2088 {
2089 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2091 if (!trim) {
2092 static char initials[10];
2093 size_t pos;
2095 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2097 memset(initials, 0, sizeof(initials));
2098 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2099 while (is_initial_sep(*author))
2100 author++;
2101 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2102 while (*author && !is_initial_sep(author[1]))
2103 author++;
2104 }
2106 author = initials;
2107 }
2109 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2110 }
2112 static bool
2113 draw_mode(struct view *view, mode_t mode)
2114 {
2115 const char *str;
2117 if (S_ISDIR(mode))
2118 str = "drwxr-xr-x";
2119 else if (S_ISLNK(mode))
2120 str = "lrwxrwxrwx";
2121 else if (S_ISGITLINK(mode))
2122 str = "m---------";
2123 else if (S_ISREG(mode) && mode & S_IXUSR)
2124 str = "-rwxr-xr-x";
2125 else if (S_ISREG(mode))
2126 str = "-rw-r--r--";
2127 else
2128 str = "----------";
2130 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2131 }
2133 static bool
2134 draw_lineno(struct view *view, unsigned int lineno)
2135 {
2136 char number[10];
2137 int digits3 = view->digits < 3 ? 3 : view->digits;
2138 int max = MIN(view->width + view->yoffset - view->col, digits3);
2139 char *text = NULL;
2141 lineno += view->offset + 1;
2142 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2143 static char fmt[] = "%1ld";
2145 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2146 if (string_format(number, fmt, lineno))
2147 text = number;
2148 }
2149 if (text)
2150 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2151 else
2152 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2153 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2154 }
2156 static bool
2157 draw_view_line(struct view *view, unsigned int lineno)
2158 {
2159 struct line *line;
2160 bool selected = (view->offset + lineno == view->lineno);
2162 assert(view_is_displayed(view));
2164 if (view->offset + lineno >= view->lines)
2165 return FALSE;
2167 line = &view->line[view->offset + lineno];
2169 wmove(view->win, lineno, 0);
2170 if (line->cleareol)
2171 wclrtoeol(view->win);
2172 view->col = 0;
2173 view->curline = line;
2174 view->curtype = LINE_NONE;
2175 line->selected = FALSE;
2176 line->dirty = line->cleareol = 0;
2178 if (selected) {
2179 set_view_attr(view, LINE_CURSOR);
2180 line->selected = TRUE;
2181 view->ops->select(view, line);
2182 }
2184 return view->ops->draw(view, line, lineno);
2185 }
2187 static void
2188 redraw_view_dirty(struct view *view)
2189 {
2190 bool dirty = FALSE;
2191 int lineno;
2193 for (lineno = 0; lineno < view->height; lineno++) {
2194 if (view->offset + lineno >= view->lines)
2195 break;
2196 if (!view->line[view->offset + lineno].dirty)
2197 continue;
2198 dirty = TRUE;
2199 if (!draw_view_line(view, lineno))
2200 break;
2201 }
2203 if (!dirty)
2204 return;
2205 wnoutrefresh(view->win);
2206 }
2208 static void
2209 redraw_view_from(struct view *view, int lineno)
2210 {
2211 assert(0 <= lineno && lineno < view->height);
2213 for (; lineno < view->height; lineno++) {
2214 if (!draw_view_line(view, lineno))
2215 break;
2216 }
2218 wnoutrefresh(view->win);
2219 }
2221 static void
2222 redraw_view(struct view *view)
2223 {
2224 werase(view->win);
2225 redraw_view_from(view, 0);
2226 }
2229 static void
2230 update_view_title(struct view *view)
2231 {
2232 char buf[SIZEOF_STR];
2233 char state[SIZEOF_STR];
2234 size_t bufpos = 0, statelen = 0;
2236 assert(view_is_displayed(view));
2238 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2239 unsigned int view_lines = view->offset + view->height;
2240 unsigned int lines = view->lines
2241 ? MIN(view_lines, view->lines) * 100 / view->lines
2242 : 0;
2244 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2245 view->ops->type,
2246 view->lineno + 1,
2247 view->lines,
2248 lines);
2250 }
2252 if (view->pipe) {
2253 time_t secs = time(NULL) - view->start_time;
2255 /* Three git seconds are a long time ... */
2256 if (secs > 2)
2257 string_format_from(state, &statelen, " loading %lds", secs);
2258 }
2260 string_format_from(buf, &bufpos, "[%s]", view->name);
2261 if (*view->ref && bufpos < view->width) {
2262 size_t refsize = strlen(view->ref);
2263 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2265 if (minsize < view->width)
2266 refsize = view->width - minsize + 7;
2267 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2268 }
2270 if (statelen && bufpos < view->width) {
2271 string_format_from(buf, &bufpos, "%s", state);
2272 }
2274 if (view == display[current_view])
2275 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2276 else
2277 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2279 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2280 wclrtoeol(view->title);
2281 wnoutrefresh(view->title);
2282 }
2284 static int
2285 apply_step(double step, int value)
2286 {
2287 if (step >= 1)
2288 return (int) step;
2289 value *= step + 0.01;
2290 return value ? value : 1;
2291 }
2293 static void
2294 resize_display(void)
2295 {
2296 int offset, i;
2297 struct view *base = display[0];
2298 struct view *view = display[1] ? display[1] : display[0];
2300 /* Setup window dimensions */
2302 getmaxyx(stdscr, base->height, base->width);
2304 /* Make room for the status window. */
2305 base->height -= 1;
2307 if (view != base) {
2308 /* Horizontal split. */
2309 view->width = base->width;
2310 view->height = apply_step(opt_scale_split_view, base->height);
2311 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2312 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2313 base->height -= view->height;
2315 /* Make room for the title bar. */
2316 view->height -= 1;
2317 }
2319 /* Make room for the title bar. */
2320 base->height -= 1;
2322 offset = 0;
2324 foreach_displayed_view (view, i) {
2325 if (!view->win) {
2326 view->win = newwin(view->height, 0, offset, 0);
2327 if (!view->win)
2328 die("Failed to create %s view", view->name);
2330 scrollok(view->win, FALSE);
2332 view->title = newwin(1, 0, offset + view->height, 0);
2333 if (!view->title)
2334 die("Failed to create title window");
2336 } else {
2337 wresize(view->win, view->height, view->width);
2338 mvwin(view->win, offset, 0);
2339 mvwin(view->title, offset + view->height, 0);
2340 }
2342 offset += view->height + 1;
2343 }
2344 }
2346 static void
2347 redraw_display(bool clear)
2348 {
2349 struct view *view;
2350 int i;
2352 foreach_displayed_view (view, i) {
2353 if (clear)
2354 wclear(view->win);
2355 redraw_view(view);
2356 update_view_title(view);
2357 }
2358 }
2360 static void
2361 toggle_view_option(bool *option, const char *help)
2362 {
2363 *option = !*option;
2364 redraw_display(FALSE);
2365 report("%sabling %s", *option ? "En" : "Dis", help);
2366 }
2368 static void
2369 open_option_menu(void)
2370 {
2371 const struct menu_item menu[] = {
2372 { '.', "line numbers", &opt_line_number },
2373 { 'D', "date display", &opt_date },
2374 { 'A', "author display", &opt_author },
2375 { 'g', "revision graph display", &opt_rev_graph },
2376 { 'F', "reference display", &opt_show_refs },
2377 { 0 }
2378 };
2379 int selected = 0;
2381 if (prompt_menu("Toggle option", menu, &selected))
2382 toggle_view_option(menu[selected].data, menu[selected].text);
2383 }
2385 static void
2386 maximize_view(struct view *view)
2387 {
2388 memset(display, 0, sizeof(display));
2389 current_view = 0;
2390 display[current_view] = view;
2391 resize_display();
2392 redraw_display(FALSE);
2393 report("");
2394 }
2397 /*
2398 * Navigation
2399 */
2401 static bool
2402 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2403 {
2404 if (lineno >= view->lines)
2405 lineno = view->lines > 0 ? view->lines - 1 : 0;
2407 if (offset > lineno || offset + view->height <= lineno) {
2408 unsigned long half = view->height / 2;
2410 if (lineno > half)
2411 offset = lineno - half;
2412 else
2413 offset = 0;
2414 }
2416 if (offset != view->offset || lineno != view->lineno) {
2417 view->offset = offset;
2418 view->lineno = lineno;
2419 return TRUE;
2420 }
2422 return FALSE;
2423 }
2425 /* Scrolling backend */
2426 static void
2427 do_scroll_view(struct view *view, int lines)
2428 {
2429 bool redraw_current_line = FALSE;
2431 /* The rendering expects the new offset. */
2432 view->offset += lines;
2434 assert(0 <= view->offset && view->offset < view->lines);
2435 assert(lines);
2437 /* Move current line into the view. */
2438 if (view->lineno < view->offset) {
2439 view->lineno = view->offset;
2440 redraw_current_line = TRUE;
2441 } else if (view->lineno >= view->offset + view->height) {
2442 view->lineno = view->offset + view->height - 1;
2443 redraw_current_line = TRUE;
2444 }
2446 assert(view->offset <= view->lineno && view->lineno < view->lines);
2448 /* Redraw the whole screen if scrolling is pointless. */
2449 if (view->height < ABS(lines)) {
2450 redraw_view(view);
2452 } else {
2453 int line = lines > 0 ? view->height - lines : 0;
2454 int end = line + ABS(lines);
2456 scrollok(view->win, TRUE);
2457 wscrl(view->win, lines);
2458 scrollok(view->win, FALSE);
2460 while (line < end && draw_view_line(view, line))
2461 line++;
2463 if (redraw_current_line)
2464 draw_view_line(view, view->lineno - view->offset);
2465 wnoutrefresh(view->win);
2466 }
2468 view->has_scrolled = TRUE;
2469 report("");
2470 }
2472 /* Scroll frontend */
2473 static void
2474 scroll_view(struct view *view, enum request request)
2475 {
2476 int lines = 1;
2478 assert(view_is_displayed(view));
2480 switch (request) {
2481 case REQ_SCROLL_LEFT:
2482 if (view->yoffset == 0) {
2483 report("Cannot scroll beyond the first column");
2484 return;
2485 }
2486 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2487 view->yoffset = 0;
2488 else
2489 view->yoffset -= apply_step(opt_hscroll, view->width);
2490 redraw_view_from(view, 0);
2491 report("");
2492 return;
2493 case REQ_SCROLL_RIGHT:
2494 view->yoffset += apply_step(opt_hscroll, view->width);
2495 redraw_view(view);
2496 report("");
2497 return;
2498 case REQ_SCROLL_PAGE_DOWN:
2499 lines = view->height;
2500 case REQ_SCROLL_LINE_DOWN:
2501 if (view->offset + lines > view->lines)
2502 lines = view->lines - view->offset;
2504 if (lines == 0 || view->offset + view->height >= view->lines) {
2505 report("Cannot scroll beyond the last line");
2506 return;
2507 }
2508 break;
2510 case REQ_SCROLL_PAGE_UP:
2511 lines = view->height;
2512 case REQ_SCROLL_LINE_UP:
2513 if (lines > view->offset)
2514 lines = view->offset;
2516 if (lines == 0) {
2517 report("Cannot scroll beyond the first line");
2518 return;
2519 }
2521 lines = -lines;
2522 break;
2524 default:
2525 die("request %d not handled in switch", request);
2526 }
2528 do_scroll_view(view, lines);
2529 }
2531 /* Cursor moving */
2532 static void
2533 move_view(struct view *view, enum request request)
2534 {
2535 int scroll_steps = 0;
2536 int steps;
2538 switch (request) {
2539 case REQ_MOVE_FIRST_LINE:
2540 steps = -view->lineno;
2541 break;
2543 case REQ_MOVE_LAST_LINE:
2544 steps = view->lines - view->lineno - 1;
2545 break;
2547 case REQ_MOVE_PAGE_UP:
2548 steps = view->height > view->lineno
2549 ? -view->lineno : -view->height;
2550 break;
2552 case REQ_MOVE_PAGE_DOWN:
2553 steps = view->lineno + view->height >= view->lines
2554 ? view->lines - view->lineno - 1 : view->height;
2555 break;
2557 case REQ_MOVE_UP:
2558 steps = -1;
2559 break;
2561 case REQ_MOVE_DOWN:
2562 steps = 1;
2563 break;
2565 default:
2566 die("request %d not handled in switch", request);
2567 }
2569 if (steps <= 0 && view->lineno == 0) {
2570 report("Cannot move beyond the first line");
2571 return;
2573 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2574 report("Cannot move beyond the last line");
2575 return;
2576 }
2578 /* Move the current line */
2579 view->lineno += steps;
2580 assert(0 <= view->lineno && view->lineno < view->lines);
2582 /* Check whether the view needs to be scrolled */
2583 if (view->lineno < view->offset ||
2584 view->lineno >= view->offset + view->height) {
2585 scroll_steps = steps;
2586 if (steps < 0 && -steps > view->offset) {
2587 scroll_steps = -view->offset;
2589 } else if (steps > 0) {
2590 if (view->lineno == view->lines - 1 &&
2591 view->lines > view->height) {
2592 scroll_steps = view->lines - view->offset - 1;
2593 if (scroll_steps >= view->height)
2594 scroll_steps -= view->height - 1;
2595 }
2596 }
2597 }
2599 if (!view_is_displayed(view)) {
2600 view->offset += scroll_steps;
2601 assert(0 <= view->offset && view->offset < view->lines);
2602 view->ops->select(view, &view->line[view->lineno]);
2603 return;
2604 }
2606 /* Repaint the old "current" line if we be scrolling */
2607 if (ABS(steps) < view->height)
2608 draw_view_line(view, view->lineno - steps - view->offset);
2610 if (scroll_steps) {
2611 do_scroll_view(view, scroll_steps);
2612 return;
2613 }
2615 /* Draw the current line */
2616 draw_view_line(view, view->lineno - view->offset);
2618 wnoutrefresh(view->win);
2619 report("");
2620 }
2623 /*
2624 * Searching
2625 */
2627 static void search_view(struct view *view, enum request request);
2629 static bool
2630 grep_text(struct view *view, const char *text[])
2631 {
2632 regmatch_t pmatch;
2633 size_t i;
2635 for (i = 0; text[i]; i++)
2636 if (*text[i] &&
2637 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2638 return TRUE;
2639 return FALSE;
2640 }
2642 static void
2643 select_view_line(struct view *view, unsigned long lineno)
2644 {
2645 unsigned long old_lineno = view->lineno;
2646 unsigned long old_offset = view->offset;
2648 if (goto_view_line(view, view->offset, lineno)) {
2649 if (view_is_displayed(view)) {
2650 if (old_offset != view->offset) {
2651 redraw_view(view);
2652 } else {
2653 draw_view_line(view, old_lineno - view->offset);
2654 draw_view_line(view, view->lineno - view->offset);
2655 wnoutrefresh(view->win);
2656 }
2657 } else {
2658 view->ops->select(view, &view->line[view->lineno]);
2659 }
2660 }
2661 }
2663 static void
2664 find_next(struct view *view, enum request request)
2665 {
2666 unsigned long lineno = view->lineno;
2667 int direction;
2669 if (!*view->grep) {
2670 if (!*opt_search)
2671 report("No previous search");
2672 else
2673 search_view(view, request);
2674 return;
2675 }
2677 switch (request) {
2678 case REQ_SEARCH:
2679 case REQ_FIND_NEXT:
2680 direction = 1;
2681 break;
2683 case REQ_SEARCH_BACK:
2684 case REQ_FIND_PREV:
2685 direction = -1;
2686 break;
2688 default:
2689 return;
2690 }
2692 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2693 lineno += direction;
2695 /* Note, lineno is unsigned long so will wrap around in which case it
2696 * will become bigger than view->lines. */
2697 for (; lineno < view->lines; lineno += direction) {
2698 if (view->ops->grep(view, &view->line[lineno])) {
2699 select_view_line(view, lineno);
2700 report("Line %ld matches '%s'", lineno + 1, view->grep);
2701 return;
2702 }
2703 }
2705 report("No match found for '%s'", view->grep);
2706 }
2708 static void
2709 search_view(struct view *view, enum request request)
2710 {
2711 int regex_err;
2713 if (view->regex) {
2714 regfree(view->regex);
2715 *view->grep = 0;
2716 } else {
2717 view->regex = calloc(1, sizeof(*view->regex));
2718 if (!view->regex)
2719 return;
2720 }
2722 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2723 if (regex_err != 0) {
2724 char buf[SIZEOF_STR] = "unknown error";
2726 regerror(regex_err, view->regex, buf, sizeof(buf));
2727 report("Search failed: %s", buf);
2728 return;
2729 }
2731 string_copy(view->grep, opt_search);
2733 find_next(view, request);
2734 }
2736 /*
2737 * Incremental updating
2738 */
2740 static void
2741 reset_view(struct view *view)
2742 {
2743 int i;
2745 for (i = 0; i < view->lines; i++)
2746 free(view->line[i].data);
2747 free(view->line);
2749 view->p_offset = view->offset;
2750 view->p_yoffset = view->yoffset;
2751 view->p_lineno = view->lineno;
2753 view->line = NULL;
2754 view->offset = 0;
2755 view->yoffset = 0;
2756 view->lines = 0;
2757 view->lineno = 0;
2758 view->vid[0] = 0;
2759 view->update_secs = 0;
2760 }
2762 static void
2763 free_argv(const char *argv[])
2764 {
2765 int argc;
2767 for (argc = 0; argv[argc]; argc++)
2768 free((void *) argv[argc]);
2769 }
2771 static bool
2772 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2773 {
2774 char buf[SIZEOF_STR];
2775 int argc;
2776 bool noreplace = flags == FORMAT_NONE;
2778 free_argv(dst_argv);
2780 for (argc = 0; src_argv[argc]; argc++) {
2781 const char *arg = src_argv[argc];
2782 size_t bufpos = 0;
2784 while (arg) {
2785 char *next = strstr(arg, "%(");
2786 int len = next - arg;
2787 const char *value;
2789 if (!next || noreplace) {
2790 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2791 noreplace = TRUE;
2792 len = strlen(arg);
2793 value = "";
2795 } else if (!prefixcmp(next, "%(directory)")) {
2796 value = opt_path;
2798 } else if (!prefixcmp(next, "%(file)")) {
2799 value = opt_file;
2801 } else if (!prefixcmp(next, "%(ref)")) {
2802 value = *opt_ref ? opt_ref : "HEAD";
2804 } else if (!prefixcmp(next, "%(head)")) {
2805 value = ref_head;
2807 } else if (!prefixcmp(next, "%(commit)")) {
2808 value = ref_commit;
2810 } else if (!prefixcmp(next, "%(blob)")) {
2811 value = ref_blob;
2813 } else {
2814 report("Unknown replacement: `%s`", next);
2815 return FALSE;
2816 }
2818 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2819 return FALSE;
2821 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2822 }
2824 dst_argv[argc] = strdup(buf);
2825 if (!dst_argv[argc])
2826 break;
2827 }
2829 dst_argv[argc] = NULL;
2831 return src_argv[argc] == NULL;
2832 }
2834 static bool
2835 restore_view_position(struct view *view)
2836 {
2837 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2838 return FALSE;
2840 /* Changing the view position cancels the restoring. */
2841 /* FIXME: Changing back to the first line is not detected. */
2842 if (view->offset != 0 || view->lineno != 0) {
2843 view->p_restore = FALSE;
2844 return FALSE;
2845 }
2847 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2848 view_is_displayed(view))
2849 werase(view->win);
2851 view->yoffset = view->p_yoffset;
2852 view->p_restore = FALSE;
2854 return TRUE;
2855 }
2857 static void
2858 end_update(struct view *view, bool force)
2859 {
2860 if (!view->pipe)
2861 return;
2862 while (!view->ops->read(view, NULL))
2863 if (!force)
2864 return;
2865 set_nonblocking_input(FALSE);
2866 if (force)
2867 kill_io(view->pipe);
2868 done_io(view->pipe);
2869 view->pipe = NULL;
2870 }
2872 static void
2873 setup_update(struct view *view, const char *vid)
2874 {
2875 set_nonblocking_input(TRUE);
2876 reset_view(view);
2877 string_copy_rev(view->vid, vid);
2878 view->pipe = &view->io;
2879 view->start_time = time(NULL);
2880 }
2882 static bool
2883 prepare_update(struct view *view, const char *argv[], const char *dir,
2884 enum format_flags flags)
2885 {
2886 if (view->pipe)
2887 end_update(view, TRUE);
2888 return init_io_rd(&view->io, argv, dir, flags);
2889 }
2891 static bool
2892 prepare_update_file(struct view *view, const char *name)
2893 {
2894 if (view->pipe)
2895 end_update(view, TRUE);
2896 return io_open(&view->io, name);
2897 }
2899 static bool
2900 begin_update(struct view *view, bool refresh)
2901 {
2902 if (view->pipe)
2903 end_update(view, TRUE);
2905 if (refresh) {
2906 if (!start_io(&view->io))
2907 return FALSE;
2909 } else {
2910 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2911 opt_path[0] = 0;
2913 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2914 return FALSE;
2916 /* Put the current ref_* value to the view title ref
2917 * member. This is needed by the blob view. Most other
2918 * views sets it automatically after loading because the
2919 * first line is a commit line. */
2920 string_copy_rev(view->ref, view->id);
2921 }
2923 setup_update(view, view->id);
2925 return TRUE;
2926 }
2928 static bool
2929 update_view(struct view *view)
2930 {
2931 char out_buffer[BUFSIZ * 2];
2932 char *line;
2933 /* Clear the view and redraw everything since the tree sorting
2934 * might have rearranged things. */
2935 bool redraw = view->lines == 0;
2936 bool can_read = TRUE;
2938 if (!view->pipe)
2939 return TRUE;
2941 if (!io_can_read(view->pipe)) {
2942 if (view->lines == 0 && view_is_displayed(view)) {
2943 time_t secs = time(NULL) - view->start_time;
2945 if (secs > 1 && secs > view->update_secs) {
2946 if (view->update_secs == 0)
2947 redraw_view(view);
2948 update_view_title(view);
2949 view->update_secs = secs;
2950 }
2951 }
2952 return TRUE;
2953 }
2955 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2956 if (opt_iconv != ICONV_NONE) {
2957 ICONV_CONST char *inbuf = line;
2958 size_t inlen = strlen(line) + 1;
2960 char *outbuf = out_buffer;
2961 size_t outlen = sizeof(out_buffer);
2963 size_t ret;
2965 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2966 if (ret != (size_t) -1)
2967 line = out_buffer;
2968 }
2970 if (!view->ops->read(view, line)) {
2971 report("Allocation failure");
2972 end_update(view, TRUE);
2973 return FALSE;
2974 }
2975 }
2977 {
2978 unsigned long lines = view->lines;
2979 int digits;
2981 for (digits = 0; lines; digits++)
2982 lines /= 10;
2984 /* Keep the displayed view in sync with line number scaling. */
2985 if (digits != view->digits) {
2986 view->digits = digits;
2987 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2988 redraw = TRUE;
2989 }
2990 }
2992 if (io_error(view->pipe)) {
2993 report("Failed to read: %s", io_strerror(view->pipe));
2994 end_update(view, TRUE);
2996 } else if (io_eof(view->pipe)) {
2997 report("");
2998 end_update(view, FALSE);
2999 }
3001 if (restore_view_position(view))
3002 redraw = TRUE;
3004 if (!view_is_displayed(view))
3005 return TRUE;
3007 if (redraw)
3008 redraw_view_from(view, 0);
3009 else
3010 redraw_view_dirty(view);
3012 /* Update the title _after_ the redraw so that if the redraw picks up a
3013 * commit reference in view->ref it'll be available here. */
3014 update_view_title(view);
3015 return TRUE;
3016 }
3018 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3020 static struct line *
3021 add_line_data(struct view *view, void *data, enum line_type type)
3022 {
3023 struct line *line;
3025 if (!realloc_lines(&view->line, view->lines, 1))
3026 return NULL;
3028 line = &view->line[view->lines++];
3029 memset(line, 0, sizeof(*line));
3030 line->type = type;
3031 line->data = data;
3032 line->dirty = 1;
3034 return line;
3035 }
3037 static struct line *
3038 add_line_text(struct view *view, const char *text, enum line_type type)
3039 {
3040 char *data = text ? strdup(text) : NULL;
3042 return data ? add_line_data(view, data, type) : NULL;
3043 }
3045 static struct line *
3046 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3047 {
3048 char buf[SIZEOF_STR];
3049 va_list args;
3051 va_start(args, fmt);
3052 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3053 buf[0] = 0;
3054 va_end(args);
3056 return buf[0] ? add_line_text(view, buf, type) : NULL;
3057 }
3059 /*
3060 * View opening
3061 */
3063 enum open_flags {
3064 OPEN_DEFAULT = 0, /* Use default view switching. */
3065 OPEN_SPLIT = 1, /* Split current view. */
3066 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3067 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3068 OPEN_PREPARED = 32, /* Open already prepared command. */
3069 };
3071 static void
3072 open_view(struct view *prev, enum request request, enum open_flags flags)
3073 {
3074 bool split = !!(flags & OPEN_SPLIT);
3075 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3076 bool nomaximize = !!(flags & OPEN_REFRESH);
3077 struct view *view = VIEW(request);
3078 int nviews = displayed_views();
3079 struct view *base_view = display[0];
3081 if (view == prev && nviews == 1 && !reload) {
3082 report("Already in %s view", view->name);
3083 return;
3084 }
3086 if (view->git_dir && !opt_git_dir[0]) {
3087 report("The %s view is disabled in pager view", view->name);
3088 return;
3089 }
3091 if (split) {
3092 display[1] = view;
3093 current_view = 1;
3094 } else if (!nomaximize) {
3095 /* Maximize the current view. */
3096 memset(display, 0, sizeof(display));
3097 current_view = 0;
3098 display[current_view] = view;
3099 }
3101 /* Resize the view when switching between split- and full-screen,
3102 * or when switching between two different full-screen views. */
3103 if (nviews != displayed_views() ||
3104 (nviews == 1 && base_view != display[0]))
3105 resize_display();
3107 if (view->ops->open) {
3108 if (view->pipe)
3109 end_update(view, TRUE);
3110 if (!view->ops->open(view)) {
3111 report("Failed to load %s view", view->name);
3112 return;
3113 }
3114 restore_view_position(view);
3116 } else if ((reload || strcmp(view->vid, view->id)) &&
3117 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3118 report("Failed to load %s view", view->name);
3119 return;
3120 }
3122 if (split && prev->lineno - prev->offset >= prev->height) {
3123 /* Take the title line into account. */
3124 int lines = prev->lineno - prev->offset - prev->height + 1;
3126 /* Scroll the view that was split if the current line is
3127 * outside the new limited view. */
3128 do_scroll_view(prev, lines);
3129 }
3131 if (prev && view != prev) {
3132 if (split) {
3133 /* "Blur" the previous view. */
3134 update_view_title(prev);
3135 }
3137 view->parent = prev;
3138 }
3140 if (view->pipe && view->lines == 0) {
3141 /* Clear the old view and let the incremental updating refill
3142 * the screen. */
3143 werase(view->win);
3144 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3145 report("");
3146 } else if (view_is_displayed(view)) {
3147 redraw_view(view);
3148 report("");
3149 }
3150 }
3152 static void
3153 open_external_viewer(const char *argv[], const char *dir)
3154 {
3155 def_prog_mode(); /* save current tty modes */
3156 endwin(); /* restore original tty modes */
3157 run_io_fg(argv, dir);
3158 fprintf(stderr, "Press Enter to continue");
3159 getc(opt_tty);
3160 reset_prog_mode();
3161 redraw_display(TRUE);
3162 }
3164 static void
3165 open_mergetool(const char *file)
3166 {
3167 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3169 open_external_viewer(mergetool_argv, opt_cdup);
3170 }
3172 static void
3173 open_editor(bool from_root, const char *file)
3174 {
3175 const char *editor_argv[] = { "vi", file, NULL };
3176 const char *editor;
3178 editor = getenv("GIT_EDITOR");
3179 if (!editor && *opt_editor)
3180 editor = opt_editor;
3181 if (!editor)
3182 editor = getenv("VISUAL");
3183 if (!editor)
3184 editor = getenv("EDITOR");
3185 if (!editor)
3186 editor = "vi";
3188 editor_argv[0] = editor;
3189 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3190 }
3192 static void
3193 open_run_request(enum request request)
3194 {
3195 struct run_request *req = get_run_request(request);
3196 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3198 if (!req) {
3199 report("Unknown run request");
3200 return;
3201 }
3203 if (format_argv(argv, req->argv, FORMAT_ALL))
3204 open_external_viewer(argv, NULL);
3205 free_argv(argv);
3206 }
3208 /*
3209 * User request switch noodle
3210 */
3212 static int
3213 view_driver(struct view *view, enum request request)
3214 {
3215 int i;
3217 if (request == REQ_NONE)
3218 return TRUE;
3220 if (request > REQ_NONE) {
3221 open_run_request(request);
3222 /* FIXME: When all views can refresh always do this. */
3223 if (view == VIEW(REQ_VIEW_STATUS) ||
3224 view == VIEW(REQ_VIEW_MAIN) ||
3225 view == VIEW(REQ_VIEW_LOG) ||
3226 view == VIEW(REQ_VIEW_BRANCH) ||
3227 view == VIEW(REQ_VIEW_STAGE))
3228 request = REQ_REFRESH;
3229 else
3230 return TRUE;
3231 }
3233 if (view && view->lines) {
3234 request = view->ops->request(view, request, &view->line[view->lineno]);
3235 if (request == REQ_NONE)
3236 return TRUE;
3237 }
3239 switch (request) {
3240 case REQ_MOVE_UP:
3241 case REQ_MOVE_DOWN:
3242 case REQ_MOVE_PAGE_UP:
3243 case REQ_MOVE_PAGE_DOWN:
3244 case REQ_MOVE_FIRST_LINE:
3245 case REQ_MOVE_LAST_LINE:
3246 move_view(view, request);
3247 break;
3249 case REQ_SCROLL_LEFT:
3250 case REQ_SCROLL_RIGHT:
3251 case REQ_SCROLL_LINE_DOWN:
3252 case REQ_SCROLL_LINE_UP:
3253 case REQ_SCROLL_PAGE_DOWN:
3254 case REQ_SCROLL_PAGE_UP:
3255 scroll_view(view, request);
3256 break;
3258 case REQ_VIEW_BLAME:
3259 if (!opt_file[0]) {
3260 report("No file chosen, press %s to open tree view",
3261 get_key(view->keymap, REQ_VIEW_TREE));
3262 break;
3263 }
3264 open_view(view, request, OPEN_DEFAULT);
3265 break;
3267 case REQ_VIEW_BLOB:
3268 if (!ref_blob[0]) {
3269 report("No file chosen, press %s to open tree view",
3270 get_key(view->keymap, REQ_VIEW_TREE));
3271 break;
3272 }
3273 open_view(view, request, OPEN_DEFAULT);
3274 break;
3276 case REQ_VIEW_PAGER:
3277 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3278 report("No pager content, press %s to run command from prompt",
3279 get_key(view->keymap, REQ_PROMPT));
3280 break;
3281 }
3282 open_view(view, request, OPEN_DEFAULT);
3283 break;
3285 case REQ_VIEW_STAGE:
3286 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3287 report("No stage content, press %s to open the status view and choose file",
3288 get_key(view->keymap, REQ_VIEW_STATUS));
3289 break;
3290 }
3291 open_view(view, request, OPEN_DEFAULT);
3292 break;
3294 case REQ_VIEW_STATUS:
3295 if (opt_is_inside_work_tree == FALSE) {
3296 report("The status view requires a working tree");
3297 break;
3298 }
3299 open_view(view, request, OPEN_DEFAULT);
3300 break;
3302 case REQ_VIEW_MAIN:
3303 case REQ_VIEW_DIFF:
3304 case REQ_VIEW_LOG:
3305 case REQ_VIEW_TREE:
3306 case REQ_VIEW_HELP:
3307 case REQ_VIEW_BRANCH:
3308 open_view(view, request, OPEN_DEFAULT);
3309 break;
3311 case REQ_NEXT:
3312 case REQ_PREVIOUS:
3313 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3315 if ((view == VIEW(REQ_VIEW_DIFF) &&
3316 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3317 (view == VIEW(REQ_VIEW_DIFF) &&
3318 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3319 (view == VIEW(REQ_VIEW_STAGE) &&
3320 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3321 (view == VIEW(REQ_VIEW_BLOB) &&
3322 view->parent == VIEW(REQ_VIEW_TREE)) ||
3323 (view == VIEW(REQ_VIEW_MAIN) &&
3324 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3325 int line;
3327 view = view->parent;
3328 line = view->lineno;
3329 move_view(view, request);
3330 if (view_is_displayed(view))
3331 update_view_title(view);
3332 if (line != view->lineno)
3333 view->ops->request(view, REQ_ENTER,
3334 &view->line[view->lineno]);
3336 } else {
3337 move_view(view, request);
3338 }
3339 break;
3341 case REQ_VIEW_NEXT:
3342 {
3343 int nviews = displayed_views();
3344 int next_view = (current_view + 1) % nviews;
3346 if (next_view == current_view) {
3347 report("Only one view is displayed");
3348 break;
3349 }
3351 current_view = next_view;
3352 /* Blur out the title of the previous view. */
3353 update_view_title(view);
3354 report("");
3355 break;
3356 }
3357 case REQ_REFRESH:
3358 report("Refreshing is not yet supported for the %s view", view->name);
3359 break;
3361 case REQ_MAXIMIZE:
3362 if (displayed_views() == 2)
3363 maximize_view(view);
3364 break;
3366 case REQ_OPTIONS:
3367 open_option_menu();
3368 break;
3370 case REQ_TOGGLE_LINENO:
3371 toggle_view_option(&opt_line_number, "line numbers");
3372 break;
3374 case REQ_TOGGLE_DATE:
3375 toggle_view_option(&opt_date, "date display");
3376 break;
3378 case REQ_TOGGLE_DATE_SHORT:
3379 toggle_view_option(&opt_date_short, "date shortening");
3380 break;
3382 case REQ_TOGGLE_AUTHOR:
3383 toggle_view_option(&opt_author, "author display");
3384 break;
3386 case REQ_TOGGLE_REV_GRAPH:
3387 toggle_view_option(&opt_rev_graph, "revision graph display");
3388 break;
3390 case REQ_TOGGLE_REFS:
3391 toggle_view_option(&opt_show_refs, "reference display");
3392 break;
3394 case REQ_TOGGLE_SORT_FIELD:
3395 case REQ_TOGGLE_SORT_ORDER:
3396 report("Sorting is not yet supported for the %s view", view->name);
3397 break;
3399 case REQ_SEARCH:
3400 case REQ_SEARCH_BACK:
3401 search_view(view, request);
3402 break;
3404 case REQ_FIND_NEXT:
3405 case REQ_FIND_PREV:
3406 find_next(view, request);
3407 break;
3409 case REQ_STOP_LOADING:
3410 for (i = 0; i < ARRAY_SIZE(views); i++) {
3411 view = &views[i];
3412 if (view->pipe)
3413 report("Stopped loading the %s view", view->name),
3414 end_update(view, TRUE);
3415 }
3416 break;
3418 case REQ_SHOW_VERSION:
3419 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3420 return TRUE;
3422 case REQ_SCREEN_REDRAW:
3423 redraw_display(TRUE);
3424 break;
3426 case REQ_EDIT:
3427 report("Nothing to edit");
3428 break;
3430 case REQ_ENTER:
3431 report("Nothing to enter");
3432 break;
3434 case REQ_VIEW_CLOSE:
3435 /* XXX: Mark closed views by letting view->parent point to the
3436 * view itself. Parents to closed view should never be
3437 * followed. */
3438 if (view->parent &&
3439 view->parent->parent != view->parent) {
3440 maximize_view(view->parent);
3441 view->parent = view;
3442 break;
3443 }
3444 /* Fall-through */
3445 case REQ_QUIT:
3446 return FALSE;
3448 default:
3449 report("Unknown key, press %s for help",
3450 get_key(view->keymap, REQ_VIEW_HELP));
3451 return TRUE;
3452 }
3454 return TRUE;
3455 }
3458 /*
3459 * View backend utilities
3460 */
3462 enum sort_field {
3463 ORDERBY_NAME,
3464 ORDERBY_DATE,
3465 ORDERBY_AUTHOR,
3466 };
3468 struct sort_state {
3469 const enum sort_field *fields;
3470 size_t size, current;
3471 bool reverse;
3472 };
3474 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3475 #define get_sort_field(state) ((state).fields[(state).current])
3476 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3478 static void
3479 sort_view(struct view *view, enum request request, struct sort_state *state,
3480 int (*compare)(const void *, const void *))
3481 {
3482 switch (request) {
3483 case REQ_TOGGLE_SORT_FIELD:
3484 state->current = (state->current + 1) % state->size;
3485 break;
3487 case REQ_TOGGLE_SORT_ORDER:
3488 state->reverse = !state->reverse;
3489 break;
3490 default:
3491 die("Not a sort request");
3492 }
3494 qsort(view->line, view->lines, sizeof(*view->line), compare);
3495 redraw_view(view);
3496 }
3498 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3500 /* Small author cache to reduce memory consumption. It uses binary
3501 * search to lookup or find place to position new entries. No entries
3502 * are ever freed. */
3503 static const char *
3504 get_author(const char *name)
3505 {
3506 static const char **authors;
3507 static size_t authors_size;
3508 int from = 0, to = authors_size - 1;
3510 while (from <= to) {
3511 size_t pos = (to + from) / 2;
3512 int cmp = strcmp(name, authors[pos]);
3514 if (!cmp)
3515 return authors[pos];
3517 if (cmp < 0)
3518 to = pos - 1;
3519 else
3520 from = pos + 1;
3521 }
3523 if (!realloc_authors(&authors, authors_size, 1))
3524 return NULL;
3525 name = strdup(name);
3526 if (!name)
3527 return NULL;
3529 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3530 authors[from] = name;
3531 authors_size++;
3533 return name;
3534 }
3536 static void
3537 parse_timezone(time_t *time, const char *zone)
3538 {
3539 long tz;
3541 tz = ('0' - zone[1]) * 60 * 60 * 10;
3542 tz += ('0' - zone[2]) * 60 * 60;
3543 tz += ('0' - zone[3]) * 60;
3544 tz += ('0' - zone[4]);
3546 if (zone[0] == '-')
3547 tz = -tz;
3549 *time -= tz;
3550 }
3552 /* Parse author lines where the name may be empty:
3553 * author <email@address.tld> 1138474660 +0100
3554 */
3555 static void
3556 parse_author_line(char *ident, const char **author, time_t *time)
3557 {
3558 char *nameend = strchr(ident, '<');
3559 char *emailend = strchr(ident, '>');
3561 if (nameend && emailend)
3562 *nameend = *emailend = 0;
3563 ident = chomp_string(ident);
3564 if (!*ident) {
3565 if (nameend)
3566 ident = chomp_string(nameend + 1);
3567 if (!*ident)
3568 ident = "Unknown";
3569 }
3571 *author = get_author(ident);
3573 /* Parse epoch and timezone */
3574 if (emailend && emailend[1] == ' ') {
3575 char *secs = emailend + 2;
3576 char *zone = strchr(secs, ' ');
3578 *time = (time_t) atol(secs);
3580 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3581 parse_timezone(time, zone + 1);
3582 }
3583 }
3585 static bool
3586 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3587 {
3588 char rev[SIZEOF_REV];
3589 const char *revlist_argv[] = {
3590 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3591 };
3592 struct menu_item *items;
3593 char text[SIZEOF_STR];
3594 bool ok = TRUE;
3595 int i;
3597 items = calloc(*parents + 1, sizeof(*items));
3598 if (!items)
3599 return FALSE;
3601 for (i = 0; i < *parents; i++) {
3602 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3603 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3604 !(items[i].text = strdup(text))) {
3605 ok = FALSE;
3606 break;
3607 }
3608 }
3610 if (ok) {
3611 *parents = 0;
3612 ok = prompt_menu("Select parent", items, parents);
3613 }
3614 for (i = 0; items[i].text; i++)
3615 free((char *) items[i].text);
3616 free(items);
3617 return ok;
3618 }
3620 static bool
3621 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3622 {
3623 char buf[SIZEOF_STR * 4];
3624 const char *revlist_argv[] = {
3625 "git", "log", "--no-color", "-1",
3626 "--pretty=format:%P", id, "--", path, NULL
3627 };
3628 int parents;
3630 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3631 (parents = strlen(buf) / 40) < 0) {
3632 report("Failed to get parent information");
3633 return FALSE;
3635 } else if (parents == 0) {
3636 if (path)
3637 report("Path '%s' does not exist in the parent", path);
3638 else
3639 report("The selected commit has no parents");
3640 return FALSE;
3641 }
3643 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3644 return FALSE;
3646 string_copy_rev(rev, &buf[41 * parents]);
3647 return TRUE;
3648 }
3650 /*
3651 * Pager backend
3652 */
3654 static bool
3655 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3656 {
3657 char text[SIZEOF_STR];
3659 if (opt_line_number && draw_lineno(view, lineno))
3660 return TRUE;
3662 string_expand(text, sizeof(text), line->data, opt_tab_size);
3663 draw_text(view, line->type, text, TRUE);
3664 return TRUE;
3665 }
3667 static bool
3668 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3669 {
3670 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3671 char ref[SIZEOF_STR];
3673 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3674 return TRUE;
3676 /* This is the only fatal call, since it can "corrupt" the buffer. */
3677 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3678 return FALSE;
3680 return TRUE;
3681 }
3683 static void
3684 add_pager_refs(struct view *view, struct line *line)
3685 {
3686 char buf[SIZEOF_STR];
3687 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3688 struct ref_list *list;
3689 size_t bufpos = 0, i;
3690 const char *sep = "Refs: ";
3691 bool is_tag = FALSE;
3693 assert(line->type == LINE_COMMIT);
3695 list = get_ref_list(commit_id);
3696 if (!list) {
3697 if (view == VIEW(REQ_VIEW_DIFF))
3698 goto try_add_describe_ref;
3699 return;
3700 }
3702 for (i = 0; i < list->size; i++) {
3703 struct ref *ref = list->refs[i];
3704 const char *fmt = ref->tag ? "%s[%s]" :
3705 ref->remote ? "%s<%s>" : "%s%s";
3707 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3708 return;
3709 sep = ", ";
3710 if (ref->tag)
3711 is_tag = TRUE;
3712 }
3714 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3715 try_add_describe_ref:
3716 /* Add <tag>-g<commit_id> "fake" reference. */
3717 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3718 return;
3719 }
3721 if (bufpos == 0)
3722 return;
3724 add_line_text(view, buf, LINE_PP_REFS);
3725 }
3727 static bool
3728 pager_read(struct view *view, char *data)
3729 {
3730 struct line *line;
3732 if (!data)
3733 return TRUE;
3735 line = add_line_text(view, data, get_line_type(data));
3736 if (!line)
3737 return FALSE;
3739 if (line->type == LINE_COMMIT &&
3740 (view == VIEW(REQ_VIEW_DIFF) ||
3741 view == VIEW(REQ_VIEW_LOG)))
3742 add_pager_refs(view, line);
3744 return TRUE;
3745 }
3747 static enum request
3748 pager_request(struct view *view, enum request request, struct line *line)
3749 {
3750 int split = 0;
3752 if (request != REQ_ENTER)
3753 return request;
3755 if (line->type == LINE_COMMIT &&
3756 (view == VIEW(REQ_VIEW_LOG) ||
3757 view == VIEW(REQ_VIEW_PAGER))) {
3758 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3759 split = 1;
3760 }
3762 /* Always scroll the view even if it was split. That way
3763 * you can use Enter to scroll through the log view and
3764 * split open each commit diff. */
3765 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3767 /* FIXME: A minor workaround. Scrolling the view will call report("")
3768 * but if we are scrolling a non-current view this won't properly
3769 * update the view title. */
3770 if (split)
3771 update_view_title(view);
3773 return REQ_NONE;
3774 }
3776 static bool
3777 pager_grep(struct view *view, struct line *line)
3778 {
3779 const char *text[] = { line->data, NULL };
3781 return grep_text(view, text);
3782 }
3784 static void
3785 pager_select(struct view *view, struct line *line)
3786 {
3787 if (line->type == LINE_COMMIT) {
3788 char *text = (char *)line->data + STRING_SIZE("commit ");
3790 if (view != VIEW(REQ_VIEW_PAGER))
3791 string_copy_rev(view->ref, text);
3792 string_copy_rev(ref_commit, text);
3793 }
3794 }
3796 static struct view_ops pager_ops = {
3797 "line",
3798 NULL,
3799 NULL,
3800 pager_read,
3801 pager_draw,
3802 pager_request,
3803 pager_grep,
3804 pager_select,
3805 };
3807 static const char *log_argv[SIZEOF_ARG] = {
3808 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3809 };
3811 static enum request
3812 log_request(struct view *view, enum request request, struct line *line)
3813 {
3814 switch (request) {
3815 case REQ_REFRESH:
3816 load_refs();
3817 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3818 return REQ_NONE;
3819 default:
3820 return pager_request(view, request, line);
3821 }
3822 }
3824 static struct view_ops log_ops = {
3825 "line",
3826 log_argv,
3827 NULL,
3828 pager_read,
3829 pager_draw,
3830 log_request,
3831 pager_grep,
3832 pager_select,
3833 };
3835 static const char *diff_argv[SIZEOF_ARG] = {
3836 "git", "show", "--pretty=fuller", "--no-color", "--root",
3837 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3838 };
3840 static struct view_ops diff_ops = {
3841 "line",
3842 diff_argv,
3843 NULL,
3844 pager_read,
3845 pager_draw,
3846 pager_request,
3847 pager_grep,
3848 pager_select,
3849 };
3851 /*
3852 * Help backend
3853 */
3855 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3857 static char *
3858 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3859 {
3860 int bufpos;
3862 for (bufpos = 0; bufpos <= namelen; bufpos++) {
3863 buf[bufpos] = tolower(name[bufpos]);
3864 if (buf[bufpos] == '_')
3865 buf[bufpos] = '-';
3866 }
3868 buf[bufpos] = 0;
3869 return buf;
3870 }
3872 #define help_keymap_name(buf, keymap) \
3873 help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3875 static bool
3876 help_open_keymap_title(struct view *view, enum keymap keymap)
3877 {
3878 char buf[SIZEOF_STR];
3879 struct line *line;
3881 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3882 help_keymap_hidden[keymap] ? '+' : '-',
3883 help_keymap_name(buf, keymap));
3884 if (line)
3885 line->other = keymap;
3887 return help_keymap_hidden[keymap];
3888 }
3890 static void
3891 help_open_keymap(struct view *view, enum keymap keymap)
3892 {
3893 const char *group = NULL;
3894 char buf[SIZEOF_STR];
3895 size_t bufpos;
3896 bool add_title = TRUE;
3897 int i;
3899 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3900 const char *key = NULL;
3902 if (req_info[i].request == REQ_NONE)
3903 continue;
3905 if (!req_info[i].request) {
3906 group = req_info[i].help;
3907 continue;
3908 }
3910 key = get_keys(keymap, req_info[i].request, TRUE);
3911 if (!key || !*key)
3912 continue;
3914 if (add_title && help_open_keymap_title(view, keymap))
3915 return;
3916 add_title = false;
3918 if (group) {
3919 add_line_text(view, group, LINE_HELP_GROUP);
3920 group = NULL;
3921 }
3923 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
3924 help_name(buf, req_info[i].name, req_info[i].namelen),
3925 req_info[i].help);
3926 }
3928 group = "External commands:";
3930 for (i = 0; i < run_requests; i++) {
3931 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3932 const char *key;
3933 int argc;
3935 if (!req || req->keymap != keymap)
3936 continue;
3938 key = get_key_name(req->key);
3939 if (!*key)
3940 key = "(no key defined)";
3942 if (add_title && help_open_keymap_title(view, keymap))
3943 return;
3944 if (group) {
3945 add_line_text(view, group, LINE_HELP_GROUP);
3946 group = NULL;
3947 }
3949 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3950 if (!string_format_from(buf, &bufpos, "%s%s",
3951 argc ? " " : "", req->argv[argc]))
3952 return;
3954 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
3955 }
3956 }
3958 static bool
3959 help_open(struct view *view)
3960 {
3961 enum keymap keymap;
3963 reset_view(view);
3964 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3965 add_line_text(view, "", LINE_DEFAULT);
3967 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
3968 help_open_keymap(view, keymap);
3970 return TRUE;
3971 }
3973 static enum request
3974 help_request(struct view *view, enum request request, struct line *line)
3975 {
3976 switch (request) {
3977 case REQ_ENTER:
3978 if (line->type == LINE_HELP_KEYMAP) {
3979 help_keymap_hidden[line->other] =
3980 !help_keymap_hidden[line->other];
3981 view->p_restore = TRUE;
3982 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
3983 }
3985 return REQ_NONE;
3986 default:
3987 return pager_request(view, request, line);
3988 }
3989 }
3991 static struct view_ops help_ops = {
3992 "line",
3993 NULL,
3994 help_open,
3995 NULL,
3996 pager_draw,
3997 help_request,
3998 pager_grep,
3999 pager_select,
4000 };
4003 /*
4004 * Tree backend
4005 */
4007 struct tree_stack_entry {
4008 struct tree_stack_entry *prev; /* Entry below this in the stack */
4009 unsigned long lineno; /* Line number to restore */
4010 char *name; /* Position of name in opt_path */
4011 };
4013 /* The top of the path stack. */
4014 static struct tree_stack_entry *tree_stack = NULL;
4015 unsigned long tree_lineno = 0;
4017 static void
4018 pop_tree_stack_entry(void)
4019 {
4020 struct tree_stack_entry *entry = tree_stack;
4022 tree_lineno = entry->lineno;
4023 entry->name[0] = 0;
4024 tree_stack = entry->prev;
4025 free(entry);
4026 }
4028 static void
4029 push_tree_stack_entry(const char *name, unsigned long lineno)
4030 {
4031 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4032 size_t pathlen = strlen(opt_path);
4034 if (!entry)
4035 return;
4037 entry->prev = tree_stack;
4038 entry->name = opt_path + pathlen;
4039 tree_stack = entry;
4041 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4042 pop_tree_stack_entry();
4043 return;
4044 }
4046 /* Move the current line to the first tree entry. */
4047 tree_lineno = 1;
4048 entry->lineno = lineno;
4049 }
4051 /* Parse output from git-ls-tree(1):
4052 *
4053 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4054 */
4056 #define SIZEOF_TREE_ATTR \
4057 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4059 #define SIZEOF_TREE_MODE \
4060 STRING_SIZE("100644 ")
4062 #define TREE_ID_OFFSET \
4063 STRING_SIZE("100644 blob ")
4065 struct tree_entry {
4066 char id[SIZEOF_REV];
4067 mode_t mode;
4068 time_t time; /* Date from the author ident. */
4069 const char *author; /* Author of the commit. */
4070 char name[1];
4071 };
4073 static const char *
4074 tree_path(const struct line *line)
4075 {
4076 return ((struct tree_entry *) line->data)->name;
4077 }
4079 static int
4080 tree_compare_entry(const struct line *line1, const struct line *line2)
4081 {
4082 if (line1->type != line2->type)
4083 return line1->type == LINE_TREE_DIR ? -1 : 1;
4084 return strcmp(tree_path(line1), tree_path(line2));
4085 }
4087 static const enum sort_field tree_sort_fields[] = {
4088 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4089 };
4090 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4092 static int
4093 tree_compare(const void *l1, const void *l2)
4094 {
4095 const struct line *line1 = (const struct line *) l1;
4096 const struct line *line2 = (const struct line *) l2;
4097 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4098 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4100 if (line1->type == LINE_TREE_HEAD)
4101 return -1;
4102 if (line2->type == LINE_TREE_HEAD)
4103 return 1;
4105 switch (get_sort_field(tree_sort_state)) {
4106 case ORDERBY_DATE:
4107 return sort_order(tree_sort_state, entry1->time - entry2->time);
4109 case ORDERBY_AUTHOR:
4110 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4112 case ORDERBY_NAME:
4113 default:
4114 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4115 }
4116 }
4119 static struct line *
4120 tree_entry(struct view *view, enum line_type type, const char *path,
4121 const char *mode, const char *id)
4122 {
4123 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4124 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4126 if (!entry || !line) {
4127 free(entry);
4128 return NULL;
4129 }
4131 strncpy(entry->name, path, strlen(path));
4132 if (mode)
4133 entry->mode = strtoul(mode, NULL, 8);
4134 if (id)
4135 string_copy_rev(entry->id, id);
4137 return line;
4138 }
4140 static bool
4141 tree_read_date(struct view *view, char *text, bool *read_date)
4142 {
4143 static const char *author_name;
4144 static time_t author_time;
4146 if (!text && *read_date) {
4147 *read_date = FALSE;
4148 return TRUE;
4150 } else if (!text) {
4151 char *path = *opt_path ? opt_path : ".";
4152 /* Find next entry to process */
4153 const char *log_file[] = {
4154 "git", "log", "--no-color", "--pretty=raw",
4155 "--cc", "--raw", view->id, "--", path, NULL
4156 };
4157 struct io io = {};
4159 if (!view->lines) {
4160 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4161 report("Tree is empty");
4162 return TRUE;
4163 }
4165 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
4166 report("Failed to load tree data");
4167 return TRUE;
4168 }
4170 done_io(view->pipe);
4171 view->io = io;
4172 *read_date = TRUE;
4173 return FALSE;
4175 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4176 parse_author_line(text + STRING_SIZE("author "),
4177 &author_name, &author_time);
4179 } else if (*text == ':') {
4180 char *pos;
4181 size_t annotated = 1;
4182 size_t i;
4184 pos = strchr(text, '\t');
4185 if (!pos)
4186 return TRUE;
4187 text = pos + 1;
4188 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4189 text += strlen(opt_prefix);
4190 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4191 text += strlen(opt_path);
4192 pos = strchr(text, '/');
4193 if (pos)
4194 *pos = 0;
4196 for (i = 1; i < view->lines; i++) {
4197 struct line *line = &view->line[i];
4198 struct tree_entry *entry = line->data;
4200 annotated += !!entry->author;
4201 if (entry->author || strcmp(entry->name, text))
4202 continue;
4204 entry->author = author_name;
4205 entry->time = author_time;
4206 line->dirty = 1;
4207 break;
4208 }
4210 if (annotated == view->lines)
4211 kill_io(view->pipe);
4212 }
4213 return TRUE;
4214 }
4216 static bool
4217 tree_read(struct view *view, char *text)
4218 {
4219 static bool read_date = FALSE;
4220 struct tree_entry *data;
4221 struct line *entry, *line;
4222 enum line_type type;
4223 size_t textlen = text ? strlen(text) : 0;
4224 char *path = text + SIZEOF_TREE_ATTR;
4226 if (read_date || !text)
4227 return tree_read_date(view, text, &read_date);
4229 if (textlen <= SIZEOF_TREE_ATTR)
4230 return FALSE;
4231 if (view->lines == 0 &&
4232 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4233 return FALSE;
4235 /* Strip the path part ... */
4236 if (*opt_path) {
4237 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4238 size_t striplen = strlen(opt_path);
4240 if (pathlen > striplen)
4241 memmove(path, path + striplen,
4242 pathlen - striplen + 1);
4244 /* Insert "link" to parent directory. */
4245 if (view->lines == 1 &&
4246 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4247 return FALSE;
4248 }
4250 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4251 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4252 if (!entry)
4253 return FALSE;
4254 data = entry->data;
4256 /* Skip "Directory ..." and ".." line. */
4257 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4258 if (tree_compare_entry(line, entry) <= 0)
4259 continue;
4261 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4263 line->data = data;
4264 line->type = type;
4265 for (; line <= entry; line++)
4266 line->dirty = line->cleareol = 1;
4267 return TRUE;
4268 }
4270 if (tree_lineno > view->lineno) {
4271 view->lineno = tree_lineno;
4272 tree_lineno = 0;
4273 }
4275 return TRUE;
4276 }
4278 static bool
4279 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4280 {
4281 struct tree_entry *entry = line->data;
4283 if (line->type == LINE_TREE_HEAD) {
4284 if (draw_text(view, line->type, "Directory path /", TRUE))
4285 return TRUE;
4286 } else {
4287 if (draw_mode(view, entry->mode))
4288 return TRUE;
4290 if (opt_author && draw_author(view, entry->author))
4291 return TRUE;
4293 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4294 return TRUE;
4295 }
4296 if (draw_text(view, line->type, entry->name, TRUE))
4297 return TRUE;
4298 return TRUE;
4299 }
4301 static void
4302 open_blob_editor()
4303 {
4304 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4305 int fd = mkstemp(file);
4307 if (fd == -1)
4308 report("Failed to create temporary file");
4309 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4310 report("Failed to save blob data to file");
4311 else
4312 open_editor(FALSE, file);
4313 if (fd != -1)
4314 unlink(file);
4315 }
4317 static enum request
4318 tree_request(struct view *view, enum request request, struct line *line)
4319 {
4320 enum open_flags flags;
4322 switch (request) {
4323 case REQ_VIEW_BLAME:
4324 if (line->type != LINE_TREE_FILE) {
4325 report("Blame only supported for files");
4326 return REQ_NONE;
4327 }
4329 string_copy(opt_ref, view->vid);
4330 return request;
4332 case REQ_EDIT:
4333 if (line->type != LINE_TREE_FILE) {
4334 report("Edit only supported for files");
4335 } else if (!is_head_commit(view->vid)) {
4336 open_blob_editor();
4337 } else {
4338 open_editor(TRUE, opt_file);
4339 }
4340 return REQ_NONE;
4342 case REQ_TOGGLE_SORT_FIELD:
4343 case REQ_TOGGLE_SORT_ORDER:
4344 sort_view(view, request, &tree_sort_state, tree_compare);
4345 return REQ_NONE;
4347 case REQ_PARENT:
4348 if (!*opt_path) {
4349 /* quit view if at top of tree */
4350 return REQ_VIEW_CLOSE;
4351 }
4352 /* fake 'cd ..' */
4353 line = &view->line[1];
4354 break;
4356 case REQ_ENTER:
4357 break;
4359 default:
4360 return request;
4361 }
4363 /* Cleanup the stack if the tree view is at a different tree. */
4364 while (!*opt_path && tree_stack)
4365 pop_tree_stack_entry();
4367 switch (line->type) {
4368 case LINE_TREE_DIR:
4369 /* Depending on whether it is a subdirectory or parent link
4370 * mangle the path buffer. */
4371 if (line == &view->line[1] && *opt_path) {
4372 pop_tree_stack_entry();
4374 } else {
4375 const char *basename = tree_path(line);
4377 push_tree_stack_entry(basename, view->lineno);
4378 }
4380 /* Trees and subtrees share the same ID, so they are not not
4381 * unique like blobs. */
4382 flags = OPEN_RELOAD;
4383 request = REQ_VIEW_TREE;
4384 break;
4386 case LINE_TREE_FILE:
4387 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4388 request = REQ_VIEW_BLOB;
4389 break;
4391 default:
4392 return REQ_NONE;
4393 }
4395 open_view(view, request, flags);
4396 if (request == REQ_VIEW_TREE)
4397 view->lineno = tree_lineno;
4399 return REQ_NONE;
4400 }
4402 static bool
4403 tree_grep(struct view *view, struct line *line)
4404 {
4405 struct tree_entry *entry = line->data;
4406 const char *text[] = {
4407 entry->name,
4408 opt_author ? entry->author : "",
4409 opt_date ? mkdate(&entry->time) : "",
4410 NULL
4411 };
4413 return grep_text(view, text);
4414 }
4416 static void
4417 tree_select(struct view *view, struct line *line)
4418 {
4419 struct tree_entry *entry = line->data;
4421 if (line->type == LINE_TREE_FILE) {
4422 string_copy_rev(ref_blob, entry->id);
4423 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4425 } else if (line->type != LINE_TREE_DIR) {
4426 return;
4427 }
4429 string_copy_rev(view->ref, entry->id);
4430 }
4432 static const char *tree_argv[SIZEOF_ARG] = {
4433 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4434 };
4436 static struct view_ops tree_ops = {
4437 "file",
4438 tree_argv,
4439 NULL,
4440 tree_read,
4441 tree_draw,
4442 tree_request,
4443 tree_grep,
4444 tree_select,
4445 };
4447 static bool
4448 blob_read(struct view *view, char *line)
4449 {
4450 if (!line)
4451 return TRUE;
4452 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4453 }
4455 static enum request
4456 blob_request(struct view *view, enum request request, struct line *line)
4457 {
4458 switch (request) {
4459 case REQ_EDIT:
4460 open_blob_editor();
4461 return REQ_NONE;
4462 default:
4463 return pager_request(view, request, line);
4464 }
4465 }
4467 static const char *blob_argv[SIZEOF_ARG] = {
4468 "git", "cat-file", "blob", "%(blob)", NULL
4469 };
4471 static struct view_ops blob_ops = {
4472 "line",
4473 blob_argv,
4474 NULL,
4475 blob_read,
4476 pager_draw,
4477 blob_request,
4478 pager_grep,
4479 pager_select,
4480 };
4482 /*
4483 * Blame backend
4484 *
4485 * Loading the blame view is a two phase job:
4486 *
4487 * 1. File content is read either using opt_file from the
4488 * filesystem or using git-cat-file.
4489 * 2. Then blame information is incrementally added by
4490 * reading output from git-blame.
4491 */
4493 static const char *blame_head_argv[] = {
4494 "git", "blame", "--incremental", "--", "%(file)", NULL
4495 };
4497 static const char *blame_ref_argv[] = {
4498 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4499 };
4501 static const char *blame_cat_file_argv[] = {
4502 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4503 };
4505 struct blame_commit {
4506 char id[SIZEOF_REV]; /* SHA1 ID. */
4507 char title[128]; /* First line of the commit message. */
4508 const char *author; /* Author of the commit. */
4509 time_t time; /* Date from the author ident. */
4510 char filename[128]; /* Name of file. */
4511 bool has_previous; /* Was a "previous" line detected. */
4512 };
4514 struct blame {
4515 struct blame_commit *commit;
4516 unsigned long lineno;
4517 char text[1];
4518 };
4520 static bool
4521 blame_open(struct view *view)
4522 {
4523 if (*opt_ref || !io_open(&view->io, opt_file)) {
4524 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4525 return FALSE;
4526 }
4528 setup_update(view, opt_file);
4529 string_format(view->ref, "%s ...", opt_file);
4531 return TRUE;
4532 }
4534 static struct blame_commit *
4535 get_blame_commit(struct view *view, const char *id)
4536 {
4537 size_t i;
4539 for (i = 0; i < view->lines; i++) {
4540 struct blame *blame = view->line[i].data;
4542 if (!blame->commit)
4543 continue;
4545 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4546 return blame->commit;
4547 }
4549 {
4550 struct blame_commit *commit = calloc(1, sizeof(*commit));
4552 if (commit)
4553 string_ncopy(commit->id, id, SIZEOF_REV);
4554 return commit;
4555 }
4556 }
4558 static bool
4559 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4560 {
4561 const char *pos = *posref;
4563 *posref = NULL;
4564 pos = strchr(pos + 1, ' ');
4565 if (!pos || !isdigit(pos[1]))
4566 return FALSE;
4567 *number = atoi(pos + 1);
4568 if (*number < min || *number > max)
4569 return FALSE;
4571 *posref = pos;
4572 return TRUE;
4573 }
4575 static struct blame_commit *
4576 parse_blame_commit(struct view *view, const char *text, int *blamed)
4577 {
4578 struct blame_commit *commit;
4579 struct blame *blame;
4580 const char *pos = text + SIZEOF_REV - 2;
4581 size_t orig_lineno = 0;
4582 size_t lineno;
4583 size_t group;
4585 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4586 return NULL;
4588 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4589 !parse_number(&pos, &lineno, 1, view->lines) ||
4590 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4591 return NULL;
4593 commit = get_blame_commit(view, text);
4594 if (!commit)
4595 return NULL;
4597 *blamed += group;
4598 while (group--) {
4599 struct line *line = &view->line[lineno + group - 1];
4601 blame = line->data;
4602 blame->commit = commit;
4603 blame->lineno = orig_lineno + group - 1;
4604 line->dirty = 1;
4605 }
4607 return commit;
4608 }
4610 static bool
4611 blame_read_file(struct view *view, const char *line, bool *read_file)
4612 {
4613 if (!line) {
4614 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4615 struct io io = {};
4617 if (view->lines == 0 && !view->parent)
4618 die("No blame exist for %s", view->vid);
4620 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4621 report("Failed to load blame data");
4622 return TRUE;
4623 }
4625 done_io(view->pipe);
4626 view->io = io;
4627 *read_file = FALSE;
4628 return FALSE;
4630 } else {
4631 size_t linelen = strlen(line);
4632 struct blame *blame = malloc(sizeof(*blame) + linelen);
4634 if (!blame)
4635 return FALSE;
4637 blame->commit = NULL;
4638 strncpy(blame->text, line, linelen);
4639 blame->text[linelen] = 0;
4640 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4641 }
4642 }
4644 static bool
4645 match_blame_header(const char *name, char **line)
4646 {
4647 size_t namelen = strlen(name);
4648 bool matched = !strncmp(name, *line, namelen);
4650 if (matched)
4651 *line += namelen;
4653 return matched;
4654 }
4656 static bool
4657 blame_read(struct view *view, char *line)
4658 {
4659 static struct blame_commit *commit = NULL;
4660 static int blamed = 0;
4661 static bool read_file = TRUE;
4663 if (read_file)
4664 return blame_read_file(view, line, &read_file);
4666 if (!line) {
4667 /* Reset all! */
4668 commit = NULL;
4669 blamed = 0;
4670 read_file = TRUE;
4671 string_format(view->ref, "%s", view->vid);
4672 if (view_is_displayed(view)) {
4673 update_view_title(view);
4674 redraw_view_from(view, 0);
4675 }
4676 return TRUE;
4677 }
4679 if (!commit) {
4680 commit = parse_blame_commit(view, line, &blamed);
4681 string_format(view->ref, "%s %2d%%", view->vid,
4682 view->lines ? blamed * 100 / view->lines : 0);
4684 } else if (match_blame_header("author ", &line)) {
4685 commit->author = get_author(line);
4687 } else if (match_blame_header("author-time ", &line)) {
4688 commit->time = (time_t) atol(line);
4690 } else if (match_blame_header("author-tz ", &line)) {
4691 parse_timezone(&commit->time, line);
4693 } else if (match_blame_header("summary ", &line)) {
4694 string_ncopy(commit->title, line, strlen(line));
4696 } else if (match_blame_header("previous ", &line)) {
4697 commit->has_previous = TRUE;
4699 } else if (match_blame_header("filename ", &line)) {
4700 string_ncopy(commit->filename, line, strlen(line));
4701 commit = NULL;
4702 }
4704 return TRUE;
4705 }
4707 static bool
4708 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4709 {
4710 struct blame *blame = line->data;
4711 time_t *time = NULL;
4712 const char *id = NULL, *author = NULL;
4713 char text[SIZEOF_STR];
4715 if (blame->commit && *blame->commit->filename) {
4716 id = blame->commit->id;
4717 author = blame->commit->author;
4718 time = &blame->commit->time;
4719 }
4721 if (opt_date && draw_date(view, time))
4722 return TRUE;
4724 if (opt_author && draw_author(view, author))
4725 return TRUE;
4727 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4728 return TRUE;
4730 if (draw_lineno(view, lineno))
4731 return TRUE;
4733 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4734 draw_text(view, LINE_DEFAULT, text, TRUE);
4735 return TRUE;
4736 }
4738 static bool
4739 check_blame_commit(struct blame *blame, bool check_null_id)
4740 {
4741 if (!blame->commit)
4742 report("Commit data not loaded yet");
4743 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4744 report("No commit exist for the selected line");
4745 else
4746 return TRUE;
4747 return FALSE;
4748 }
4750 static void
4751 setup_blame_parent_line(struct view *view, struct blame *blame)
4752 {
4753 const char *diff_tree_argv[] = {
4754 "git", "diff-tree", "-U0", blame->commit->id,
4755 "--", blame->commit->filename, NULL
4756 };
4757 struct io io = {};
4758 int parent_lineno = -1;
4759 int blamed_lineno = -1;
4760 char *line;
4762 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4763 return;
4765 while ((line = io_get(&io, '\n', TRUE))) {
4766 if (*line == '@') {
4767 char *pos = strchr(line, '+');
4769 parent_lineno = atoi(line + 4);
4770 if (pos)
4771 blamed_lineno = atoi(pos + 1);
4773 } else if (*line == '+' && parent_lineno != -1) {
4774 if (blame->lineno == blamed_lineno - 1 &&
4775 !strcmp(blame->text, line + 1)) {
4776 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4777 break;
4778 }
4779 blamed_lineno++;
4780 }
4781 }
4783 done_io(&io);
4784 }
4786 static enum request
4787 blame_request(struct view *view, enum request request, struct line *line)
4788 {
4789 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4790 struct blame *blame = line->data;
4792 switch (request) {
4793 case REQ_VIEW_BLAME:
4794 if (check_blame_commit(blame, TRUE)) {
4795 string_copy(opt_ref, blame->commit->id);
4796 string_copy(opt_file, blame->commit->filename);
4797 if (blame->lineno)
4798 view->lineno = blame->lineno;
4799 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4800 }
4801 break;
4803 case REQ_PARENT:
4804 if (check_blame_commit(blame, TRUE) &&
4805 select_commit_parent(blame->commit->id, opt_ref,
4806 blame->commit->filename)) {
4807 string_copy(opt_file, blame->commit->filename);
4808 setup_blame_parent_line(view, blame);
4809 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4810 }
4811 break;
4813 case REQ_ENTER:
4814 if (!check_blame_commit(blame, FALSE))
4815 break;
4817 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4818 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4819 break;
4821 if (!strcmp(blame->commit->id, NULL_ID)) {
4822 struct view *diff = VIEW(REQ_VIEW_DIFF);
4823 const char *diff_index_argv[] = {
4824 "git", "diff-index", "--root", "--patch-with-stat",
4825 "-C", "-M", "HEAD", "--", view->vid, NULL
4826 };
4828 if (!blame->commit->has_previous) {
4829 diff_index_argv[1] = "diff";
4830 diff_index_argv[2] = "--no-color";
4831 diff_index_argv[6] = "--";
4832 diff_index_argv[7] = "/dev/null";
4833 }
4835 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4836 report("Failed to allocate diff command");
4837 break;
4838 }
4839 flags |= OPEN_PREPARED;
4840 }
4842 open_view(view, REQ_VIEW_DIFF, flags);
4843 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4844 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4845 break;
4847 default:
4848 return request;
4849 }
4851 return REQ_NONE;
4852 }
4854 static bool
4855 blame_grep(struct view *view, struct line *line)
4856 {
4857 struct blame *blame = line->data;
4858 struct blame_commit *commit = blame->commit;
4859 const char *text[] = {
4860 blame->text,
4861 commit ? commit->title : "",
4862 commit ? commit->id : "",
4863 commit && opt_author ? commit->author : "",
4864 commit && opt_date ? mkdate(&commit->time) : "",
4865 NULL
4866 };
4868 return grep_text(view, text);
4869 }
4871 static void
4872 blame_select(struct view *view, struct line *line)
4873 {
4874 struct blame *blame = line->data;
4875 struct blame_commit *commit = blame->commit;
4877 if (!commit)
4878 return;
4880 if (!strcmp(commit->id, NULL_ID))
4881 string_ncopy(ref_commit, "HEAD", 4);
4882 else
4883 string_copy_rev(ref_commit, commit->id);
4884 }
4886 static struct view_ops blame_ops = {
4887 "line",
4888 NULL,
4889 blame_open,
4890 blame_read,
4891 blame_draw,
4892 blame_request,
4893 blame_grep,
4894 blame_select,
4895 };
4897 /*
4898 * Branch backend
4899 */
4901 struct branch {
4902 const char *author; /* Author of the last commit. */
4903 time_t time; /* Date of the last activity. */
4904 struct ref *ref; /* Name and commit ID information. */
4905 };
4907 static const enum sort_field branch_sort_fields[] = {
4908 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4909 };
4910 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4912 static int
4913 branch_compare(const void *l1, const void *l2)
4914 {
4915 const struct branch *branch1 = ((const struct line *) l1)->data;
4916 const struct branch *branch2 = ((const struct line *) l2)->data;
4918 switch (get_sort_field(branch_sort_state)) {
4919 case ORDERBY_DATE:
4920 return sort_order(branch_sort_state, branch1->time - branch2->time);
4922 case ORDERBY_AUTHOR:
4923 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4925 case ORDERBY_NAME:
4926 default:
4927 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4928 }
4929 }
4931 static bool
4932 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4933 {
4934 struct branch *branch = line->data;
4935 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4937 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4938 return TRUE;
4940 if (opt_author && draw_author(view, branch->author))
4941 return TRUE;
4943 draw_text(view, type, branch->ref->name, TRUE);
4944 return TRUE;
4945 }
4947 static enum request
4948 branch_request(struct view *view, enum request request, struct line *line)
4949 {
4950 switch (request) {
4951 case REQ_REFRESH:
4952 load_refs();
4953 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4954 return REQ_NONE;
4956 case REQ_TOGGLE_SORT_FIELD:
4957 case REQ_TOGGLE_SORT_ORDER:
4958 sort_view(view, request, &branch_sort_state, branch_compare);
4959 return REQ_NONE;
4961 case REQ_ENTER:
4962 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4963 return REQ_NONE;
4965 default:
4966 return request;
4967 }
4968 }
4970 static bool
4971 branch_read(struct view *view, char *line)
4972 {
4973 static char id[SIZEOF_REV];
4974 struct branch *reference;
4975 size_t i;
4977 if (!line)
4978 return TRUE;
4980 switch (get_line_type(line)) {
4981 case LINE_COMMIT:
4982 string_copy_rev(id, line + STRING_SIZE("commit "));
4983 return TRUE;
4985 case LINE_AUTHOR:
4986 for (i = 0, reference = NULL; i < view->lines; i++) {
4987 struct branch *branch = view->line[i].data;
4989 if (strcmp(branch->ref->id, id))
4990 continue;
4992 view->line[i].dirty = TRUE;
4993 if (reference) {
4994 branch->author = reference->author;
4995 branch->time = reference->time;
4996 continue;
4997 }
4999 parse_author_line(line + STRING_SIZE("author "),
5000 &branch->author, &branch->time);
5001 reference = branch;
5002 }
5003 return TRUE;
5005 default:
5006 return TRUE;
5007 }
5009 }
5011 static bool
5012 branch_open_visitor(void *data, struct ref *ref)
5013 {
5014 struct view *view = data;
5015 struct branch *branch;
5017 if (ref->tag || ref->ltag || ref->remote)
5018 return TRUE;
5020 branch = calloc(1, sizeof(*branch));
5021 if (!branch)
5022 return FALSE;
5024 branch->ref = ref;
5025 return !!add_line_data(view, branch, LINE_DEFAULT);
5026 }
5028 static bool
5029 branch_open(struct view *view)
5030 {
5031 const char *branch_log[] = {
5032 "git", "log", "--no-color", "--pretty=raw",
5033 "--simplify-by-decoration", "--all", NULL
5034 };
5036 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
5037 report("Failed to load branch data");
5038 return TRUE;
5039 }
5041 setup_update(view, view->id);
5042 foreach_ref(branch_open_visitor, view);
5043 view->p_restore = TRUE;
5045 return TRUE;
5046 }
5048 static bool
5049 branch_grep(struct view *view, struct line *line)
5050 {
5051 struct branch *branch = line->data;
5052 const char *text[] = {
5053 branch->ref->name,
5054 branch->author,
5055 NULL
5056 };
5058 return grep_text(view, text);
5059 }
5061 static void
5062 branch_select(struct view *view, struct line *line)
5063 {
5064 struct branch *branch = line->data;
5066 string_copy_rev(view->ref, branch->ref->id);
5067 string_copy_rev(ref_commit, branch->ref->id);
5068 string_copy_rev(ref_head, branch->ref->id);
5069 }
5071 static struct view_ops branch_ops = {
5072 "branch",
5073 NULL,
5074 branch_open,
5075 branch_read,
5076 branch_draw,
5077 branch_request,
5078 branch_grep,
5079 branch_select,
5080 };
5082 /*
5083 * Status backend
5084 */
5086 struct status {
5087 char status;
5088 struct {
5089 mode_t mode;
5090 char rev[SIZEOF_REV];
5091 char name[SIZEOF_STR];
5092 } old;
5093 struct {
5094 mode_t mode;
5095 char rev[SIZEOF_REV];
5096 char name[SIZEOF_STR];
5097 } new;
5098 };
5100 static char status_onbranch[SIZEOF_STR];
5101 static struct status stage_status;
5102 static enum line_type stage_line_type;
5103 static size_t stage_chunks;
5104 static int *stage_chunk;
5106 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5108 /* This should work even for the "On branch" line. */
5109 static inline bool
5110 status_has_none(struct view *view, struct line *line)
5111 {
5112 return line < view->line + view->lines && !line[1].data;
5113 }
5115 /* Get fields from the diff line:
5116 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5117 */
5118 static inline bool
5119 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5120 {
5121 const char *old_mode = buf + 1;
5122 const char *new_mode = buf + 8;
5123 const char *old_rev = buf + 15;
5124 const char *new_rev = buf + 56;
5125 const char *status = buf + 97;
5127 if (bufsize < 98 ||
5128 old_mode[-1] != ':' ||
5129 new_mode[-1] != ' ' ||
5130 old_rev[-1] != ' ' ||
5131 new_rev[-1] != ' ' ||
5132 status[-1] != ' ')
5133 return FALSE;
5135 file->status = *status;
5137 string_copy_rev(file->old.rev, old_rev);
5138 string_copy_rev(file->new.rev, new_rev);
5140 file->old.mode = strtoul(old_mode, NULL, 8);
5141 file->new.mode = strtoul(new_mode, NULL, 8);
5143 file->old.name[0] = file->new.name[0] = 0;
5145 return TRUE;
5146 }
5148 static bool
5149 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5150 {
5151 struct status *unmerged = NULL;
5152 char *buf;
5153 struct io io = {};
5155 if (!run_io(&io, argv, NULL, IO_RD))
5156 return FALSE;
5158 add_line_data(view, NULL, type);
5160 while ((buf = io_get(&io, 0, TRUE))) {
5161 struct status *file = unmerged;
5163 if (!file) {
5164 file = calloc(1, sizeof(*file));
5165 if (!file || !add_line_data(view, file, type))
5166 goto error_out;
5167 }
5169 /* Parse diff info part. */
5170 if (status) {
5171 file->status = status;
5172 if (status == 'A')
5173 string_copy(file->old.rev, NULL_ID);
5175 } else if (!file->status || file == unmerged) {
5176 if (!status_get_diff(file, buf, strlen(buf)))
5177 goto error_out;
5179 buf = io_get(&io, 0, TRUE);
5180 if (!buf)
5181 break;
5183 /* Collapse all modified entries that follow an
5184 * associated unmerged entry. */
5185 if (unmerged == file) {
5186 unmerged->status = 'U';
5187 unmerged = NULL;
5188 } else if (file->status == 'U') {
5189 unmerged = file;
5190 }
5191 }
5193 /* Grab the old name for rename/copy. */
5194 if (!*file->old.name &&
5195 (file->status == 'R' || file->status == 'C')) {
5196 string_ncopy(file->old.name, buf, strlen(buf));
5198 buf = io_get(&io, 0, TRUE);
5199 if (!buf)
5200 break;
5201 }
5203 /* git-ls-files just delivers a NUL separated list of
5204 * file names similar to the second half of the
5205 * git-diff-* output. */
5206 string_ncopy(file->new.name, buf, strlen(buf));
5207 if (!*file->old.name)
5208 string_copy(file->old.name, file->new.name);
5209 file = NULL;
5210 }
5212 if (io_error(&io)) {
5213 error_out:
5214 done_io(&io);
5215 return FALSE;
5216 }
5218 if (!view->line[view->lines - 1].data)
5219 add_line_data(view, NULL, LINE_STAT_NONE);
5221 done_io(&io);
5222 return TRUE;
5223 }
5225 /* Don't show unmerged entries in the staged section. */
5226 static const char *status_diff_index_argv[] = {
5227 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5228 "--cached", "-M", "HEAD", NULL
5229 };
5231 static const char *status_diff_files_argv[] = {
5232 "git", "diff-files", "-z", NULL
5233 };
5235 static const char *status_list_other_argv[] = {
5236 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5237 };
5239 static const char *status_list_no_head_argv[] = {
5240 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5241 };
5243 static const char *update_index_argv[] = {
5244 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5245 };
5247 /* Restore the previous line number to stay in the context or select a
5248 * line with something that can be updated. */
5249 static void
5250 status_restore(struct view *view)
5251 {
5252 if (view->p_lineno >= view->lines)
5253 view->p_lineno = view->lines - 1;
5254 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5255 view->p_lineno++;
5256 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5257 view->p_lineno--;
5259 /* If the above fails, always skip the "On branch" line. */
5260 if (view->p_lineno < view->lines)
5261 view->lineno = view->p_lineno;
5262 else
5263 view->lineno = 1;
5265 if (view->lineno < view->offset)
5266 view->offset = view->lineno;
5267 else if (view->offset + view->height <= view->lineno)
5268 view->offset = view->lineno - view->height + 1;
5270 view->p_restore = FALSE;
5271 }
5273 static void
5274 status_update_onbranch(void)
5275 {
5276 static const char *paths[][2] = {
5277 { "rebase-apply/rebasing", "Rebasing" },
5278 { "rebase-apply/applying", "Applying mailbox" },
5279 { "rebase-apply/", "Rebasing mailbox" },
5280 { "rebase-merge/interactive", "Interactive rebase" },
5281 { "rebase-merge/", "Rebase merge" },
5282 { "MERGE_HEAD", "Merging" },
5283 { "BISECT_LOG", "Bisecting" },
5284 { "HEAD", "On branch" },
5285 };
5286 char buf[SIZEOF_STR];
5287 struct stat stat;
5288 int i;
5290 if (is_initial_commit()) {
5291 string_copy(status_onbranch, "Initial commit");
5292 return;
5293 }
5295 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5296 char *head = opt_head;
5298 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5299 lstat(buf, &stat) < 0)
5300 continue;
5302 if (!*opt_head) {
5303 struct io io = {};
5305 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5306 io_open(&io, buf) &&
5307 io_read_buf(&io, buf, sizeof(buf))) {
5308 head = buf;
5309 if (!prefixcmp(head, "refs/heads/"))
5310 head += STRING_SIZE("refs/heads/");
5311 }
5312 }
5314 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5315 string_copy(status_onbranch, opt_head);
5316 return;
5317 }
5319 string_copy(status_onbranch, "Not currently on any branch");
5320 }
5322 /* First parse staged info using git-diff-index(1), then parse unstaged
5323 * info using git-diff-files(1), and finally untracked files using
5324 * git-ls-files(1). */
5325 static bool
5326 status_open(struct view *view)
5327 {
5328 reset_view(view);
5330 add_line_data(view, NULL, LINE_STAT_HEAD);
5331 status_update_onbranch();
5333 run_io_bg(update_index_argv);
5335 if (is_initial_commit()) {
5336 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5337 return FALSE;
5338 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5339 return FALSE;
5340 }
5342 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5343 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5344 return FALSE;
5346 /* Restore the exact position or use the specialized restore
5347 * mode? */
5348 if (!view->p_restore)
5349 status_restore(view);
5350 return TRUE;
5351 }
5353 static bool
5354 status_draw(struct view *view, struct line *line, unsigned int lineno)
5355 {
5356 struct status *status = line->data;
5357 enum line_type type;
5358 const char *text;
5360 if (!status) {
5361 switch (line->type) {
5362 case LINE_STAT_STAGED:
5363 type = LINE_STAT_SECTION;
5364 text = "Changes to be committed:";
5365 break;
5367 case LINE_STAT_UNSTAGED:
5368 type = LINE_STAT_SECTION;
5369 text = "Changed but not updated:";
5370 break;
5372 case LINE_STAT_UNTRACKED:
5373 type = LINE_STAT_SECTION;
5374 text = "Untracked files:";
5375 break;
5377 case LINE_STAT_NONE:
5378 type = LINE_DEFAULT;
5379 text = " (no files)";
5380 break;
5382 case LINE_STAT_HEAD:
5383 type = LINE_STAT_HEAD;
5384 text = status_onbranch;
5385 break;
5387 default:
5388 return FALSE;
5389 }
5390 } else {
5391 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5393 buf[0] = status->status;
5394 if (draw_text(view, line->type, buf, TRUE))
5395 return TRUE;
5396 type = LINE_DEFAULT;
5397 text = status->new.name;
5398 }
5400 draw_text(view, type, text, TRUE);
5401 return TRUE;
5402 }
5404 static enum request
5405 status_load_error(struct view *view, struct view *stage, const char *path)
5406 {
5407 if (displayed_views() == 2 || display[current_view] != view)
5408 maximize_view(view);
5409 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5410 return REQ_NONE;
5411 }
5413 static enum request
5414 status_enter(struct view *view, struct line *line)
5415 {
5416 struct status *status = line->data;
5417 const char *oldpath = status ? status->old.name : NULL;
5418 /* Diffs for unmerged entries are empty when passing the new
5419 * path, so leave it empty. */
5420 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5421 const char *info;
5422 enum open_flags split;
5423 struct view *stage = VIEW(REQ_VIEW_STAGE);
5425 if (line->type == LINE_STAT_NONE ||
5426 (!status && line[1].type == LINE_STAT_NONE)) {
5427 report("No file to diff");
5428 return REQ_NONE;
5429 }
5431 switch (line->type) {
5432 case LINE_STAT_STAGED:
5433 if (is_initial_commit()) {
5434 const char *no_head_diff_argv[] = {
5435 "git", "diff", "--no-color", "--patch-with-stat",
5436 "--", "/dev/null", newpath, NULL
5437 };
5439 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5440 return status_load_error(view, stage, newpath);
5441 } else {
5442 const char *index_show_argv[] = {
5443 "git", "diff-index", "--root", "--patch-with-stat",
5444 "-C", "-M", "--cached", "HEAD", "--",
5445 oldpath, newpath, NULL
5446 };
5448 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5449 return status_load_error(view, stage, newpath);
5450 }
5452 if (status)
5453 info = "Staged changes to %s";
5454 else
5455 info = "Staged changes";
5456 break;
5458 case LINE_STAT_UNSTAGED:
5459 {
5460 const char *files_show_argv[] = {
5461 "git", "diff-files", "--root", "--patch-with-stat",
5462 "-C", "-M", "--", oldpath, newpath, NULL
5463 };
5465 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5466 return status_load_error(view, stage, newpath);
5467 if (status)
5468 info = "Unstaged changes to %s";
5469 else
5470 info = "Unstaged changes";
5471 break;
5472 }
5473 case LINE_STAT_UNTRACKED:
5474 if (!newpath) {
5475 report("No file to show");
5476 return REQ_NONE;
5477 }
5479 if (!suffixcmp(status->new.name, -1, "/")) {
5480 report("Cannot display a directory");
5481 return REQ_NONE;
5482 }
5484 if (!prepare_update_file(stage, newpath))
5485 return status_load_error(view, stage, newpath);
5486 info = "Untracked file %s";
5487 break;
5489 case LINE_STAT_HEAD:
5490 return REQ_NONE;
5492 default:
5493 die("line type %d not handled in switch", line->type);
5494 }
5496 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5497 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5498 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5499 if (status) {
5500 stage_status = *status;
5501 } else {
5502 memset(&stage_status, 0, sizeof(stage_status));
5503 }
5505 stage_line_type = line->type;
5506 stage_chunks = 0;
5507 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5508 }
5510 return REQ_NONE;
5511 }
5513 static bool
5514 status_exists(struct status *status, enum line_type type)
5515 {
5516 struct view *view = VIEW(REQ_VIEW_STATUS);
5517 unsigned long lineno;
5519 for (lineno = 0; lineno < view->lines; lineno++) {
5520 struct line *line = &view->line[lineno];
5521 struct status *pos = line->data;
5523 if (line->type != type)
5524 continue;
5525 if (!pos && (!status || !status->status) && line[1].data) {
5526 select_view_line(view, lineno);
5527 return TRUE;
5528 }
5529 if (pos && !strcmp(status->new.name, pos->new.name)) {
5530 select_view_line(view, lineno);
5531 return TRUE;
5532 }
5533 }
5535 return FALSE;
5536 }
5539 static bool
5540 status_update_prepare(struct io *io, enum line_type type)
5541 {
5542 const char *staged_argv[] = {
5543 "git", "update-index", "-z", "--index-info", NULL
5544 };
5545 const char *others_argv[] = {
5546 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5547 };
5549 switch (type) {
5550 case LINE_STAT_STAGED:
5551 return run_io(io, staged_argv, opt_cdup, IO_WR);
5553 case LINE_STAT_UNSTAGED:
5554 return run_io(io, others_argv, opt_cdup, IO_WR);
5556 case LINE_STAT_UNTRACKED:
5557 return run_io(io, others_argv, NULL, IO_WR);
5559 default:
5560 die("line type %d not handled in switch", type);
5561 return FALSE;
5562 }
5563 }
5565 static bool
5566 status_update_write(struct io *io, struct status *status, enum line_type type)
5567 {
5568 char buf[SIZEOF_STR];
5569 size_t bufsize = 0;
5571 switch (type) {
5572 case LINE_STAT_STAGED:
5573 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5574 status->old.mode,
5575 status->old.rev,
5576 status->old.name, 0))
5577 return FALSE;
5578 break;
5580 case LINE_STAT_UNSTAGED:
5581 case LINE_STAT_UNTRACKED:
5582 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5583 return FALSE;
5584 break;
5586 default:
5587 die("line type %d not handled in switch", type);
5588 }
5590 return io_write(io, buf, bufsize);
5591 }
5593 static bool
5594 status_update_file(struct status *status, enum line_type type)
5595 {
5596 struct io io = {};
5597 bool result;
5599 if (!status_update_prepare(&io, type))
5600 return FALSE;
5602 result = status_update_write(&io, status, type);
5603 return done_io(&io) && result;
5604 }
5606 static bool
5607 status_update_files(struct view *view, struct line *line)
5608 {
5609 char buf[sizeof(view->ref)];
5610 struct io io = {};
5611 bool result = TRUE;
5612 struct line *pos = view->line + view->lines;
5613 int files = 0;
5614 int file, done;
5615 int cursor_y = -1, cursor_x = -1;
5617 if (!status_update_prepare(&io, line->type))
5618 return FALSE;
5620 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5621 files++;
5623 string_copy(buf, view->ref);
5624 getsyx(cursor_y, cursor_x);
5625 for (file = 0, done = 5; result && file < files; line++, file++) {
5626 int almost_done = file * 100 / files;
5628 if (almost_done > done) {
5629 done = almost_done;
5630 string_format(view->ref, "updating file %u of %u (%d%% done)",
5631 file, files, done);
5632 update_view_title(view);
5633 setsyx(cursor_y, cursor_x);
5634 doupdate();
5635 }
5636 result = status_update_write(&io, line->data, line->type);
5637 }
5638 string_copy(view->ref, buf);
5640 return done_io(&io) && result;
5641 }
5643 static bool
5644 status_update(struct view *view)
5645 {
5646 struct line *line = &view->line[view->lineno];
5648 assert(view->lines);
5650 if (!line->data) {
5651 /* This should work even for the "On branch" line. */
5652 if (line < view->line + view->lines && !line[1].data) {
5653 report("Nothing to update");
5654 return FALSE;
5655 }
5657 if (!status_update_files(view, line + 1)) {
5658 report("Failed to update file status");
5659 return FALSE;
5660 }
5662 } else if (!status_update_file(line->data, line->type)) {
5663 report("Failed to update file status");
5664 return FALSE;
5665 }
5667 return TRUE;
5668 }
5670 static bool
5671 status_revert(struct status *status, enum line_type type, bool has_none)
5672 {
5673 if (!status || type != LINE_STAT_UNSTAGED) {
5674 if (type == LINE_STAT_STAGED) {
5675 report("Cannot revert changes to staged files");
5676 } else if (type == LINE_STAT_UNTRACKED) {
5677 report("Cannot revert changes to untracked files");
5678 } else if (has_none) {
5679 report("Nothing to revert");
5680 } else {
5681 report("Cannot revert changes to multiple files");
5682 }
5683 return FALSE;
5685 } else {
5686 char mode[10] = "100644";
5687 const char *reset_argv[] = {
5688 "git", "update-index", "--cacheinfo", mode,
5689 status->old.rev, status->old.name, NULL
5690 };
5691 const char *checkout_argv[] = {
5692 "git", "checkout", "--", status->old.name, NULL
5693 };
5695 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5696 return FALSE;
5697 string_format(mode, "%o", status->old.mode);
5698 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5699 run_io_fg(checkout_argv, opt_cdup);
5700 }
5701 }
5703 static enum request
5704 status_request(struct view *view, enum request request, struct line *line)
5705 {
5706 struct status *status = line->data;
5708 switch (request) {
5709 case REQ_STATUS_UPDATE:
5710 if (!status_update(view))
5711 return REQ_NONE;
5712 break;
5714 case REQ_STATUS_REVERT:
5715 if (!status_revert(status, line->type, status_has_none(view, line)))
5716 return REQ_NONE;
5717 break;
5719 case REQ_STATUS_MERGE:
5720 if (!status || status->status != 'U') {
5721 report("Merging only possible for files with unmerged status ('U').");
5722 return REQ_NONE;
5723 }
5724 open_mergetool(status->new.name);
5725 break;
5727 case REQ_EDIT:
5728 if (!status)
5729 return request;
5730 if (status->status == 'D') {
5731 report("File has been deleted.");
5732 return REQ_NONE;
5733 }
5735 open_editor(status->status != '?', status->new.name);
5736 break;
5738 case REQ_VIEW_BLAME:
5739 if (status) {
5740 string_copy(opt_file, status->new.name);
5741 opt_ref[0] = 0;
5742 }
5743 return request;
5745 case REQ_ENTER:
5746 /* After returning the status view has been split to
5747 * show the stage view. No further reloading is
5748 * necessary. */
5749 return status_enter(view, line);
5751 case REQ_REFRESH:
5752 /* Simply reload the view. */
5753 break;
5755 default:
5756 return request;
5757 }
5759 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5761 return REQ_NONE;
5762 }
5764 static void
5765 status_select(struct view *view, struct line *line)
5766 {
5767 struct status *status = line->data;
5768 char file[SIZEOF_STR] = "all files";
5769 const char *text;
5770 const char *key;
5772 if (status && !string_format(file, "'%s'", status->new.name))
5773 return;
5775 if (!status && line[1].type == LINE_STAT_NONE)
5776 line++;
5778 switch (line->type) {
5779 case LINE_STAT_STAGED:
5780 text = "Press %s to unstage %s for commit";
5781 break;
5783 case LINE_STAT_UNSTAGED:
5784 text = "Press %s to stage %s for commit";
5785 break;
5787 case LINE_STAT_UNTRACKED:
5788 text = "Press %s to stage %s for addition";
5789 break;
5791 case LINE_STAT_HEAD:
5792 case LINE_STAT_NONE:
5793 text = "Nothing to update";
5794 break;
5796 default:
5797 die("line type %d not handled in switch", line->type);
5798 }
5800 if (status && status->status == 'U') {
5801 text = "Press %s to resolve conflict in %s";
5802 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5804 } else {
5805 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5806 }
5808 string_format(view->ref, text, key, file);
5809 }
5811 static bool
5812 status_grep(struct view *view, struct line *line)
5813 {
5814 struct status *status = line->data;
5816 if (status) {
5817 const char buf[2] = { status->status, 0 };
5818 const char *text[] = { status->new.name, buf, NULL };
5820 return grep_text(view, text);
5821 }
5823 return FALSE;
5824 }
5826 static struct view_ops status_ops = {
5827 "file",
5828 NULL,
5829 status_open,
5830 NULL,
5831 status_draw,
5832 status_request,
5833 status_grep,
5834 status_select,
5835 };
5838 static bool
5839 stage_diff_write(struct io *io, struct line *line, struct line *end)
5840 {
5841 while (line < end) {
5842 if (!io_write(io, line->data, strlen(line->data)) ||
5843 !io_write(io, "\n", 1))
5844 return FALSE;
5845 line++;
5846 if (line->type == LINE_DIFF_CHUNK ||
5847 line->type == LINE_DIFF_HEADER)
5848 break;
5849 }
5851 return TRUE;
5852 }
5854 static struct line *
5855 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5856 {
5857 for (; view->line < line; line--)
5858 if (line->type == type)
5859 return line;
5861 return NULL;
5862 }
5864 static bool
5865 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5866 {
5867 const char *apply_argv[SIZEOF_ARG] = {
5868 "git", "apply", "--whitespace=nowarn", NULL
5869 };
5870 struct line *diff_hdr;
5871 struct io io = {};
5872 int argc = 3;
5874 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5875 if (!diff_hdr)
5876 return FALSE;
5878 if (!revert)
5879 apply_argv[argc++] = "--cached";
5880 if (revert || stage_line_type == LINE_STAT_STAGED)
5881 apply_argv[argc++] = "-R";
5882 apply_argv[argc++] = "-";
5883 apply_argv[argc++] = NULL;
5884 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5885 return FALSE;
5887 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5888 !stage_diff_write(&io, chunk, view->line + view->lines))
5889 chunk = NULL;
5891 done_io(&io);
5892 run_io_bg(update_index_argv);
5894 return chunk ? TRUE : FALSE;
5895 }
5897 static bool
5898 stage_update(struct view *view, struct line *line)
5899 {
5900 struct line *chunk = NULL;
5902 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5903 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5905 if (chunk) {
5906 if (!stage_apply_chunk(view, chunk, FALSE)) {
5907 report("Failed to apply chunk");
5908 return FALSE;
5909 }
5911 } else if (!stage_status.status) {
5912 view = VIEW(REQ_VIEW_STATUS);
5914 for (line = view->line; line < view->line + view->lines; line++)
5915 if (line->type == stage_line_type)
5916 break;
5918 if (!status_update_files(view, line + 1)) {
5919 report("Failed to update files");
5920 return FALSE;
5921 }
5923 } else if (!status_update_file(&stage_status, stage_line_type)) {
5924 report("Failed to update file");
5925 return FALSE;
5926 }
5928 return TRUE;
5929 }
5931 static bool
5932 stage_revert(struct view *view, struct line *line)
5933 {
5934 struct line *chunk = NULL;
5936 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5937 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5939 if (chunk) {
5940 if (!prompt_yesno("Are you sure you want to revert changes?"))
5941 return FALSE;
5943 if (!stage_apply_chunk(view, chunk, TRUE)) {
5944 report("Failed to revert chunk");
5945 return FALSE;
5946 }
5947 return TRUE;
5949 } else {
5950 return status_revert(stage_status.status ? &stage_status : NULL,
5951 stage_line_type, FALSE);
5952 }
5953 }
5956 static void
5957 stage_next(struct view *view, struct line *line)
5958 {
5959 int i;
5961 if (!stage_chunks) {
5962 for (line = view->line; line < view->line + view->lines; line++) {
5963 if (line->type != LINE_DIFF_CHUNK)
5964 continue;
5966 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5967 report("Allocation failure");
5968 return;
5969 }
5971 stage_chunk[stage_chunks++] = line - view->line;
5972 }
5973 }
5975 for (i = 0; i < stage_chunks; i++) {
5976 if (stage_chunk[i] > view->lineno) {
5977 do_scroll_view(view, stage_chunk[i] - view->lineno);
5978 report("Chunk %d of %d", i + 1, stage_chunks);
5979 return;
5980 }
5981 }
5983 report("No next chunk found");
5984 }
5986 static enum request
5987 stage_request(struct view *view, enum request request, struct line *line)
5988 {
5989 switch (request) {
5990 case REQ_STATUS_UPDATE:
5991 if (!stage_update(view, line))
5992 return REQ_NONE;
5993 break;
5995 case REQ_STATUS_REVERT:
5996 if (!stage_revert(view, line))
5997 return REQ_NONE;
5998 break;
6000 case REQ_STAGE_NEXT:
6001 if (stage_line_type == LINE_STAT_UNTRACKED) {
6002 report("File is untracked; press %s to add",
6003 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6004 return REQ_NONE;
6005 }
6006 stage_next(view, line);
6007 return REQ_NONE;
6009 case REQ_EDIT:
6010 if (!stage_status.new.name[0])
6011 return request;
6012 if (stage_status.status == 'D') {
6013 report("File has been deleted.");
6014 return REQ_NONE;
6015 }
6017 open_editor(stage_status.status != '?', stage_status.new.name);
6018 break;
6020 case REQ_REFRESH:
6021 /* Reload everything ... */
6022 break;
6024 case REQ_VIEW_BLAME:
6025 if (stage_status.new.name[0]) {
6026 string_copy(opt_file, stage_status.new.name);
6027 opt_ref[0] = 0;
6028 }
6029 return request;
6031 case REQ_ENTER:
6032 return pager_request(view, request, line);
6034 default:
6035 return request;
6036 }
6038 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6039 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6041 /* Check whether the staged entry still exists, and close the
6042 * stage view if it doesn't. */
6043 if (!status_exists(&stage_status, stage_line_type)) {
6044 status_restore(VIEW(REQ_VIEW_STATUS));
6045 return REQ_VIEW_CLOSE;
6046 }
6048 if (stage_line_type == LINE_STAT_UNTRACKED) {
6049 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6050 report("Cannot display a directory");
6051 return REQ_NONE;
6052 }
6054 if (!prepare_update_file(view, stage_status.new.name)) {
6055 report("Failed to open file: %s", strerror(errno));
6056 return REQ_NONE;
6057 }
6058 }
6059 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6061 return REQ_NONE;
6062 }
6064 static struct view_ops stage_ops = {
6065 "line",
6066 NULL,
6067 NULL,
6068 pager_read,
6069 pager_draw,
6070 stage_request,
6071 pager_grep,
6072 pager_select,
6073 };
6076 /*
6077 * Revision graph
6078 */
6080 struct commit {
6081 char id[SIZEOF_REV]; /* SHA1 ID. */
6082 char title[128]; /* First line of the commit message. */
6083 const char *author; /* Author of the commit. */
6084 time_t time; /* Date from the author ident. */
6085 struct ref_list *refs; /* Repository references. */
6086 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6087 size_t graph_size; /* The width of the graph array. */
6088 bool has_parents; /* Rewritten --parents seen. */
6089 };
6091 /* Size of rev graph with no "padding" columns */
6092 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6094 struct rev_graph {
6095 struct rev_graph *prev, *next, *parents;
6096 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6097 size_t size;
6098 struct commit *commit;
6099 size_t pos;
6100 unsigned int boundary:1;
6101 };
6103 /* Parents of the commit being visualized. */
6104 static struct rev_graph graph_parents[4];
6106 /* The current stack of revisions on the graph. */
6107 static struct rev_graph graph_stacks[4] = {
6108 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6109 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6110 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6111 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6112 };
6114 static inline bool
6115 graph_parent_is_merge(struct rev_graph *graph)
6116 {
6117 return graph->parents->size > 1;
6118 }
6120 static inline void
6121 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6122 {
6123 struct commit *commit = graph->commit;
6125 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6126 commit->graph[commit->graph_size++] = symbol;
6127 }
6129 static void
6130 clear_rev_graph(struct rev_graph *graph)
6131 {
6132 graph->boundary = 0;
6133 graph->size = graph->pos = 0;
6134 graph->commit = NULL;
6135 memset(graph->parents, 0, sizeof(*graph->parents));
6136 }
6138 static void
6139 done_rev_graph(struct rev_graph *graph)
6140 {
6141 if (graph_parent_is_merge(graph) &&
6142 graph->pos < graph->size - 1 &&
6143 graph->next->size == graph->size + graph->parents->size - 1) {
6144 size_t i = graph->pos + graph->parents->size - 1;
6146 graph->commit->graph_size = i * 2;
6147 while (i < graph->next->size - 1) {
6148 append_to_rev_graph(graph, ' ');
6149 append_to_rev_graph(graph, '\\');
6150 i++;
6151 }
6152 }
6154 clear_rev_graph(graph);
6155 }
6157 static void
6158 push_rev_graph(struct rev_graph *graph, const char *parent)
6159 {
6160 int i;
6162 /* "Collapse" duplicate parents lines.
6163 *
6164 * FIXME: This needs to also update update the drawn graph but
6165 * for now it just serves as a method for pruning graph lines. */
6166 for (i = 0; i < graph->size; i++)
6167 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6168 return;
6170 if (graph->size < SIZEOF_REVITEMS) {
6171 string_copy_rev(graph->rev[graph->size++], parent);
6172 }
6173 }
6175 static chtype
6176 get_rev_graph_symbol(struct rev_graph *graph)
6177 {
6178 chtype symbol;
6180 if (graph->boundary)
6181 symbol = REVGRAPH_BOUND;
6182 else if (graph->parents->size == 0)
6183 symbol = REVGRAPH_INIT;
6184 else if (graph_parent_is_merge(graph))
6185 symbol = REVGRAPH_MERGE;
6186 else if (graph->pos >= graph->size)
6187 symbol = REVGRAPH_BRANCH;
6188 else
6189 symbol = REVGRAPH_COMMIT;
6191 return symbol;
6192 }
6194 static void
6195 draw_rev_graph(struct rev_graph *graph)
6196 {
6197 struct rev_filler {
6198 chtype separator, line;
6199 };
6200 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6201 static struct rev_filler fillers[] = {
6202 { ' ', '|' },
6203 { '`', '.' },
6204 { '\'', ' ' },
6205 { '/', ' ' },
6206 };
6207 chtype symbol = get_rev_graph_symbol(graph);
6208 struct rev_filler *filler;
6209 size_t i;
6211 if (opt_line_graphics)
6212 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6214 filler = &fillers[DEFAULT];
6216 for (i = 0; i < graph->pos; i++) {
6217 append_to_rev_graph(graph, filler->line);
6218 if (graph_parent_is_merge(graph->prev) &&
6219 graph->prev->pos == i)
6220 filler = &fillers[RSHARP];
6222 append_to_rev_graph(graph, filler->separator);
6223 }
6225 /* Place the symbol for this revision. */
6226 append_to_rev_graph(graph, symbol);
6228 if (graph->prev->size > graph->size)
6229 filler = &fillers[RDIAG];
6230 else
6231 filler = &fillers[DEFAULT];
6233 i++;
6235 for (; i < graph->size; i++) {
6236 append_to_rev_graph(graph, filler->separator);
6237 append_to_rev_graph(graph, filler->line);
6238 if (graph_parent_is_merge(graph->prev) &&
6239 i < graph->prev->pos + graph->parents->size)
6240 filler = &fillers[RSHARP];
6241 if (graph->prev->size > graph->size)
6242 filler = &fillers[LDIAG];
6243 }
6245 if (graph->prev->size > graph->size) {
6246 append_to_rev_graph(graph, filler->separator);
6247 if (filler->line != ' ')
6248 append_to_rev_graph(graph, filler->line);
6249 }
6250 }
6252 /* Prepare the next rev graph */
6253 static void
6254 prepare_rev_graph(struct rev_graph *graph)
6255 {
6256 size_t i;
6258 /* First, traverse all lines of revisions up to the active one. */
6259 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6260 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6261 break;
6263 push_rev_graph(graph->next, graph->rev[graph->pos]);
6264 }
6266 /* Interleave the new revision parent(s). */
6267 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6268 push_rev_graph(graph->next, graph->parents->rev[i]);
6270 /* Lastly, put any remaining revisions. */
6271 for (i = graph->pos + 1; i < graph->size; i++)
6272 push_rev_graph(graph->next, graph->rev[i]);
6273 }
6275 static void
6276 update_rev_graph(struct view *view, struct rev_graph *graph)
6277 {
6278 /* If this is the finalizing update ... */
6279 if (graph->commit)
6280 prepare_rev_graph(graph);
6282 /* Graph visualization needs a one rev look-ahead,
6283 * so the first update doesn't visualize anything. */
6284 if (!graph->prev->commit)
6285 return;
6287 if (view->lines > 2)
6288 view->line[view->lines - 3].dirty = 1;
6289 if (view->lines > 1)
6290 view->line[view->lines - 2].dirty = 1;
6291 draw_rev_graph(graph->prev);
6292 done_rev_graph(graph->prev->prev);
6293 }
6296 /*
6297 * Main view backend
6298 */
6300 static const char *main_argv[SIZEOF_ARG] = {
6301 "git", "log", "--no-color", "--pretty=raw", "--parents",
6302 "--topo-order", "%(head)", NULL
6303 };
6305 static bool
6306 main_draw(struct view *view, struct line *line, unsigned int lineno)
6307 {
6308 struct commit *commit = line->data;
6310 if (!commit->author)
6311 return FALSE;
6313 if (opt_date && draw_date(view, &commit->time))
6314 return TRUE;
6316 if (opt_author && draw_author(view, commit->author))
6317 return TRUE;
6319 if (opt_rev_graph && commit->graph_size &&
6320 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6321 return TRUE;
6323 if (opt_show_refs && commit->refs) {
6324 size_t i;
6326 for (i = 0; i < commit->refs->size; i++) {
6327 struct ref *ref = commit->refs->refs[i];
6328 enum line_type type;
6330 if (ref->head)
6331 type = LINE_MAIN_HEAD;
6332 else if (ref->ltag)
6333 type = LINE_MAIN_LOCAL_TAG;
6334 else if (ref->tag)
6335 type = LINE_MAIN_TAG;
6336 else if (ref->tracked)
6337 type = LINE_MAIN_TRACKED;
6338 else if (ref->remote)
6339 type = LINE_MAIN_REMOTE;
6340 else
6341 type = LINE_MAIN_REF;
6343 if (draw_text(view, type, "[", TRUE) ||
6344 draw_text(view, type, ref->name, TRUE) ||
6345 draw_text(view, type, "]", TRUE))
6346 return TRUE;
6348 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6349 return TRUE;
6350 }
6351 }
6353 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6354 return TRUE;
6355 }
6357 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6358 static bool
6359 main_read(struct view *view, char *line)
6360 {
6361 static struct rev_graph *graph = graph_stacks;
6362 enum line_type type;
6363 struct commit *commit;
6365 if (!line) {
6366 int i;
6368 if (!view->lines && !view->parent)
6369 die("No revisions match the given arguments.");
6370 if (view->lines > 0) {
6371 commit = view->line[view->lines - 1].data;
6372 view->line[view->lines - 1].dirty = 1;
6373 if (!commit->author) {
6374 view->lines--;
6375 free(commit);
6376 graph->commit = NULL;
6377 }
6378 }
6379 update_rev_graph(view, graph);
6381 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6382 clear_rev_graph(&graph_stacks[i]);
6383 return TRUE;
6384 }
6386 type = get_line_type(line);
6387 if (type == LINE_COMMIT) {
6388 commit = calloc(1, sizeof(struct commit));
6389 if (!commit)
6390 return FALSE;
6392 line += STRING_SIZE("commit ");
6393 if (*line == '-') {
6394 graph->boundary = 1;
6395 line++;
6396 }
6398 string_copy_rev(commit->id, line);
6399 commit->refs = get_ref_list(commit->id);
6400 graph->commit = commit;
6401 add_line_data(view, commit, LINE_MAIN_COMMIT);
6403 while ((line = strchr(line, ' '))) {
6404 line++;
6405 push_rev_graph(graph->parents, line);
6406 commit->has_parents = TRUE;
6407 }
6408 return TRUE;
6409 }
6411 if (!view->lines)
6412 return TRUE;
6413 commit = view->line[view->lines - 1].data;
6415 switch (type) {
6416 case LINE_PARENT:
6417 if (commit->has_parents)
6418 break;
6419 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6420 break;
6422 case LINE_AUTHOR:
6423 parse_author_line(line + STRING_SIZE("author "),
6424 &commit->author, &commit->time);
6425 update_rev_graph(view, graph);
6426 graph = graph->next;
6427 break;
6429 default:
6430 /* Fill in the commit title if it has not already been set. */
6431 if (commit->title[0])
6432 break;
6434 /* Require titles to start with a non-space character at the
6435 * offset used by git log. */
6436 if (strncmp(line, " ", 4))
6437 break;
6438 line += 4;
6439 /* Well, if the title starts with a whitespace character,
6440 * try to be forgiving. Otherwise we end up with no title. */
6441 while (isspace(*line))
6442 line++;
6443 if (*line == '\0')
6444 break;
6445 /* FIXME: More graceful handling of titles; append "..." to
6446 * shortened titles, etc. */
6448 string_expand(commit->title, sizeof(commit->title), line, 1);
6449 view->line[view->lines - 1].dirty = 1;
6450 }
6452 return TRUE;
6453 }
6455 static enum request
6456 main_request(struct view *view, enum request request, struct line *line)
6457 {
6458 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6460 switch (request) {
6461 case REQ_ENTER:
6462 open_view(view, REQ_VIEW_DIFF, flags);
6463 break;
6464 case REQ_REFRESH:
6465 load_refs();
6466 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6467 break;
6468 default:
6469 return request;
6470 }
6472 return REQ_NONE;
6473 }
6475 static bool
6476 grep_refs(struct ref_list *list, regex_t *regex)
6477 {
6478 regmatch_t pmatch;
6479 size_t i;
6481 if (!opt_show_refs || !list)
6482 return FALSE;
6484 for (i = 0; i < list->size; i++) {
6485 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6486 return TRUE;
6487 }
6489 return FALSE;
6490 }
6492 static bool
6493 main_grep(struct view *view, struct line *line)
6494 {
6495 struct commit *commit = line->data;
6496 const char *text[] = {
6497 commit->title,
6498 opt_author ? commit->author : "",
6499 opt_date ? mkdate(&commit->time) : "",
6500 NULL
6501 };
6503 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6504 }
6506 static void
6507 main_select(struct view *view, struct line *line)
6508 {
6509 struct commit *commit = line->data;
6511 string_copy_rev(view->ref, commit->id);
6512 string_copy_rev(ref_commit, view->ref);
6513 }
6515 static struct view_ops main_ops = {
6516 "commit",
6517 main_argv,
6518 NULL,
6519 main_read,
6520 main_draw,
6521 main_request,
6522 main_grep,
6523 main_select,
6524 };
6527 /*
6528 * Unicode / UTF-8 handling
6529 *
6530 * NOTE: Much of the following code for dealing with Unicode is derived from
6531 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6532 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6533 */
6535 static inline int
6536 unicode_width(unsigned long c)
6537 {
6538 if (c >= 0x1100 &&
6539 (c <= 0x115f /* Hangul Jamo */
6540 || c == 0x2329
6541 || c == 0x232a
6542 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6543 /* CJK ... Yi */
6544 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6545 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6546 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6547 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6548 || (c >= 0xffe0 && c <= 0xffe6)
6549 || (c >= 0x20000 && c <= 0x2fffd)
6550 || (c >= 0x30000 && c <= 0x3fffd)))
6551 return 2;
6553 if (c == '\t')
6554 return opt_tab_size;
6556 return 1;
6557 }
6559 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6560 * Illegal bytes are set one. */
6561 static const unsigned char utf8_bytes[256] = {
6562 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,
6563 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,
6564 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,
6565 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,
6566 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,
6567 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,
6568 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,
6569 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,
6570 };
6572 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6573 static inline unsigned long
6574 utf8_to_unicode(const char *string, size_t length)
6575 {
6576 unsigned long unicode;
6578 switch (length) {
6579 case 1:
6580 unicode = string[0];
6581 break;
6582 case 2:
6583 unicode = (string[0] & 0x1f) << 6;
6584 unicode += (string[1] & 0x3f);
6585 break;
6586 case 3:
6587 unicode = (string[0] & 0x0f) << 12;
6588 unicode += ((string[1] & 0x3f) << 6);
6589 unicode += (string[2] & 0x3f);
6590 break;
6591 case 4:
6592 unicode = (string[0] & 0x0f) << 18;
6593 unicode += ((string[1] & 0x3f) << 12);
6594 unicode += ((string[2] & 0x3f) << 6);
6595 unicode += (string[3] & 0x3f);
6596 break;
6597 case 5:
6598 unicode = (string[0] & 0x0f) << 24;
6599 unicode += ((string[1] & 0x3f) << 18);
6600 unicode += ((string[2] & 0x3f) << 12);
6601 unicode += ((string[3] & 0x3f) << 6);
6602 unicode += (string[4] & 0x3f);
6603 break;
6604 case 6:
6605 unicode = (string[0] & 0x01) << 30;
6606 unicode += ((string[1] & 0x3f) << 24);
6607 unicode += ((string[2] & 0x3f) << 18);
6608 unicode += ((string[3] & 0x3f) << 12);
6609 unicode += ((string[4] & 0x3f) << 6);
6610 unicode += (string[5] & 0x3f);
6611 break;
6612 default:
6613 die("Invalid Unicode length");
6614 }
6616 /* Invalid characters could return the special 0xfffd value but NUL
6617 * should be just as good. */
6618 return unicode > 0xffff ? 0 : unicode;
6619 }
6621 /* Calculates how much of string can be shown within the given maximum width
6622 * and sets trimmed parameter to non-zero value if all of string could not be
6623 * shown. If the reserve flag is TRUE, it will reserve at least one
6624 * trailing character, which can be useful when drawing a delimiter.
6625 *
6626 * Returns the number of bytes to output from string to satisfy max_width. */
6627 static size_t
6628 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6629 {
6630 const char *string = *start;
6631 const char *end = strchr(string, '\0');
6632 unsigned char last_bytes = 0;
6633 size_t last_ucwidth = 0;
6635 *width = 0;
6636 *trimmed = 0;
6638 while (string < end) {
6639 int c = *(unsigned char *) string;
6640 unsigned char bytes = utf8_bytes[c];
6641 size_t ucwidth;
6642 unsigned long unicode;
6644 if (string + bytes > end)
6645 break;
6647 /* Change representation to figure out whether
6648 * it is a single- or double-width character. */
6650 unicode = utf8_to_unicode(string, bytes);
6651 /* FIXME: Graceful handling of invalid Unicode character. */
6652 if (!unicode)
6653 break;
6655 ucwidth = unicode_width(unicode);
6656 if (skip > 0) {
6657 skip -= ucwidth <= skip ? ucwidth : skip;
6658 *start += bytes;
6659 }
6660 *width += ucwidth;
6661 if (*width > max_width) {
6662 *trimmed = 1;
6663 *width -= ucwidth;
6664 if (reserve && *width == max_width) {
6665 string -= last_bytes;
6666 *width -= last_ucwidth;
6667 }
6668 break;
6669 }
6671 string += bytes;
6672 last_bytes = ucwidth ? bytes : 0;
6673 last_ucwidth = ucwidth;
6674 }
6676 return string - *start;
6677 }
6680 /*
6681 * Status management
6682 */
6684 /* Whether or not the curses interface has been initialized. */
6685 static bool cursed = FALSE;
6687 /* Terminal hacks and workarounds. */
6688 static bool use_scroll_redrawwin;
6689 static bool use_scroll_status_wclear;
6691 /* The status window is used for polling keystrokes. */
6692 static WINDOW *status_win;
6694 /* Reading from the prompt? */
6695 static bool input_mode = FALSE;
6697 static bool status_empty = FALSE;
6699 /* Update status and title window. */
6700 static void
6701 report(const char *msg, ...)
6702 {
6703 struct view *view = display[current_view];
6705 if (input_mode)
6706 return;
6708 if (!view) {
6709 char buf[SIZEOF_STR];
6710 va_list args;
6712 va_start(args, msg);
6713 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6714 buf[sizeof(buf) - 1] = 0;
6715 buf[sizeof(buf) - 2] = '.';
6716 buf[sizeof(buf) - 3] = '.';
6717 buf[sizeof(buf) - 4] = '.';
6718 }
6719 va_end(args);
6720 die("%s", buf);
6721 }
6723 if (!status_empty || *msg) {
6724 va_list args;
6726 va_start(args, msg);
6728 wmove(status_win, 0, 0);
6729 if (view->has_scrolled && use_scroll_status_wclear)
6730 wclear(status_win);
6731 if (*msg) {
6732 vwprintw(status_win, msg, args);
6733 status_empty = FALSE;
6734 } else {
6735 status_empty = TRUE;
6736 }
6737 wclrtoeol(status_win);
6738 wnoutrefresh(status_win);
6740 va_end(args);
6741 }
6743 update_view_title(view);
6744 }
6746 /* Controls when nodelay should be in effect when polling user input. */
6747 static void
6748 set_nonblocking_input(bool loading)
6749 {
6750 static unsigned int loading_views;
6752 if ((loading == FALSE && loading_views-- == 1) ||
6753 (loading == TRUE && loading_views++ == 0))
6754 nodelay(status_win, loading);
6755 }
6757 static void
6758 init_display(void)
6759 {
6760 const char *term;
6761 int x, y;
6763 /* Initialize the curses library */
6764 if (isatty(STDIN_FILENO)) {
6765 cursed = !!initscr();
6766 opt_tty = stdin;
6767 } else {
6768 /* Leave stdin and stdout alone when acting as a pager. */
6769 opt_tty = fopen("/dev/tty", "r+");
6770 if (!opt_tty)
6771 die("Failed to open /dev/tty");
6772 cursed = !!newterm(NULL, opt_tty, opt_tty);
6773 }
6775 if (!cursed)
6776 die("Failed to initialize curses");
6778 nonl(); /* Disable conversion and detect newlines from input. */
6779 cbreak(); /* Take input chars one at a time, no wait for \n */
6780 noecho(); /* Don't echo input */
6781 leaveok(stdscr, FALSE);
6783 if (has_colors())
6784 init_colors();
6786 getmaxyx(stdscr, y, x);
6787 status_win = newwin(1, 0, y - 1, 0);
6788 if (!status_win)
6789 die("Failed to create status window");
6791 /* Enable keyboard mapping */
6792 keypad(status_win, TRUE);
6793 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6795 TABSIZE = opt_tab_size;
6796 if (opt_line_graphics) {
6797 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6798 }
6800 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6801 if (term && !strcmp(term, "gnome-terminal")) {
6802 /* In the gnome-terminal-emulator, the message from
6803 * scrolling up one line when impossible followed by
6804 * scrolling down one line causes corruption of the
6805 * status line. This is fixed by calling wclear. */
6806 use_scroll_status_wclear = TRUE;
6807 use_scroll_redrawwin = FALSE;
6809 } else if (term && !strcmp(term, "xrvt-xpm")) {
6810 /* No problems with full optimizations in xrvt-(unicode)
6811 * and aterm. */
6812 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6814 } else {
6815 /* When scrolling in (u)xterm the last line in the
6816 * scrolling direction will update slowly. */
6817 use_scroll_redrawwin = TRUE;
6818 use_scroll_status_wclear = FALSE;
6819 }
6820 }
6822 static int
6823 get_input(int prompt_position)
6824 {
6825 struct view *view;
6826 int i, key, cursor_y, cursor_x;
6828 if (prompt_position)
6829 input_mode = TRUE;
6831 while (TRUE) {
6832 foreach_view (view, i) {
6833 update_view(view);
6834 if (view_is_displayed(view) && view->has_scrolled &&
6835 use_scroll_redrawwin)
6836 redrawwin(view->win);
6837 view->has_scrolled = FALSE;
6838 }
6840 /* Update the cursor position. */
6841 if (prompt_position) {
6842 getbegyx(status_win, cursor_y, cursor_x);
6843 cursor_x = prompt_position;
6844 } else {
6845 view = display[current_view];
6846 getbegyx(view->win, cursor_y, cursor_x);
6847 cursor_x = view->width - 1;
6848 cursor_y += view->lineno - view->offset;
6849 }
6850 setsyx(cursor_y, cursor_x);
6852 /* Refresh, accept single keystroke of input */
6853 doupdate();
6854 key = wgetch(status_win);
6856 /* wgetch() with nodelay() enabled returns ERR when
6857 * there's no input. */
6858 if (key == ERR) {
6860 } else if (key == KEY_RESIZE) {
6861 int height, width;
6863 getmaxyx(stdscr, height, width);
6865 wresize(status_win, 1, width);
6866 mvwin(status_win, height - 1, 0);
6867 wnoutrefresh(status_win);
6868 resize_display();
6869 redraw_display(TRUE);
6871 } else {
6872 input_mode = FALSE;
6873 return key;
6874 }
6875 }
6876 }
6878 static char *
6879 prompt_input(const char *prompt, input_handler handler, void *data)
6880 {
6881 enum input_status status = INPUT_OK;
6882 static char buf[SIZEOF_STR];
6883 size_t pos = 0;
6885 buf[pos] = 0;
6887 while (status == INPUT_OK || status == INPUT_SKIP) {
6888 int key;
6890 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6891 wclrtoeol(status_win);
6893 key = get_input(pos + 1);
6894 switch (key) {
6895 case KEY_RETURN:
6896 case KEY_ENTER:
6897 case '\n':
6898 status = pos ? INPUT_STOP : INPUT_CANCEL;
6899 break;
6901 case KEY_BACKSPACE:
6902 if (pos > 0)
6903 buf[--pos] = 0;
6904 else
6905 status = INPUT_CANCEL;
6906 break;
6908 case KEY_ESC:
6909 status = INPUT_CANCEL;
6910 break;
6912 default:
6913 if (pos >= sizeof(buf)) {
6914 report("Input string too long");
6915 return NULL;
6916 }
6918 status = handler(data, buf, key);
6919 if (status == INPUT_OK)
6920 buf[pos++] = (char) key;
6921 }
6922 }
6924 /* Clear the status window */
6925 status_empty = FALSE;
6926 report("");
6928 if (status == INPUT_CANCEL)
6929 return NULL;
6931 buf[pos++] = 0;
6933 return buf;
6934 }
6936 static enum input_status
6937 prompt_yesno_handler(void *data, char *buf, int c)
6938 {
6939 if (c == 'y' || c == 'Y')
6940 return INPUT_STOP;
6941 if (c == 'n' || c == 'N')
6942 return INPUT_CANCEL;
6943 return INPUT_SKIP;
6944 }
6946 static bool
6947 prompt_yesno(const char *prompt)
6948 {
6949 char prompt2[SIZEOF_STR];
6951 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6952 return FALSE;
6954 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6955 }
6957 static enum input_status
6958 read_prompt_handler(void *data, char *buf, int c)
6959 {
6960 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6961 }
6963 static char *
6964 read_prompt(const char *prompt)
6965 {
6966 return prompt_input(prompt, read_prompt_handler, NULL);
6967 }
6969 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
6970 {
6971 enum input_status status = INPUT_OK;
6972 int size = 0;
6974 while (items[size].text)
6975 size++;
6977 while (status == INPUT_OK) {
6978 const struct menu_item *item = &items[*selected];
6979 int key;
6980 int i;
6982 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
6983 prompt, *selected + 1, size);
6984 if (item->hotkey)
6985 wprintw(status_win, "[%c] ", (char) item->hotkey);
6986 wprintw(status_win, "%s", item->text);
6987 wclrtoeol(status_win);
6989 key = get_input(COLS - 1);
6990 switch (key) {
6991 case KEY_RETURN:
6992 case KEY_ENTER:
6993 case '\n':
6994 status = INPUT_STOP;
6995 break;
6997 case KEY_LEFT:
6998 case KEY_UP:
6999 *selected = *selected - 1;
7000 if (*selected < 0)
7001 *selected = size - 1;
7002 break;
7004 case KEY_RIGHT:
7005 case KEY_DOWN:
7006 *selected = (*selected + 1) % size;
7007 break;
7009 case KEY_ESC:
7010 status = INPUT_CANCEL;
7011 break;
7013 default:
7014 for (i = 0; items[i].text; i++)
7015 if (items[i].hotkey == key) {
7016 *selected = i;
7017 status = INPUT_STOP;
7018 break;
7019 }
7020 }
7021 }
7023 /* Clear the status window */
7024 status_empty = FALSE;
7025 report("");
7027 return status != INPUT_CANCEL;
7028 }
7030 /*
7031 * Repository properties
7032 */
7034 static struct ref **refs = NULL;
7035 static size_t refs_size = 0;
7037 static struct ref_list **ref_lists = NULL;
7038 static size_t ref_lists_size = 0;
7040 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7041 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7042 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7044 static int
7045 compare_refs(const void *ref1_, const void *ref2_)
7046 {
7047 const struct ref *ref1 = *(const struct ref **)ref1_;
7048 const struct ref *ref2 = *(const struct ref **)ref2_;
7050 if (ref1->tag != ref2->tag)
7051 return ref2->tag - ref1->tag;
7052 if (ref1->ltag != ref2->ltag)
7053 return ref2->ltag - ref2->ltag;
7054 if (ref1->head != ref2->head)
7055 return ref2->head - ref1->head;
7056 if (ref1->tracked != ref2->tracked)
7057 return ref2->tracked - ref1->tracked;
7058 if (ref1->remote != ref2->remote)
7059 return ref2->remote - ref1->remote;
7060 return strcmp(ref1->name, ref2->name);
7061 }
7063 static void
7064 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
7065 {
7066 size_t i;
7068 for (i = 0; i < refs_size; i++)
7069 if (!visitor(data, refs[i]))
7070 break;
7071 }
7073 static struct ref_list *
7074 get_ref_list(const char *id)
7075 {
7076 struct ref_list *list;
7077 size_t i;
7079 for (i = 0; i < ref_lists_size; i++)
7080 if (!strcmp(id, ref_lists[i]->id))
7081 return ref_lists[i];
7083 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7084 return NULL;
7085 list = calloc(1, sizeof(*list));
7086 if (!list)
7087 return NULL;
7089 for (i = 0; i < refs_size; i++) {
7090 if (!strcmp(id, refs[i]->id) &&
7091 realloc_refs_list(&list->refs, list->size, 1))
7092 list->refs[list->size++] = refs[i];
7093 }
7095 if (!list->refs) {
7096 free(list);
7097 return NULL;
7098 }
7100 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7101 ref_lists[ref_lists_size++] = list;
7102 return list;
7103 }
7105 static int
7106 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7107 {
7108 struct ref *ref = NULL;
7109 bool tag = FALSE;
7110 bool ltag = FALSE;
7111 bool remote = FALSE;
7112 bool tracked = FALSE;
7113 bool head = FALSE;
7114 int from = 0, to = refs_size - 1;
7116 if (!prefixcmp(name, "refs/tags/")) {
7117 if (!suffixcmp(name, namelen, "^{}")) {
7118 namelen -= 3;
7119 name[namelen] = 0;
7120 } else {
7121 ltag = TRUE;
7122 }
7124 tag = TRUE;
7125 namelen -= STRING_SIZE("refs/tags/");
7126 name += STRING_SIZE("refs/tags/");
7128 } else if (!prefixcmp(name, "refs/remotes/")) {
7129 remote = TRUE;
7130 namelen -= STRING_SIZE("refs/remotes/");
7131 name += STRING_SIZE("refs/remotes/");
7132 tracked = !strcmp(opt_remote, name);
7134 } else if (!prefixcmp(name, "refs/heads/")) {
7135 namelen -= STRING_SIZE("refs/heads/");
7136 name += STRING_SIZE("refs/heads/");
7137 head = !strncmp(opt_head, name, namelen);
7139 } else if (!strcmp(name, "HEAD")) {
7140 string_ncopy(opt_head_rev, id, idlen);
7141 return OK;
7142 }
7144 /* If we are reloading or it's an annotated tag, replace the
7145 * previous SHA1 with the resolved commit id; relies on the fact
7146 * git-ls-remote lists the commit id of an annotated tag right
7147 * before the commit id it points to. */
7148 while (from <= to) {
7149 size_t pos = (to + from) / 2;
7150 int cmp = strcmp(name, refs[pos]->name);
7152 if (!cmp) {
7153 ref = refs[pos];
7154 break;
7155 }
7157 if (cmp < 0)
7158 to = pos - 1;
7159 else
7160 from = pos + 1;
7161 }
7163 if (!ref) {
7164 if (!realloc_refs(&refs, refs_size, 1))
7165 return ERR;
7166 ref = calloc(1, sizeof(*ref) + namelen);
7167 if (!ref)
7168 return ERR;
7169 memmove(refs + from + 1, refs + from,
7170 (refs_size - from) * sizeof(*refs));
7171 refs[from] = ref;
7172 strncpy(ref->name, name, namelen);
7173 refs_size++;
7174 }
7176 ref->head = head;
7177 ref->tag = tag;
7178 ref->ltag = ltag;
7179 ref->remote = remote;
7180 ref->tracked = tracked;
7181 string_copy_rev(ref->id, id);
7183 return OK;
7184 }
7186 static int
7187 load_refs(void)
7188 {
7189 const char *head_argv[] = {
7190 "git", "symbolic-ref", "HEAD", NULL
7191 };
7192 static const char *ls_remote_argv[SIZEOF_ARG] = {
7193 "git", "ls-remote", opt_git_dir, NULL
7194 };
7195 static bool init = FALSE;
7196 size_t i;
7198 if (!init) {
7199 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7200 init = TRUE;
7201 }
7203 if (!*opt_git_dir)
7204 return OK;
7206 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7207 !prefixcmp(opt_head, "refs/heads/")) {
7208 char *offset = opt_head + STRING_SIZE("refs/heads/");
7210 memmove(opt_head, offset, strlen(offset) + 1);
7211 }
7213 for (i = 0; i < refs_size; i++)
7214 refs[i]->id[0] = 0;
7216 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7217 return ERR;
7219 /* Update the ref lists to reflect changes. */
7220 for (i = 0; i < ref_lists_size; i++) {
7221 struct ref_list *list = ref_lists[i];
7222 size_t old, new;
7224 for (old = new = 0; old < list->size; old++)
7225 if (!strcmp(list->id, list->refs[old]->id))
7226 list->refs[new++] = list->refs[old];
7227 list->size = new;
7228 }
7230 return OK;
7231 }
7233 static void
7234 set_remote_branch(const char *name, const char *value, size_t valuelen)
7235 {
7236 if (!strcmp(name, ".remote")) {
7237 string_ncopy(opt_remote, value, valuelen);
7239 } else if (*opt_remote && !strcmp(name, ".merge")) {
7240 size_t from = strlen(opt_remote);
7242 if (!prefixcmp(value, "refs/heads/"))
7243 value += STRING_SIZE("refs/heads/");
7245 if (!string_format_from(opt_remote, &from, "/%s", value))
7246 opt_remote[0] = 0;
7247 }
7248 }
7250 static void
7251 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7252 {
7253 const char *argv[SIZEOF_ARG] = { name, "=" };
7254 int argc = 1 + (cmd == option_set_command);
7255 int error = ERR;
7257 if (!argv_from_string(argv, &argc, value))
7258 config_msg = "Too many option arguments";
7259 else
7260 error = cmd(argc, argv);
7262 if (error == ERR)
7263 warn("Option 'tig.%s': %s", name, config_msg);
7264 }
7266 static bool
7267 set_environment_variable(const char *name, const char *value)
7268 {
7269 size_t len = strlen(name) + 1 + strlen(value) + 1;
7270 char *env = malloc(len);
7272 if (env &&
7273 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7274 putenv(env) == 0)
7275 return TRUE;
7276 free(env);
7277 return FALSE;
7278 }
7280 static void
7281 set_work_tree(const char *value)
7282 {
7283 char cwd[SIZEOF_STR];
7285 if (!getcwd(cwd, sizeof(cwd)))
7286 die("Failed to get cwd path: %s", strerror(errno));
7287 if (chdir(opt_git_dir) < 0)
7288 die("Failed to chdir(%s): %s", strerror(errno));
7289 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7290 die("Failed to get git path: %s", strerror(errno));
7291 if (chdir(cwd) < 0)
7292 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7293 if (chdir(value) < 0)
7294 die("Failed to chdir(%s): %s", value, strerror(errno));
7295 if (!getcwd(cwd, sizeof(cwd)))
7296 die("Failed to get cwd path: %s", strerror(errno));
7297 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7298 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7299 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7300 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7301 opt_is_inside_work_tree = TRUE;
7302 }
7304 static int
7305 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7306 {
7307 if (!strcmp(name, "i18n.commitencoding"))
7308 string_ncopy(opt_encoding, value, valuelen);
7310 else if (!strcmp(name, "core.editor"))
7311 string_ncopy(opt_editor, value, valuelen);
7313 else if (!strcmp(name, "core.worktree"))
7314 set_work_tree(value);
7316 else if (!prefixcmp(name, "tig.color."))
7317 set_repo_config_option(name + 10, value, option_color_command);
7319 else if (!prefixcmp(name, "tig.bind."))
7320 set_repo_config_option(name + 9, value, option_bind_command);
7322 else if (!prefixcmp(name, "tig."))
7323 set_repo_config_option(name + 4, value, option_set_command);
7325 else if (*opt_head && !prefixcmp(name, "branch.") &&
7326 !strncmp(name + 7, opt_head, strlen(opt_head)))
7327 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7329 return OK;
7330 }
7332 static int
7333 load_git_config(void)
7334 {
7335 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7337 return run_io_load(config_list_argv, "=", read_repo_config_option);
7338 }
7340 static int
7341 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7342 {
7343 if (!opt_git_dir[0]) {
7344 string_ncopy(opt_git_dir, name, namelen);
7346 } else if (opt_is_inside_work_tree == -1) {
7347 /* This can be 3 different values depending on the
7348 * version of git being used. If git-rev-parse does not
7349 * understand --is-inside-work-tree it will simply echo
7350 * the option else either "true" or "false" is printed.
7351 * Default to true for the unknown case. */
7352 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7354 } else if (*name == '.') {
7355 string_ncopy(opt_cdup, name, namelen);
7357 } else {
7358 string_ncopy(opt_prefix, name, namelen);
7359 }
7361 return OK;
7362 }
7364 static int
7365 load_repo_info(void)
7366 {
7367 const char *rev_parse_argv[] = {
7368 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7369 "--show-cdup", "--show-prefix", NULL
7370 };
7372 return run_io_load(rev_parse_argv, "=", read_repo_info);
7373 }
7376 /*
7377 * Main
7378 */
7380 static const char usage[] =
7381 "tig " TIG_VERSION " (" __DATE__ ")\n"
7382 "\n"
7383 "Usage: tig [options] [revs] [--] [paths]\n"
7384 " or: tig show [options] [revs] [--] [paths]\n"
7385 " or: tig blame [rev] path\n"
7386 " or: tig status\n"
7387 " or: tig < [git command output]\n"
7388 "\n"
7389 "Options:\n"
7390 " -v, --version Show version and exit\n"
7391 " -h, --help Show help message and exit";
7393 static void __NORETURN
7394 quit(int sig)
7395 {
7396 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7397 if (cursed)
7398 endwin();
7399 exit(0);
7400 }
7402 static void __NORETURN
7403 die(const char *err, ...)
7404 {
7405 va_list args;
7407 endwin();
7409 va_start(args, err);
7410 fputs("tig: ", stderr);
7411 vfprintf(stderr, err, args);
7412 fputs("\n", stderr);
7413 va_end(args);
7415 exit(1);
7416 }
7418 static void
7419 warn(const char *msg, ...)
7420 {
7421 va_list args;
7423 va_start(args, msg);
7424 fputs("tig warning: ", stderr);
7425 vfprintf(stderr, msg, args);
7426 fputs("\n", stderr);
7427 va_end(args);
7428 }
7430 static enum request
7431 parse_options(int argc, const char *argv[])
7432 {
7433 enum request request = REQ_VIEW_MAIN;
7434 const char *subcommand;
7435 bool seen_dashdash = FALSE;
7436 /* XXX: This is vulnerable to the user overriding options
7437 * required for the main view parser. */
7438 const char *custom_argv[SIZEOF_ARG] = {
7439 "git", "log", "--no-color", "--pretty=raw", "--parents",
7440 "--topo-order", NULL
7441 };
7442 int i, j = 6;
7444 if (!isatty(STDIN_FILENO)) {
7445 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7446 return REQ_VIEW_PAGER;
7447 }
7449 if (argc <= 1)
7450 return REQ_NONE;
7452 subcommand = argv[1];
7453 if (!strcmp(subcommand, "status")) {
7454 if (argc > 2)
7455 warn("ignoring arguments after `%s'", subcommand);
7456 return REQ_VIEW_STATUS;
7458 } else if (!strcmp(subcommand, "blame")) {
7459 if (argc <= 2 || argc > 4)
7460 die("invalid number of options to blame\n\n%s", usage);
7462 i = 2;
7463 if (argc == 4) {
7464 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7465 i++;
7466 }
7468 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7469 return REQ_VIEW_BLAME;
7471 } else if (!strcmp(subcommand, "show")) {
7472 request = REQ_VIEW_DIFF;
7474 } else {
7475 subcommand = NULL;
7476 }
7478 if (subcommand) {
7479 custom_argv[1] = subcommand;
7480 j = 2;
7481 }
7483 for (i = 1 + !!subcommand; i < argc; i++) {
7484 const char *opt = argv[i];
7486 if (seen_dashdash || !strcmp(opt, "--")) {
7487 seen_dashdash = TRUE;
7489 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7490 printf("tig version %s\n", TIG_VERSION);
7491 quit(0);
7493 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7494 printf("%s\n", usage);
7495 quit(0);
7496 }
7498 custom_argv[j++] = opt;
7499 if (j >= ARRAY_SIZE(custom_argv))
7500 die("command too long");
7501 }
7503 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7504 die("Failed to format arguments");
7506 return request;
7507 }
7509 int
7510 main(int argc, const char *argv[])
7511 {
7512 enum request request = parse_options(argc, argv);
7513 struct view *view;
7514 size_t i;
7516 signal(SIGINT, quit);
7517 signal(SIGPIPE, SIG_IGN);
7519 if (setlocale(LC_ALL, "")) {
7520 char *codeset = nl_langinfo(CODESET);
7522 string_ncopy(opt_codeset, codeset, strlen(codeset));
7523 }
7525 if (load_repo_info() == ERR)
7526 die("Failed to load repo info.");
7528 if (load_options() == ERR)
7529 die("Failed to load user config.");
7531 if (load_git_config() == ERR)
7532 die("Failed to load repo config.");
7534 /* Require a git repository unless when running in pager mode. */
7535 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7536 die("Not a git repository");
7538 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7539 opt_utf8 = FALSE;
7541 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7542 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7543 if (opt_iconv == ICONV_NONE)
7544 die("Failed to initialize character set conversion");
7545 }
7547 if (load_refs() == ERR)
7548 die("Failed to load refs.");
7550 foreach_view (view, i)
7551 argv_from_env(view->ops->argv, view->cmd_env);
7553 init_display();
7555 if (request != REQ_NONE)
7556 open_view(NULL, request, OPEN_PREPARED);
7557 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7559 while (view_driver(display[current_view], request)) {
7560 int key = get_input(0);
7562 view = display[current_view];
7563 request = get_keybinding(view->keymap, key);
7565 /* Some low-level request handling. This keeps access to
7566 * status_win restricted. */
7567 switch (request) {
7568 case REQ_PROMPT:
7569 {
7570 char *cmd = read_prompt(":");
7572 if (cmd && isdigit(*cmd)) {
7573 int lineno = view->lineno + 1;
7575 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7576 select_view_line(view, lineno - 1);
7577 report("");
7578 } else {
7579 report("Unable to parse '%s' as a line number", cmd);
7580 }
7582 } else if (cmd) {
7583 struct view *next = VIEW(REQ_VIEW_PAGER);
7584 const char *argv[SIZEOF_ARG] = { "git" };
7585 int argc = 1;
7587 /* When running random commands, initially show the
7588 * command in the title. However, it maybe later be
7589 * overwritten if a commit line is selected. */
7590 string_ncopy(next->ref, cmd, strlen(cmd));
7592 if (!argv_from_string(argv, &argc, cmd)) {
7593 report("Too many arguments");
7594 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7595 report("Failed to format command");
7596 } else {
7597 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7598 }
7599 }
7601 request = REQ_NONE;
7602 break;
7603 }
7604 case REQ_SEARCH:
7605 case REQ_SEARCH_BACK:
7606 {
7607 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7608 char *search = read_prompt(prompt);
7610 if (search)
7611 string_ncopy(opt_search, search, strlen(search));
7612 else if (*opt_search)
7613 request = request == REQ_SEARCH ?
7614 REQ_FIND_NEXT :
7615 REQ_FIND_PREV;
7616 else
7617 request = REQ_NONE;
7618 break;
7619 }
7620 default:
7621 break;
7622 }
7623 }
7625 quit(0);
7627 return 0;
7628 }