cffa86d5fcafdd22544a19e7283f5e1f3241749d
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 ")
107 #define ID_COLS 8
109 #define MIN_VIEW_HEIGHT 4
111 #define NULL_ID "0000000000000000000000000000000000000000"
113 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
115 #ifndef GIT_CONFIG
116 #define GIT_CONFIG "config"
117 #endif
119 /* Some ASCII-shorthands fitted into the ncurses namespace. */
120 #define KEY_TAB '\t'
121 #define KEY_RETURN '\r'
122 #define KEY_ESC 27
125 struct ref {
126 char id[SIZEOF_REV]; /* Commit SHA1 ID */
127 unsigned int head:1; /* Is it the current HEAD? */
128 unsigned int tag:1; /* Is it a tag? */
129 unsigned int ltag:1; /* If so, is the tag local? */
130 unsigned int remote:1; /* Is it a remote ref? */
131 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
132 char name[1]; /* Ref name; tag or head names are shortened. */
133 };
135 struct ref_list {
136 char id[SIZEOF_REV]; /* Commit SHA1 ID */
137 size_t size; /* Number of refs. */
138 struct ref **refs; /* References for this ID. */
139 };
141 static struct ref_list *get_ref_list(const char *id);
142 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
143 static int load_refs(void);
145 enum format_flags {
146 FORMAT_ALL, /* Perform replacement in all arguments. */
147 FORMAT_DASH, /* Perform replacement up until "--". */
148 FORMAT_NONE /* No replacement should be performed. */
149 };
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154 INPUT_OK,
155 INPUT_SKIP,
156 INPUT_STOP,
157 INPUT_CANCEL
158 };
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 struct menu_item {
166 int hotkey;
167 const char *text;
168 void *data;
169 };
171 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
173 /*
174 * Allocation helpers ... Entering macro hell to never be seen again.
175 */
177 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
178 static type * \
179 name(type **mem, size_t size, size_t increase) \
180 { \
181 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
182 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183 type *tmp = *mem; \
184 \
185 if (mem == NULL || num_chunks != num_chunks_new) { \
186 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
187 if (tmp) \
188 *mem = tmp; \
189 } \
190 \
191 return tmp; \
192 }
194 /*
195 * String helpers
196 */
198 static inline void
199 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
200 {
201 if (srclen > dstlen - 1)
202 srclen = dstlen - 1;
204 strncpy(dst, src, srclen);
205 dst[srclen] = 0;
206 }
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214 string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
222 static void
223 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
224 {
225 size_t size, pos;
227 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
228 if (src[pos] == '\t') {
229 size_t expanded = tabsize - (size % tabsize);
231 if (expanded + size >= dstlen - 1)
232 expanded = dstlen - size - 1;
233 memcpy(dst + size, " ", expanded);
234 size += expanded;
235 } else {
236 dst[size++] = src[pos];
237 }
238 }
240 dst[size] = 0;
241 }
243 static char *
244 chomp_string(char *name)
245 {
246 int namelen;
248 while (isspace(*name))
249 name++;
251 namelen = strlen(name) - 1;
252 while (namelen > 0 && isspace(name[namelen]))
253 name[namelen--] = 0;
255 return name;
256 }
258 static bool
259 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
260 {
261 va_list args;
262 size_t pos = bufpos ? *bufpos : 0;
264 va_start(args, fmt);
265 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
266 va_end(args);
268 if (bufpos)
269 *bufpos = pos;
271 return pos >= bufsize ? FALSE : TRUE;
272 }
274 #define string_format(buf, fmt, args...) \
275 string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278 string_nformat(buf, sizeof(buf), from, fmt, args)
280 static int
281 string_enum_compare(const char *str1, const char *str2, int len)
282 {
283 size_t i;
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287 /* Diff-Header == DIFF_HEADER */
288 for (i = 0; i < len; i++) {
289 if (toupper(str1[i]) == toupper(str2[i]))
290 continue;
292 if (string_enum_sep(str1[i]) &&
293 string_enum_sep(str2[i]))
294 continue;
296 return str1[i] - str2[i];
297 }
299 return 0;
300 }
302 struct enum_map {
303 const char *name;
304 int namelen;
305 int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static bool
311 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
312 {
313 size_t namelen = strlen(name);
314 int i;
316 for (i = 0; i < map_size; i++)
317 if (namelen == map[i].namelen &&
318 !string_enum_compare(name, map[i].name, namelen)) {
319 *value = map[i].value;
320 return TRUE;
321 }
323 return FALSE;
324 }
326 #define map_enum(attr, map, name) \
327 map_enum_do(map, ARRAY_SIZE(map), attr, name)
329 #define prefixcmp(str1, str2) \
330 strncmp(str1, str2, STRING_SIZE(str2))
332 static inline int
333 suffixcmp(const char *str, int slen, const char *suffix)
334 {
335 size_t len = slen >= 0 ? slen : strlen(str);
336 size_t suffixlen = strlen(suffix);
338 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
339 }
342 static const char *
343 mkdate(const time_t *time)
344 {
345 static char buf[DATE_COLS + 1];
346 struct tm tm;
348 gmtime_r(time, &tm);
349 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
350 }
353 static bool
354 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
355 {
356 int valuelen;
358 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
359 bool advance = cmd[valuelen] != 0;
361 cmd[valuelen] = 0;
362 argv[(*argc)++] = chomp_string(cmd);
363 cmd = chomp_string(cmd + valuelen + advance);
364 }
366 if (*argc < SIZEOF_ARG)
367 argv[*argc] = NULL;
368 return *argc < SIZEOF_ARG;
369 }
371 static void
372 argv_from_env(const char **argv, const char *name)
373 {
374 char *env = argv ? getenv(name) : NULL;
375 int argc = 0;
377 if (env && *env)
378 env = strdup(env);
379 if (env && !argv_from_string(argv, &argc, env))
380 die("Too many arguments in the `%s` environment variable", name);
381 }
384 /*
385 * Executing external commands.
386 */
388 enum io_type {
389 IO_FD, /* File descriptor based IO. */
390 IO_BG, /* Execute command in the background. */
391 IO_FG, /* Execute command with same std{in,out,err}. */
392 IO_RD, /* Read only fork+exec IO. */
393 IO_WR, /* Write only fork+exec IO. */
394 IO_AP, /* Append fork+exec output to file. */
395 };
397 struct io {
398 enum io_type type; /* The requested type of pipe. */
399 const char *dir; /* Directory from which to execute. */
400 pid_t pid; /* Pipe for reading or writing. */
401 int pipe; /* Pipe end for reading or writing. */
402 int error; /* Error status. */
403 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
404 char *buf; /* Read buffer. */
405 size_t bufalloc; /* Allocated buffer size. */
406 size_t bufsize; /* Buffer content size. */
407 char *bufpos; /* Current buffer position. */
408 unsigned int eof:1; /* Has end of file been reached. */
409 };
411 static void
412 reset_io(struct io *io)
413 {
414 io->pipe = -1;
415 io->pid = 0;
416 io->buf = io->bufpos = NULL;
417 io->bufalloc = io->bufsize = 0;
418 io->error = 0;
419 io->eof = 0;
420 }
422 static void
423 init_io(struct io *io, const char *dir, enum io_type type)
424 {
425 reset_io(io);
426 io->type = type;
427 io->dir = dir;
428 }
430 static bool
431 init_io_rd(struct io *io, const char *argv[], const char *dir,
432 enum format_flags flags)
433 {
434 init_io(io, dir, IO_RD);
435 return format_argv(io->argv, argv, flags);
436 }
438 static bool
439 io_open(struct io *io, const char *name)
440 {
441 init_io(io, NULL, IO_FD);
442 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
443 if (io->pipe == -1)
444 io->error = errno;
445 return io->pipe != -1;
446 }
448 static bool
449 kill_io(struct io *io)
450 {
451 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
452 }
454 static bool
455 done_io(struct io *io)
456 {
457 pid_t pid = io->pid;
459 if (io->pipe != -1)
460 close(io->pipe);
461 free(io->buf);
462 reset_io(io);
464 while (pid > 0) {
465 int status;
466 pid_t waiting = waitpid(pid, &status, 0);
468 if (waiting < 0) {
469 if (errno == EINTR)
470 continue;
471 report("waitpid failed (%s)", strerror(errno));
472 return FALSE;
473 }
475 return waiting == pid &&
476 !WIFSIGNALED(status) &&
477 WIFEXITED(status) &&
478 !WEXITSTATUS(status);
479 }
481 return TRUE;
482 }
484 static bool
485 start_io(struct io *io)
486 {
487 int pipefds[2] = { -1, -1 };
489 if (io->type == IO_FD)
490 return TRUE;
492 if ((io->type == IO_RD || io->type == IO_WR) &&
493 pipe(pipefds) < 0)
494 return FALSE;
495 else if (io->type == IO_AP)
496 pipefds[1] = io->pipe;
498 if ((io->pid = fork())) {
499 if (pipefds[!(io->type == IO_WR)] != -1)
500 close(pipefds[!(io->type == IO_WR)]);
501 if (io->pid != -1) {
502 io->pipe = pipefds[!!(io->type == IO_WR)];
503 return TRUE;
504 }
506 } else {
507 if (io->type != IO_FG) {
508 int devnull = open("/dev/null", O_RDWR);
509 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
510 int writefd = (io->type == IO_RD || io->type == IO_AP)
511 ? pipefds[1] : devnull;
513 dup2(readfd, STDIN_FILENO);
514 dup2(writefd, STDOUT_FILENO);
515 dup2(devnull, STDERR_FILENO);
517 close(devnull);
518 if (pipefds[0] != -1)
519 close(pipefds[0]);
520 if (pipefds[1] != -1)
521 close(pipefds[1]);
522 }
524 if (io->dir && *io->dir && chdir(io->dir) == -1)
525 die("Failed to change directory: %s", strerror(errno));
527 execvp(io->argv[0], (char *const*) io->argv);
528 die("Failed to execute program: %s", strerror(errno));
529 }
531 if (pipefds[!!(io->type == IO_WR)] != -1)
532 close(pipefds[!!(io->type == IO_WR)]);
533 return FALSE;
534 }
536 static bool
537 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
538 {
539 init_io(io, dir, type);
540 if (!format_argv(io->argv, argv, FORMAT_NONE))
541 return FALSE;
542 return start_io(io);
543 }
545 static int
546 run_io_do(struct io *io)
547 {
548 return start_io(io) && done_io(io);
549 }
551 static int
552 run_io_bg(const char **argv)
553 {
554 struct io io = {};
556 init_io(&io, NULL, IO_BG);
557 if (!format_argv(io.argv, argv, FORMAT_NONE))
558 return FALSE;
559 return run_io_do(&io);
560 }
562 static bool
563 run_io_fg(const char **argv, const char *dir)
564 {
565 struct io io = {};
567 init_io(&io, dir, IO_FG);
568 if (!format_argv(io.argv, argv, FORMAT_NONE))
569 return FALSE;
570 return run_io_do(&io);
571 }
573 static bool
574 run_io_append(const char **argv, enum format_flags flags, int fd)
575 {
576 struct io io = {};
578 init_io(&io, NULL, IO_AP);
579 io.pipe = fd;
580 if (format_argv(io.argv, argv, flags))
581 return run_io_do(&io);
582 close(fd);
583 return FALSE;
584 }
586 static bool
587 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
588 {
589 return init_io_rd(io, argv, NULL, flags) && start_io(io);
590 }
592 static bool
593 io_eof(struct io *io)
594 {
595 return io->eof;
596 }
598 static int
599 io_error(struct io *io)
600 {
601 return io->error;
602 }
604 static char *
605 io_strerror(struct io *io)
606 {
607 return strerror(io->error);
608 }
610 static bool
611 io_can_read(struct io *io)
612 {
613 struct timeval tv = { 0, 500 };
614 fd_set fds;
616 FD_ZERO(&fds);
617 FD_SET(io->pipe, &fds);
619 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
620 }
622 static ssize_t
623 io_read(struct io *io, void *buf, size_t bufsize)
624 {
625 do {
626 ssize_t readsize = read(io->pipe, buf, bufsize);
628 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
629 continue;
630 else if (readsize == -1)
631 io->error = errno;
632 else if (readsize == 0)
633 io->eof = 1;
634 return readsize;
635 } while (1);
636 }
638 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
640 static char *
641 io_get(struct io *io, int c, bool can_read)
642 {
643 char *eol;
644 ssize_t readsize;
646 while (TRUE) {
647 if (io->bufsize > 0) {
648 eol = memchr(io->bufpos, c, io->bufsize);
649 if (eol) {
650 char *line = io->bufpos;
652 *eol = 0;
653 io->bufpos = eol + 1;
654 io->bufsize -= io->bufpos - line;
655 return line;
656 }
657 }
659 if (io_eof(io)) {
660 if (io->bufsize) {
661 io->bufpos[io->bufsize] = 0;
662 io->bufsize = 0;
663 return io->bufpos;
664 }
665 return NULL;
666 }
668 if (!can_read)
669 return NULL;
671 if (io->bufsize > 0 && io->bufpos > io->buf)
672 memmove(io->buf, io->bufpos, io->bufsize);
674 if (io->bufalloc == io->bufsize) {
675 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
676 return NULL;
677 io->bufalloc += BUFSIZ;
678 }
680 io->bufpos = io->buf;
681 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
682 if (io_error(io))
683 return NULL;
684 io->bufsize += readsize;
685 }
686 }
688 static bool
689 io_write(struct io *io, const void *buf, size_t bufsize)
690 {
691 size_t written = 0;
693 while (!io_error(io) && written < bufsize) {
694 ssize_t size;
696 size = write(io->pipe, buf + written, bufsize - written);
697 if (size < 0 && (errno == EAGAIN || errno == EINTR))
698 continue;
699 else if (size == -1)
700 io->error = errno;
701 else
702 written += size;
703 }
705 return written == bufsize;
706 }
708 static bool
709 io_read_buf(struct io *io, char buf[], size_t bufsize)
710 {
711 char *result = io_get(io, '\n', TRUE);
713 if (result) {
714 result = chomp_string(result);
715 string_ncopy_do(buf, bufsize, result, strlen(result));
716 }
718 return done_io(io) && result;
719 }
721 static bool
722 run_io_buf(const char **argv, char buf[], size_t bufsize)
723 {
724 struct io io = {};
726 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
727 }
729 static int
730 io_load(struct io *io, const char *separators,
731 int (*read_property)(char *, size_t, char *, size_t))
732 {
733 char *name;
734 int state = OK;
736 if (!start_io(io))
737 return ERR;
739 while (state == OK && (name = io_get(io, '\n', TRUE))) {
740 char *value;
741 size_t namelen;
742 size_t valuelen;
744 name = chomp_string(name);
745 namelen = strcspn(name, separators);
747 if (name[namelen]) {
748 name[namelen] = 0;
749 value = chomp_string(name + namelen + 1);
750 valuelen = strlen(value);
752 } else {
753 value = "";
754 valuelen = 0;
755 }
757 state = read_property(name, namelen, value, valuelen);
758 }
760 if (state != ERR && io_error(io))
761 state = ERR;
762 done_io(io);
764 return state;
765 }
767 static int
768 run_io_load(const char **argv, const char *separators,
769 int (*read_property)(char *, size_t, char *, size_t))
770 {
771 struct io io = {};
773 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
774 ? io_load(&io, separators, read_property) : ERR;
775 }
778 /*
779 * User requests
780 */
782 #define REQ_INFO \
783 /* XXX: Keep the view request first and in sync with views[]. */ \
784 REQ_GROUP("View switching") \
785 REQ_(VIEW_MAIN, "Show main view"), \
786 REQ_(VIEW_DIFF, "Show diff view"), \
787 REQ_(VIEW_LOG, "Show log view"), \
788 REQ_(VIEW_TREE, "Show tree view"), \
789 REQ_(VIEW_BLOB, "Show blob view"), \
790 REQ_(VIEW_BLAME, "Show blame view"), \
791 REQ_(VIEW_BRANCH, "Show branch view"), \
792 REQ_(VIEW_HELP, "Show help page"), \
793 REQ_(VIEW_PAGER, "Show pager view"), \
794 REQ_(VIEW_STATUS, "Show status view"), \
795 REQ_(VIEW_STAGE, "Show stage view"), \
796 \
797 REQ_GROUP("View manipulation") \
798 REQ_(ENTER, "Enter current line and scroll"), \
799 REQ_(NEXT, "Move to next"), \
800 REQ_(PREVIOUS, "Move to previous"), \
801 REQ_(PARENT, "Move to parent"), \
802 REQ_(VIEW_NEXT, "Move focus to next view"), \
803 REQ_(REFRESH, "Reload and refresh"), \
804 REQ_(MAXIMIZE, "Maximize the current view"), \
805 REQ_(VIEW_CLOSE, "Close the current view"), \
806 REQ_(QUIT, "Close all views and quit"), \
807 \
808 REQ_GROUP("View specific requests") \
809 REQ_(STATUS_UPDATE, "Update file status"), \
810 REQ_(STATUS_REVERT, "Revert file changes"), \
811 REQ_(STATUS_MERGE, "Merge file using external tool"), \
812 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
813 \
814 REQ_GROUP("Cursor navigation") \
815 REQ_(MOVE_UP, "Move cursor one line up"), \
816 REQ_(MOVE_DOWN, "Move cursor one line down"), \
817 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
818 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
819 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
820 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
821 \
822 REQ_GROUP("Scrolling") \
823 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
824 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
825 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
826 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
827 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
828 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
829 \
830 REQ_GROUP("Searching") \
831 REQ_(SEARCH, "Search the view"), \
832 REQ_(SEARCH_BACK, "Search backwards in the view"), \
833 REQ_(FIND_NEXT, "Find next search match"), \
834 REQ_(FIND_PREV, "Find previous search match"), \
835 \
836 REQ_GROUP("Option manipulation") \
837 REQ_(OPTIONS, "Open option menu"), \
838 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
839 REQ_(TOGGLE_DATE, "Toggle date display"), \
840 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
841 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
842 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
843 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
844 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
845 \
846 REQ_GROUP("Misc") \
847 REQ_(PROMPT, "Bring up the prompt"), \
848 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
849 REQ_(SHOW_VERSION, "Show version information"), \
850 REQ_(STOP_LOADING, "Stop all loading views"), \
851 REQ_(EDIT, "Open in editor"), \
852 REQ_(NONE, "Do nothing")
855 /* User action requests. */
856 enum request {
857 #define REQ_GROUP(help)
858 #define REQ_(req, help) REQ_##req
860 /* Offset all requests to avoid conflicts with ncurses getch values. */
861 REQ_OFFSET = KEY_MAX + 1,
862 REQ_INFO
864 #undef REQ_GROUP
865 #undef REQ_
866 };
868 struct request_info {
869 enum request request;
870 const char *name;
871 int namelen;
872 const char *help;
873 };
875 static const struct request_info req_info[] = {
876 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
877 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
878 REQ_INFO
879 #undef REQ_GROUP
880 #undef REQ_
881 };
883 static enum request
884 get_request(const char *name)
885 {
886 int namelen = strlen(name);
887 int i;
889 for (i = 0; i < ARRAY_SIZE(req_info); i++)
890 if (req_info[i].namelen == namelen &&
891 !string_enum_compare(req_info[i].name, name, namelen))
892 return req_info[i].request;
894 return REQ_NONE;
895 }
898 /*
899 * Options
900 */
902 /* Option and state variables. */
903 static bool opt_date = TRUE;
904 static bool opt_author = TRUE;
905 static bool opt_line_number = FALSE;
906 static bool opt_line_graphics = TRUE;
907 static bool opt_rev_graph = FALSE;
908 static bool opt_show_refs = TRUE;
909 static int opt_num_interval = 5;
910 static double opt_hscroll = 0.50;
911 static double opt_scale_split_view = 2.0 / 3.0;
912 static int opt_tab_size = 8;
913 static int opt_author_cols = 19;
914 static char opt_path[SIZEOF_STR] = "";
915 static char opt_file[SIZEOF_STR] = "";
916 static char opt_ref[SIZEOF_REF] = "";
917 static char opt_head[SIZEOF_REF] = "";
918 static char opt_head_rev[SIZEOF_REV] = "";
919 static char opt_remote[SIZEOF_REF] = "";
920 static char opt_encoding[20] = "UTF-8";
921 static bool opt_utf8 = TRUE;
922 static char opt_codeset[20] = "UTF-8";
923 static iconv_t opt_iconv = ICONV_NONE;
924 static char opt_search[SIZEOF_STR] = "";
925 static char opt_cdup[SIZEOF_STR] = "";
926 static char opt_prefix[SIZEOF_STR] = "";
927 static char opt_git_dir[SIZEOF_STR] = "";
928 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
929 static char opt_editor[SIZEOF_STR] = "";
930 static FILE *opt_tty = NULL;
932 #define is_initial_commit() (!*opt_head_rev)
933 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
936 /*
937 * Line-oriented content detection.
938 */
940 #define LINE_INFO \
941 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
943 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
944 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
945 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
946 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
947 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
948 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
950 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
951 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
952 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
953 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
954 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
955 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
956 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
957 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
958 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
959 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
960 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
961 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
962 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
963 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
964 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
965 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
966 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
967 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
968 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
969 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
970 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
971 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
972 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
973 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
974 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
975 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
976 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
977 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
978 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
979 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
980 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
981 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
982 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
983 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
984 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
985 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
986 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
987 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
988 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
989 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
990 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
991 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
992 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
993 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
994 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
995 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
997 enum line_type {
998 #define LINE(type, line, fg, bg, attr) \
999 LINE_##type
1000 LINE_INFO,
1001 LINE_NONE
1002 #undef LINE
1003 };
1005 struct line_info {
1006 const char *name; /* Option name. */
1007 int namelen; /* Size of option name. */
1008 const char *line; /* The start of line to match. */
1009 int linelen; /* Size of string to match. */
1010 int fg, bg, attr; /* Color and text attributes for the lines. */
1011 };
1013 static struct line_info line_info[] = {
1014 #define LINE(type, line, fg, bg, attr) \
1015 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1016 LINE_INFO
1017 #undef LINE
1018 };
1020 static enum line_type
1021 get_line_type(const char *line)
1022 {
1023 int linelen = strlen(line);
1024 enum line_type type;
1026 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1027 /* Case insensitive search matches Signed-off-by lines better. */
1028 if (linelen >= line_info[type].linelen &&
1029 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1030 return type;
1032 return LINE_DEFAULT;
1033 }
1035 static inline int
1036 get_line_attr(enum line_type type)
1037 {
1038 assert(type < ARRAY_SIZE(line_info));
1039 return COLOR_PAIR(type) | line_info[type].attr;
1040 }
1042 static struct line_info *
1043 get_line_info(const char *name)
1044 {
1045 size_t namelen = strlen(name);
1046 enum line_type type;
1048 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1049 if (namelen == line_info[type].namelen &&
1050 !string_enum_compare(line_info[type].name, name, namelen))
1051 return &line_info[type];
1053 return NULL;
1054 }
1056 static void
1057 init_colors(void)
1058 {
1059 int default_bg = line_info[LINE_DEFAULT].bg;
1060 int default_fg = line_info[LINE_DEFAULT].fg;
1061 enum line_type type;
1063 start_color();
1065 if (assume_default_colors(default_fg, default_bg) == ERR) {
1066 default_bg = COLOR_BLACK;
1067 default_fg = COLOR_WHITE;
1068 }
1070 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1071 struct line_info *info = &line_info[type];
1072 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1073 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1075 init_pair(type, fg, bg);
1076 }
1077 }
1079 struct line {
1080 enum line_type type;
1082 /* State flags */
1083 unsigned int selected:1;
1084 unsigned int dirty:1;
1085 unsigned int cleareol:1;
1087 void *data; /* User data */
1088 };
1091 /*
1092 * Keys
1093 */
1095 struct keybinding {
1096 int alias;
1097 enum request request;
1098 };
1100 static const struct keybinding default_keybindings[] = {
1101 /* View switching */
1102 { 'm', REQ_VIEW_MAIN },
1103 { 'd', REQ_VIEW_DIFF },
1104 { 'l', REQ_VIEW_LOG },
1105 { 't', REQ_VIEW_TREE },
1106 { 'f', REQ_VIEW_BLOB },
1107 { 'B', REQ_VIEW_BLAME },
1108 { 'H', REQ_VIEW_BRANCH },
1109 { 'p', REQ_VIEW_PAGER },
1110 { 'h', REQ_VIEW_HELP },
1111 { 'S', REQ_VIEW_STATUS },
1112 { 'c', REQ_VIEW_STAGE },
1114 /* View manipulation */
1115 { 'q', REQ_VIEW_CLOSE },
1116 { KEY_TAB, REQ_VIEW_NEXT },
1117 { KEY_RETURN, REQ_ENTER },
1118 { KEY_UP, REQ_PREVIOUS },
1119 { KEY_DOWN, REQ_NEXT },
1120 { 'R', REQ_REFRESH },
1121 { KEY_F(5), REQ_REFRESH },
1122 { 'O', REQ_MAXIMIZE },
1124 /* Cursor navigation */
1125 { 'k', REQ_MOVE_UP },
1126 { 'j', REQ_MOVE_DOWN },
1127 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1128 { KEY_END, REQ_MOVE_LAST_LINE },
1129 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1130 { ' ', REQ_MOVE_PAGE_DOWN },
1131 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1132 { 'b', REQ_MOVE_PAGE_UP },
1133 { '-', REQ_MOVE_PAGE_UP },
1135 /* Scrolling */
1136 { KEY_LEFT, REQ_SCROLL_LEFT },
1137 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1138 { KEY_IC, REQ_SCROLL_LINE_UP },
1139 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1140 { 'w', REQ_SCROLL_PAGE_UP },
1141 { 's', REQ_SCROLL_PAGE_DOWN },
1143 /* Searching */
1144 { '/', REQ_SEARCH },
1145 { '?', REQ_SEARCH_BACK },
1146 { 'n', REQ_FIND_NEXT },
1147 { 'N', REQ_FIND_PREV },
1149 /* Misc */
1150 { 'Q', REQ_QUIT },
1151 { 'z', REQ_STOP_LOADING },
1152 { 'v', REQ_SHOW_VERSION },
1153 { 'r', REQ_SCREEN_REDRAW },
1154 { 'o', REQ_OPTIONS },
1155 { '.', REQ_TOGGLE_LINENO },
1156 { 'D', REQ_TOGGLE_DATE },
1157 { 'A', REQ_TOGGLE_AUTHOR },
1158 { 'g', REQ_TOGGLE_REV_GRAPH },
1159 { 'F', REQ_TOGGLE_REFS },
1160 { 'I', REQ_TOGGLE_SORT_ORDER },
1161 { 'i', REQ_TOGGLE_SORT_FIELD },
1162 { ':', REQ_PROMPT },
1163 { 'u', REQ_STATUS_UPDATE },
1164 { '!', REQ_STATUS_REVERT },
1165 { 'M', REQ_STATUS_MERGE },
1166 { '@', REQ_STAGE_NEXT },
1167 { ',', REQ_PARENT },
1168 { 'e', REQ_EDIT },
1169 };
1171 #define KEYMAP_INFO \
1172 KEYMAP_(GENERIC), \
1173 KEYMAP_(MAIN), \
1174 KEYMAP_(DIFF), \
1175 KEYMAP_(LOG), \
1176 KEYMAP_(TREE), \
1177 KEYMAP_(BLOB), \
1178 KEYMAP_(BLAME), \
1179 KEYMAP_(BRANCH), \
1180 KEYMAP_(PAGER), \
1181 KEYMAP_(HELP), \
1182 KEYMAP_(STATUS), \
1183 KEYMAP_(STAGE)
1185 enum keymap {
1186 #define KEYMAP_(name) KEYMAP_##name
1187 KEYMAP_INFO
1188 #undef KEYMAP_
1189 };
1191 static const struct enum_map keymap_table[] = {
1192 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1193 KEYMAP_INFO
1194 #undef KEYMAP_
1195 };
1197 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1199 struct keybinding_table {
1200 struct keybinding *data;
1201 size_t size;
1202 };
1204 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1206 static void
1207 add_keybinding(enum keymap keymap, enum request request, int key)
1208 {
1209 struct keybinding_table *table = &keybindings[keymap];
1211 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1212 if (!table->data)
1213 die("Failed to allocate keybinding");
1214 table->data[table->size].alias = key;
1215 table->data[table->size++].request = request;
1216 }
1218 /* Looks for a key binding first in the given map, then in the generic map, and
1219 * lastly in the default keybindings. */
1220 static enum request
1221 get_keybinding(enum keymap keymap, int key)
1222 {
1223 size_t i;
1225 for (i = 0; i < keybindings[keymap].size; i++)
1226 if (keybindings[keymap].data[i].alias == key)
1227 return keybindings[keymap].data[i].request;
1229 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1230 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1231 return keybindings[KEYMAP_GENERIC].data[i].request;
1233 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1234 if (default_keybindings[i].alias == key)
1235 return default_keybindings[i].request;
1237 return (enum request) key;
1238 }
1241 struct key {
1242 const char *name;
1243 int value;
1244 };
1246 static const struct key key_table[] = {
1247 { "Enter", KEY_RETURN },
1248 { "Space", ' ' },
1249 { "Backspace", KEY_BACKSPACE },
1250 { "Tab", KEY_TAB },
1251 { "Escape", KEY_ESC },
1252 { "Left", KEY_LEFT },
1253 { "Right", KEY_RIGHT },
1254 { "Up", KEY_UP },
1255 { "Down", KEY_DOWN },
1256 { "Insert", KEY_IC },
1257 { "Delete", KEY_DC },
1258 { "Hash", '#' },
1259 { "Home", KEY_HOME },
1260 { "End", KEY_END },
1261 { "PageUp", KEY_PPAGE },
1262 { "PageDown", KEY_NPAGE },
1263 { "F1", KEY_F(1) },
1264 { "F2", KEY_F(2) },
1265 { "F3", KEY_F(3) },
1266 { "F4", KEY_F(4) },
1267 { "F5", KEY_F(5) },
1268 { "F6", KEY_F(6) },
1269 { "F7", KEY_F(7) },
1270 { "F8", KEY_F(8) },
1271 { "F9", KEY_F(9) },
1272 { "F10", KEY_F(10) },
1273 { "F11", KEY_F(11) },
1274 { "F12", KEY_F(12) },
1275 };
1277 static int
1278 get_key_value(const char *name)
1279 {
1280 int i;
1282 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1283 if (!strcasecmp(key_table[i].name, name))
1284 return key_table[i].value;
1286 if (strlen(name) == 1 && isprint(*name))
1287 return (int) *name;
1289 return ERR;
1290 }
1292 static const char *
1293 get_key_name(int key_value)
1294 {
1295 static char key_char[] = "'X'";
1296 const char *seq = NULL;
1297 int key;
1299 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1300 if (key_table[key].value == key_value)
1301 seq = key_table[key].name;
1303 if (seq == NULL &&
1304 key_value < 127 &&
1305 isprint(key_value)) {
1306 key_char[1] = (char) key_value;
1307 seq = key_char;
1308 }
1310 return seq ? seq : "(no key)";
1311 }
1313 static const char *
1314 get_key(enum request request)
1315 {
1316 static char buf[BUFSIZ];
1317 size_t pos = 0;
1318 char *sep = "";
1319 int i;
1321 buf[pos] = 0;
1323 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1324 const struct keybinding *keybinding = &default_keybindings[i];
1326 if (keybinding->request != request)
1327 continue;
1329 if (!string_format_from(buf, &pos, "%s%s", sep,
1330 get_key_name(keybinding->alias)))
1331 return "Too many keybindings!";
1332 sep = ", ";
1333 }
1335 return buf;
1336 }
1338 struct run_request {
1339 enum keymap keymap;
1340 int key;
1341 const char *argv[SIZEOF_ARG];
1342 };
1344 static struct run_request *run_request;
1345 static size_t run_requests;
1347 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1349 static enum request
1350 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1351 {
1352 struct run_request *req;
1354 if (argc >= ARRAY_SIZE(req->argv) - 1)
1355 return REQ_NONE;
1357 if (!realloc_run_requests(&run_request, run_requests, 1))
1358 return REQ_NONE;
1360 req = &run_request[run_requests];
1361 req->keymap = keymap;
1362 req->key = key;
1363 req->argv[0] = NULL;
1365 if (!format_argv(req->argv, argv, FORMAT_NONE))
1366 return REQ_NONE;
1368 return REQ_NONE + ++run_requests;
1369 }
1371 static struct run_request *
1372 get_run_request(enum request request)
1373 {
1374 if (request <= REQ_NONE)
1375 return NULL;
1376 return &run_request[request - REQ_NONE - 1];
1377 }
1379 static void
1380 add_builtin_run_requests(void)
1381 {
1382 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1383 const char *commit[] = { "git", "commit", NULL };
1384 const char *gc[] = { "git", "gc", NULL };
1385 struct {
1386 enum keymap keymap;
1387 int key;
1388 int argc;
1389 const char **argv;
1390 } reqs[] = {
1391 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1392 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1393 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1394 };
1395 int i;
1397 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1398 enum request req;
1400 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1401 if (req != REQ_NONE)
1402 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1403 }
1404 }
1406 /*
1407 * User config file handling.
1408 */
1410 static int config_lineno;
1411 static bool config_errors;
1412 static const char *config_msg;
1414 static const struct enum_map color_map[] = {
1415 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1416 COLOR_MAP(DEFAULT),
1417 COLOR_MAP(BLACK),
1418 COLOR_MAP(BLUE),
1419 COLOR_MAP(CYAN),
1420 COLOR_MAP(GREEN),
1421 COLOR_MAP(MAGENTA),
1422 COLOR_MAP(RED),
1423 COLOR_MAP(WHITE),
1424 COLOR_MAP(YELLOW),
1425 };
1427 static const struct enum_map attr_map[] = {
1428 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1429 ATTR_MAP(NORMAL),
1430 ATTR_MAP(BLINK),
1431 ATTR_MAP(BOLD),
1432 ATTR_MAP(DIM),
1433 ATTR_MAP(REVERSE),
1434 ATTR_MAP(STANDOUT),
1435 ATTR_MAP(UNDERLINE),
1436 };
1438 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1440 static int parse_step(double *opt, const char *arg)
1441 {
1442 *opt = atoi(arg);
1443 if (!strchr(arg, '%'))
1444 return OK;
1446 /* "Shift down" so 100% and 1 does not conflict. */
1447 *opt = (*opt - 1) / 100;
1448 if (*opt >= 1.0) {
1449 *opt = 0.99;
1450 config_msg = "Step value larger than 100%";
1451 return ERR;
1452 }
1453 if (*opt < 0.0) {
1454 *opt = 1;
1455 config_msg = "Invalid step value";
1456 return ERR;
1457 }
1458 return OK;
1459 }
1461 static int
1462 parse_int(int *opt, const char *arg, int min, int max)
1463 {
1464 int value = atoi(arg);
1466 if (min <= value && value <= max) {
1467 *opt = value;
1468 return OK;
1469 }
1471 config_msg = "Integer value out of bound";
1472 return ERR;
1473 }
1475 static bool
1476 set_color(int *color, const char *name)
1477 {
1478 if (map_enum(color, color_map, name))
1479 return TRUE;
1480 if (!prefixcmp(name, "color"))
1481 return parse_int(color, name + 5, 0, 255) == OK;
1482 return FALSE;
1483 }
1485 /* Wants: object fgcolor bgcolor [attribute] */
1486 static int
1487 option_color_command(int argc, const char *argv[])
1488 {
1489 struct line_info *info;
1491 if (argc < 3) {
1492 config_msg = "Wrong number of arguments given to color command";
1493 return ERR;
1494 }
1496 info = get_line_info(argv[0]);
1497 if (!info) {
1498 static const struct enum_map obsolete[] = {
1499 ENUM_MAP("main-delim", LINE_DELIMITER),
1500 ENUM_MAP("main-date", LINE_DATE),
1501 ENUM_MAP("main-author", LINE_AUTHOR),
1502 };
1503 int index;
1505 if (!map_enum(&index, obsolete, argv[0])) {
1506 config_msg = "Unknown color name";
1507 return ERR;
1508 }
1509 info = &line_info[index];
1510 }
1512 if (!set_color(&info->fg, argv[1]) ||
1513 !set_color(&info->bg, argv[2])) {
1514 config_msg = "Unknown color";
1515 return ERR;
1516 }
1518 info->attr = 0;
1519 while (argc-- > 3) {
1520 int attr;
1522 if (!set_attribute(&attr, argv[argc])) {
1523 config_msg = "Unknown attribute";
1524 return ERR;
1525 }
1526 info->attr |= attr;
1527 }
1529 return OK;
1530 }
1532 static int parse_bool(bool *opt, const char *arg)
1533 {
1534 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1535 ? TRUE : FALSE;
1536 return OK;
1537 }
1539 static int
1540 parse_string(char *opt, const char *arg, size_t optsize)
1541 {
1542 int arglen = strlen(arg);
1544 switch (arg[0]) {
1545 case '\"':
1546 case '\'':
1547 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1548 config_msg = "Unmatched quotation";
1549 return ERR;
1550 }
1551 arg += 1; arglen -= 2;
1552 default:
1553 string_ncopy_do(opt, optsize, arg, arglen);
1554 return OK;
1555 }
1556 }
1558 /* Wants: name = value */
1559 static int
1560 option_set_command(int argc, const char *argv[])
1561 {
1562 if (argc != 3) {
1563 config_msg = "Wrong number of arguments given to set command";
1564 return ERR;
1565 }
1567 if (strcmp(argv[1], "=")) {
1568 config_msg = "No value assigned";
1569 return ERR;
1570 }
1572 if (!strcmp(argv[0], "show-author"))
1573 return parse_bool(&opt_author, argv[2]);
1575 if (!strcmp(argv[0], "show-date"))
1576 return parse_bool(&opt_date, argv[2]);
1578 if (!strcmp(argv[0], "show-rev-graph"))
1579 return parse_bool(&opt_rev_graph, argv[2]);
1581 if (!strcmp(argv[0], "show-refs"))
1582 return parse_bool(&opt_show_refs, argv[2]);
1584 if (!strcmp(argv[0], "show-line-numbers"))
1585 return parse_bool(&opt_line_number, argv[2]);
1587 if (!strcmp(argv[0], "line-graphics"))
1588 return parse_bool(&opt_line_graphics, argv[2]);
1590 if (!strcmp(argv[0], "line-number-interval"))
1591 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1593 if (!strcmp(argv[0], "author-width"))
1594 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1596 if (!strcmp(argv[0], "horizontal-scroll"))
1597 return parse_step(&opt_hscroll, argv[2]);
1599 if (!strcmp(argv[0], "split-view-height"))
1600 return parse_step(&opt_scale_split_view, argv[2]);
1602 if (!strcmp(argv[0], "tab-size"))
1603 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1605 if (!strcmp(argv[0], "commit-encoding"))
1606 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1608 config_msg = "Unknown variable name";
1609 return ERR;
1610 }
1612 /* Wants: mode request key */
1613 static int
1614 option_bind_command(int argc, const char *argv[])
1615 {
1616 enum request request;
1617 int keymap;
1618 int key;
1620 if (argc < 3) {
1621 config_msg = "Wrong number of arguments given to bind command";
1622 return ERR;
1623 }
1625 if (set_keymap(&keymap, argv[0]) == ERR) {
1626 config_msg = "Unknown key map";
1627 return ERR;
1628 }
1630 key = get_key_value(argv[1]);
1631 if (key == ERR) {
1632 config_msg = "Unknown key";
1633 return ERR;
1634 }
1636 request = get_request(argv[2]);
1637 if (request == REQ_NONE) {
1638 static const struct enum_map obsolete[] = {
1639 ENUM_MAP("cherry-pick", REQ_NONE),
1640 ENUM_MAP("screen-resize", REQ_NONE),
1641 ENUM_MAP("tree-parent", REQ_PARENT),
1642 };
1643 int alias;
1645 if (map_enum(&alias, obsolete, argv[2])) {
1646 if (alias != REQ_NONE)
1647 add_keybinding(keymap, alias, key);
1648 config_msg = "Obsolete request name";
1649 return ERR;
1650 }
1651 }
1652 if (request == REQ_NONE && *argv[2]++ == '!')
1653 request = add_run_request(keymap, key, argc - 2, argv + 2);
1654 if (request == REQ_NONE) {
1655 config_msg = "Unknown request name";
1656 return ERR;
1657 }
1659 add_keybinding(keymap, request, key);
1661 return OK;
1662 }
1664 static int
1665 set_option(const char *opt, char *value)
1666 {
1667 const char *argv[SIZEOF_ARG];
1668 int argc = 0;
1670 if (!argv_from_string(argv, &argc, value)) {
1671 config_msg = "Too many option arguments";
1672 return ERR;
1673 }
1675 if (!strcmp(opt, "color"))
1676 return option_color_command(argc, argv);
1678 if (!strcmp(opt, "set"))
1679 return option_set_command(argc, argv);
1681 if (!strcmp(opt, "bind"))
1682 return option_bind_command(argc, argv);
1684 config_msg = "Unknown option command";
1685 return ERR;
1686 }
1688 static int
1689 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1690 {
1691 int status = OK;
1693 config_lineno++;
1694 config_msg = "Internal error";
1696 /* Check for comment markers, since read_properties() will
1697 * only ensure opt and value are split at first " \t". */
1698 optlen = strcspn(opt, "#");
1699 if (optlen == 0)
1700 return OK;
1702 if (opt[optlen] != 0) {
1703 config_msg = "No option value";
1704 status = ERR;
1706 } else {
1707 /* Look for comment endings in the value. */
1708 size_t len = strcspn(value, "#");
1710 if (len < valuelen) {
1711 valuelen = len;
1712 value[valuelen] = 0;
1713 }
1715 status = set_option(opt, value);
1716 }
1718 if (status == ERR) {
1719 warn("Error on line %d, near '%.*s': %s",
1720 config_lineno, (int) optlen, opt, config_msg);
1721 config_errors = TRUE;
1722 }
1724 /* Always keep going if errors are encountered. */
1725 return OK;
1726 }
1728 static void
1729 load_option_file(const char *path)
1730 {
1731 struct io io = {};
1733 /* It's OK that the file doesn't exist. */
1734 if (!io_open(&io, path))
1735 return;
1737 config_lineno = 0;
1738 config_errors = FALSE;
1740 if (io_load(&io, " \t", read_option) == ERR ||
1741 config_errors == TRUE)
1742 warn("Errors while loading %s.", path);
1743 }
1745 static int
1746 load_options(void)
1747 {
1748 const char *home = getenv("HOME");
1749 const char *tigrc_user = getenv("TIGRC_USER");
1750 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1751 char buf[SIZEOF_STR];
1753 add_builtin_run_requests();
1755 if (!tigrc_system)
1756 tigrc_system = SYSCONFDIR "/tigrc";
1757 load_option_file(tigrc_system);
1759 if (!tigrc_user) {
1760 if (!home || !string_format(buf, "%s/.tigrc", home))
1761 return ERR;
1762 tigrc_user = buf;
1763 }
1764 load_option_file(tigrc_user);
1766 return OK;
1767 }
1770 /*
1771 * The viewer
1772 */
1774 struct view;
1775 struct view_ops;
1777 /* The display array of active views and the index of the current view. */
1778 static struct view *display[2];
1779 static unsigned int current_view;
1781 #define foreach_displayed_view(view, i) \
1782 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1784 #define displayed_views() (display[1] != NULL ? 2 : 1)
1786 /* Current head and commit ID */
1787 static char ref_blob[SIZEOF_REF] = "";
1788 static char ref_commit[SIZEOF_REF] = "HEAD";
1789 static char ref_head[SIZEOF_REF] = "HEAD";
1791 struct view {
1792 const char *name; /* View name */
1793 const char *cmd_env; /* Command line set via environment */
1794 const char *id; /* Points to either of ref_{head,commit,blob} */
1796 struct view_ops *ops; /* View operations */
1798 enum keymap keymap; /* What keymap does this view have */
1799 bool git_dir; /* Whether the view requires a git directory. */
1801 char ref[SIZEOF_REF]; /* Hovered commit reference */
1802 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1804 int height, width; /* The width and height of the main window */
1805 WINDOW *win; /* The main window */
1806 WINDOW *title; /* The title window living below the main window */
1808 /* Navigation */
1809 unsigned long offset; /* Offset of the window top */
1810 unsigned long yoffset; /* Offset from the window side. */
1811 unsigned long lineno; /* Current line number */
1812 unsigned long p_offset; /* Previous offset of the window top */
1813 unsigned long p_yoffset;/* Previous offset from the window side */
1814 unsigned long p_lineno; /* Previous current line number */
1815 bool p_restore; /* Should the previous position be restored. */
1817 /* Searching */
1818 char grep[SIZEOF_STR]; /* Search string */
1819 regex_t *regex; /* Pre-compiled regexp */
1821 /* If non-NULL, points to the view that opened this view. If this view
1822 * is closed tig will switch back to the parent view. */
1823 struct view *parent;
1825 /* Buffering */
1826 size_t lines; /* Total number of lines */
1827 struct line *line; /* Line index */
1828 unsigned int digits; /* Number of digits in the lines member. */
1830 /* Drawing */
1831 struct line *curline; /* Line currently being drawn. */
1832 enum line_type curtype; /* Attribute currently used for drawing. */
1833 unsigned long col; /* Column when drawing. */
1834 bool has_scrolled; /* View was scrolled. */
1836 /* Loading */
1837 struct io io;
1838 struct io *pipe;
1839 time_t start_time;
1840 time_t update_secs;
1841 };
1843 struct view_ops {
1844 /* What type of content being displayed. Used in the title bar. */
1845 const char *type;
1846 /* Default command arguments. */
1847 const char **argv;
1848 /* Open and reads in all view content. */
1849 bool (*open)(struct view *view);
1850 /* Read one line; updates view->line. */
1851 bool (*read)(struct view *view, char *data);
1852 /* Draw one line; @lineno must be < view->height. */
1853 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1854 /* Depending on view handle a special requests. */
1855 enum request (*request)(struct view *view, enum request request, struct line *line);
1856 /* Search for regexp in a line. */
1857 bool (*grep)(struct view *view, struct line *line);
1858 /* Select line */
1859 void (*select)(struct view *view, struct line *line);
1860 };
1862 static struct view_ops blame_ops;
1863 static struct view_ops blob_ops;
1864 static struct view_ops diff_ops;
1865 static struct view_ops help_ops;
1866 static struct view_ops log_ops;
1867 static struct view_ops main_ops;
1868 static struct view_ops pager_ops;
1869 static struct view_ops stage_ops;
1870 static struct view_ops status_ops;
1871 static struct view_ops tree_ops;
1872 static struct view_ops branch_ops;
1874 #define VIEW_STR(name, env, ref, ops, map, git) \
1875 { name, #env, ref, ops, map, git }
1877 #define VIEW_(id, name, ops, git, ref) \
1878 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1881 static struct view views[] = {
1882 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1883 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1884 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1885 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1886 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1887 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1888 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
1889 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1890 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1891 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1892 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1893 };
1895 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1896 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1898 #define foreach_view(view, i) \
1899 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1901 #define view_is_displayed(view) \
1902 (view == display[0] || view == display[1])
1905 enum line_graphic {
1906 LINE_GRAPHIC_VLINE
1907 };
1909 static chtype line_graphics[] = {
1910 /* LINE_GRAPHIC_VLINE: */ '|'
1911 };
1913 static inline void
1914 set_view_attr(struct view *view, enum line_type type)
1915 {
1916 if (!view->curline->selected && view->curtype != type) {
1917 wattrset(view->win, get_line_attr(type));
1918 wchgat(view->win, -1, 0, type, NULL);
1919 view->curtype = type;
1920 }
1921 }
1923 static int
1924 draw_chars(struct view *view, enum line_type type, const char *string,
1925 int max_len, bool use_tilde)
1926 {
1927 int len = 0;
1928 int col = 0;
1929 int trimmed = FALSE;
1930 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1932 if (max_len <= 0)
1933 return 0;
1935 if (opt_utf8) {
1936 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1937 } else {
1938 col = len = strlen(string);
1939 if (len > max_len) {
1940 if (use_tilde) {
1941 max_len -= 1;
1942 }
1943 col = len = max_len;
1944 trimmed = TRUE;
1945 }
1946 }
1948 set_view_attr(view, type);
1949 if (len > 0)
1950 waddnstr(view->win, string, len);
1951 if (trimmed && use_tilde) {
1952 set_view_attr(view, LINE_DELIMITER);
1953 waddch(view->win, '~');
1954 col++;
1955 }
1957 return col;
1958 }
1960 static int
1961 draw_space(struct view *view, enum line_type type, int max, int spaces)
1962 {
1963 static char space[] = " ";
1964 int col = 0;
1966 spaces = MIN(max, spaces);
1968 while (spaces > 0) {
1969 int len = MIN(spaces, sizeof(space) - 1);
1971 col += draw_chars(view, type, space, len, FALSE);
1972 spaces -= len;
1973 }
1975 return col;
1976 }
1978 static bool
1979 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1980 {
1981 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1982 return view->width + view->yoffset <= view->col;
1983 }
1985 static bool
1986 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1987 {
1988 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1989 int max = view->width + view->yoffset - view->col;
1990 int i;
1992 if (max < size)
1993 size = max;
1995 set_view_attr(view, type);
1996 /* Using waddch() instead of waddnstr() ensures that
1997 * they'll be rendered correctly for the cursor line. */
1998 for (i = skip; i < size; i++)
1999 waddch(view->win, graphic[i]);
2001 view->col += size;
2002 if (size < max && skip <= size)
2003 waddch(view->win, ' ');
2004 view->col++;
2006 return view->width + view->yoffset <= view->col;
2007 }
2009 static bool
2010 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2011 {
2012 int max = MIN(view->width + view->yoffset - view->col, len);
2013 int col;
2015 if (text)
2016 col = draw_chars(view, type, text, max - 1, trim);
2017 else
2018 col = draw_space(view, type, max - 1, max - 1);
2020 view->col += col;
2021 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2022 return view->width + view->yoffset <= view->col;
2023 }
2025 static bool
2026 draw_date(struct view *view, time_t *time)
2027 {
2028 const char *date = mkdate(time);
2030 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2031 }
2033 static bool
2034 draw_author(struct view *view, const char *author)
2035 {
2036 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2038 if (!trim) {
2039 static char initials[10];
2040 size_t pos;
2042 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2044 memset(initials, 0, sizeof(initials));
2045 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2046 while (is_initial_sep(*author))
2047 author++;
2048 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2049 while (*author && !is_initial_sep(author[1]))
2050 author++;
2051 }
2053 author = initials;
2054 }
2056 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2057 }
2059 static bool
2060 draw_mode(struct view *view, mode_t mode)
2061 {
2062 const char *str;
2064 if (S_ISDIR(mode))
2065 str = "drwxr-xr-x";
2066 else if (S_ISLNK(mode))
2067 str = "lrwxrwxrwx";
2068 else if (S_ISGITLINK(mode))
2069 str = "m---------";
2070 else if (S_ISREG(mode) && mode & S_IXUSR)
2071 str = "-rwxr-xr-x";
2072 else if (S_ISREG(mode))
2073 str = "-rw-r--r--";
2074 else
2075 str = "----------";
2077 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2078 }
2080 static bool
2081 draw_lineno(struct view *view, unsigned int lineno)
2082 {
2083 char number[10];
2084 int digits3 = view->digits < 3 ? 3 : view->digits;
2085 int max = MIN(view->width + view->yoffset - view->col, digits3);
2086 char *text = NULL;
2088 lineno += view->offset + 1;
2089 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2090 static char fmt[] = "%1ld";
2092 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2093 if (string_format(number, fmt, lineno))
2094 text = number;
2095 }
2096 if (text)
2097 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2098 else
2099 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2100 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2101 }
2103 static bool
2104 draw_view_line(struct view *view, unsigned int lineno)
2105 {
2106 struct line *line;
2107 bool selected = (view->offset + lineno == view->lineno);
2109 assert(view_is_displayed(view));
2111 if (view->offset + lineno >= view->lines)
2112 return FALSE;
2114 line = &view->line[view->offset + lineno];
2116 wmove(view->win, lineno, 0);
2117 if (line->cleareol)
2118 wclrtoeol(view->win);
2119 view->col = 0;
2120 view->curline = line;
2121 view->curtype = LINE_NONE;
2122 line->selected = FALSE;
2123 line->dirty = line->cleareol = 0;
2125 if (selected) {
2126 set_view_attr(view, LINE_CURSOR);
2127 line->selected = TRUE;
2128 view->ops->select(view, line);
2129 }
2131 return view->ops->draw(view, line, lineno);
2132 }
2134 static void
2135 redraw_view_dirty(struct view *view)
2136 {
2137 bool dirty = FALSE;
2138 int lineno;
2140 for (lineno = 0; lineno < view->height; lineno++) {
2141 if (view->offset + lineno >= view->lines)
2142 break;
2143 if (!view->line[view->offset + lineno].dirty)
2144 continue;
2145 dirty = TRUE;
2146 if (!draw_view_line(view, lineno))
2147 break;
2148 }
2150 if (!dirty)
2151 return;
2152 wnoutrefresh(view->win);
2153 }
2155 static void
2156 redraw_view_from(struct view *view, int lineno)
2157 {
2158 assert(0 <= lineno && lineno < view->height);
2160 for (; lineno < view->height; lineno++) {
2161 if (!draw_view_line(view, lineno))
2162 break;
2163 }
2165 wnoutrefresh(view->win);
2166 }
2168 static void
2169 redraw_view(struct view *view)
2170 {
2171 werase(view->win);
2172 redraw_view_from(view, 0);
2173 }
2176 static void
2177 update_view_title(struct view *view)
2178 {
2179 char buf[SIZEOF_STR];
2180 char state[SIZEOF_STR];
2181 size_t bufpos = 0, statelen = 0;
2183 assert(view_is_displayed(view));
2185 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2186 unsigned int view_lines = view->offset + view->height;
2187 unsigned int lines = view->lines
2188 ? MIN(view_lines, view->lines) * 100 / view->lines
2189 : 0;
2191 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2192 view->ops->type,
2193 view->lineno + 1,
2194 view->lines,
2195 lines);
2197 }
2199 if (view->pipe) {
2200 time_t secs = time(NULL) - view->start_time;
2202 /* Three git seconds are a long time ... */
2203 if (secs > 2)
2204 string_format_from(state, &statelen, " loading %lds", secs);
2205 }
2207 string_format_from(buf, &bufpos, "[%s]", view->name);
2208 if (*view->ref && bufpos < view->width) {
2209 size_t refsize = strlen(view->ref);
2210 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2212 if (minsize < view->width)
2213 refsize = view->width - minsize + 7;
2214 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2215 }
2217 if (statelen && bufpos < view->width) {
2218 string_format_from(buf, &bufpos, "%s", state);
2219 }
2221 if (view == display[current_view])
2222 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2223 else
2224 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2226 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2227 wclrtoeol(view->title);
2228 wnoutrefresh(view->title);
2229 }
2231 static int
2232 apply_step(double step, int value)
2233 {
2234 if (step >= 1)
2235 return (int) step;
2236 value *= step + 0.01;
2237 return value ? value : 1;
2238 }
2240 static void
2241 resize_display(void)
2242 {
2243 int offset, i;
2244 struct view *base = display[0];
2245 struct view *view = display[1] ? display[1] : display[0];
2247 /* Setup window dimensions */
2249 getmaxyx(stdscr, base->height, base->width);
2251 /* Make room for the status window. */
2252 base->height -= 1;
2254 if (view != base) {
2255 /* Horizontal split. */
2256 view->width = base->width;
2257 view->height = apply_step(opt_scale_split_view, base->height);
2258 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2259 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2260 base->height -= view->height;
2262 /* Make room for the title bar. */
2263 view->height -= 1;
2264 }
2266 /* Make room for the title bar. */
2267 base->height -= 1;
2269 offset = 0;
2271 foreach_displayed_view (view, i) {
2272 if (!view->win) {
2273 view->win = newwin(view->height, 0, offset, 0);
2274 if (!view->win)
2275 die("Failed to create %s view", view->name);
2277 scrollok(view->win, FALSE);
2279 view->title = newwin(1, 0, offset + view->height, 0);
2280 if (!view->title)
2281 die("Failed to create title window");
2283 } else {
2284 wresize(view->win, view->height, view->width);
2285 mvwin(view->win, offset, 0);
2286 mvwin(view->title, offset + view->height, 0);
2287 }
2289 offset += view->height + 1;
2290 }
2291 }
2293 static void
2294 redraw_display(bool clear)
2295 {
2296 struct view *view;
2297 int i;
2299 foreach_displayed_view (view, i) {
2300 if (clear)
2301 wclear(view->win);
2302 redraw_view(view);
2303 update_view_title(view);
2304 }
2305 }
2307 static void
2308 toggle_view_option(bool *option, const char *help)
2309 {
2310 *option = !*option;
2311 redraw_display(FALSE);
2312 report("%sabling %s", *option ? "En" : "Dis", help);
2313 }
2315 static void
2316 open_option_menu(void)
2317 {
2318 const struct menu_item menu[] = {
2319 { '.', "line numbers", &opt_line_number },
2320 { 'D', "date display", &opt_date },
2321 { 'A', "author display", &opt_author },
2322 { 'g', "revision graph display", &opt_rev_graph },
2323 { 'F', "reference display", &opt_show_refs },
2324 { 0 }
2325 };
2326 int selected = 0;
2328 if (prompt_menu("Toggle option", menu, &selected))
2329 toggle_view_option(menu[selected].data, menu[selected].text);
2330 }
2332 static void
2333 maximize_view(struct view *view)
2334 {
2335 memset(display, 0, sizeof(display));
2336 current_view = 0;
2337 display[current_view] = view;
2338 resize_display();
2339 redraw_display(FALSE);
2340 report("");
2341 }
2344 /*
2345 * Navigation
2346 */
2348 static bool
2349 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2350 {
2351 if (lineno >= view->lines)
2352 lineno = view->lines > 0 ? view->lines - 1 : 0;
2354 if (offset > lineno || offset + view->height <= lineno) {
2355 unsigned long half = view->height / 2;
2357 if (lineno > half)
2358 offset = lineno - half;
2359 else
2360 offset = 0;
2361 }
2363 if (offset != view->offset || lineno != view->lineno) {
2364 view->offset = offset;
2365 view->lineno = lineno;
2366 return TRUE;
2367 }
2369 return FALSE;
2370 }
2372 /* Scrolling backend */
2373 static void
2374 do_scroll_view(struct view *view, int lines)
2375 {
2376 bool redraw_current_line = FALSE;
2378 /* The rendering expects the new offset. */
2379 view->offset += lines;
2381 assert(0 <= view->offset && view->offset < view->lines);
2382 assert(lines);
2384 /* Move current line into the view. */
2385 if (view->lineno < view->offset) {
2386 view->lineno = view->offset;
2387 redraw_current_line = TRUE;
2388 } else if (view->lineno >= view->offset + view->height) {
2389 view->lineno = view->offset + view->height - 1;
2390 redraw_current_line = TRUE;
2391 }
2393 assert(view->offset <= view->lineno && view->lineno < view->lines);
2395 /* Redraw the whole screen if scrolling is pointless. */
2396 if (view->height < ABS(lines)) {
2397 redraw_view(view);
2399 } else {
2400 int line = lines > 0 ? view->height - lines : 0;
2401 int end = line + ABS(lines);
2403 scrollok(view->win, TRUE);
2404 wscrl(view->win, lines);
2405 scrollok(view->win, FALSE);
2407 while (line < end && draw_view_line(view, line))
2408 line++;
2410 if (redraw_current_line)
2411 draw_view_line(view, view->lineno - view->offset);
2412 wnoutrefresh(view->win);
2413 }
2415 view->has_scrolled = TRUE;
2416 report("");
2417 }
2419 /* Scroll frontend */
2420 static void
2421 scroll_view(struct view *view, enum request request)
2422 {
2423 int lines = 1;
2425 assert(view_is_displayed(view));
2427 switch (request) {
2428 case REQ_SCROLL_LEFT:
2429 if (view->yoffset == 0) {
2430 report("Cannot scroll beyond the first column");
2431 return;
2432 }
2433 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2434 view->yoffset = 0;
2435 else
2436 view->yoffset -= apply_step(opt_hscroll, view->width);
2437 redraw_view_from(view, 0);
2438 report("");
2439 return;
2440 case REQ_SCROLL_RIGHT:
2441 view->yoffset += apply_step(opt_hscroll, view->width);
2442 redraw_view(view);
2443 report("");
2444 return;
2445 case REQ_SCROLL_PAGE_DOWN:
2446 lines = view->height;
2447 case REQ_SCROLL_LINE_DOWN:
2448 if (view->offset + lines > view->lines)
2449 lines = view->lines - view->offset;
2451 if (lines == 0 || view->offset + view->height >= view->lines) {
2452 report("Cannot scroll beyond the last line");
2453 return;
2454 }
2455 break;
2457 case REQ_SCROLL_PAGE_UP:
2458 lines = view->height;
2459 case REQ_SCROLL_LINE_UP:
2460 if (lines > view->offset)
2461 lines = view->offset;
2463 if (lines == 0) {
2464 report("Cannot scroll beyond the first line");
2465 return;
2466 }
2468 lines = -lines;
2469 break;
2471 default:
2472 die("request %d not handled in switch", request);
2473 }
2475 do_scroll_view(view, lines);
2476 }
2478 /* Cursor moving */
2479 static void
2480 move_view(struct view *view, enum request request)
2481 {
2482 int scroll_steps = 0;
2483 int steps;
2485 switch (request) {
2486 case REQ_MOVE_FIRST_LINE:
2487 steps = -view->lineno;
2488 break;
2490 case REQ_MOVE_LAST_LINE:
2491 steps = view->lines - view->lineno - 1;
2492 break;
2494 case REQ_MOVE_PAGE_UP:
2495 steps = view->height > view->lineno
2496 ? -view->lineno : -view->height;
2497 break;
2499 case REQ_MOVE_PAGE_DOWN:
2500 steps = view->lineno + view->height >= view->lines
2501 ? view->lines - view->lineno - 1 : view->height;
2502 break;
2504 case REQ_MOVE_UP:
2505 steps = -1;
2506 break;
2508 case REQ_MOVE_DOWN:
2509 steps = 1;
2510 break;
2512 default:
2513 die("request %d not handled in switch", request);
2514 }
2516 if (steps <= 0 && view->lineno == 0) {
2517 report("Cannot move beyond the first line");
2518 return;
2520 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2521 report("Cannot move beyond the last line");
2522 return;
2523 }
2525 /* Move the current line */
2526 view->lineno += steps;
2527 assert(0 <= view->lineno && view->lineno < view->lines);
2529 /* Check whether the view needs to be scrolled */
2530 if (view->lineno < view->offset ||
2531 view->lineno >= view->offset + view->height) {
2532 scroll_steps = steps;
2533 if (steps < 0 && -steps > view->offset) {
2534 scroll_steps = -view->offset;
2536 } else if (steps > 0) {
2537 if (view->lineno == view->lines - 1 &&
2538 view->lines > view->height) {
2539 scroll_steps = view->lines - view->offset - 1;
2540 if (scroll_steps >= view->height)
2541 scroll_steps -= view->height - 1;
2542 }
2543 }
2544 }
2546 if (!view_is_displayed(view)) {
2547 view->offset += scroll_steps;
2548 assert(0 <= view->offset && view->offset < view->lines);
2549 view->ops->select(view, &view->line[view->lineno]);
2550 return;
2551 }
2553 /* Repaint the old "current" line if we be scrolling */
2554 if (ABS(steps) < view->height)
2555 draw_view_line(view, view->lineno - steps - view->offset);
2557 if (scroll_steps) {
2558 do_scroll_view(view, scroll_steps);
2559 return;
2560 }
2562 /* Draw the current line */
2563 draw_view_line(view, view->lineno - view->offset);
2565 wnoutrefresh(view->win);
2566 report("");
2567 }
2570 /*
2571 * Searching
2572 */
2574 static void search_view(struct view *view, enum request request);
2576 static bool
2577 grep_text(struct view *view, const char *text[])
2578 {
2579 regmatch_t pmatch;
2580 size_t i;
2582 for (i = 0; text[i]; i++)
2583 if (*text[i] &&
2584 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2585 return TRUE;
2586 return FALSE;
2587 }
2589 static void
2590 select_view_line(struct view *view, unsigned long lineno)
2591 {
2592 unsigned long old_lineno = view->lineno;
2593 unsigned long old_offset = view->offset;
2595 if (goto_view_line(view, view->offset, lineno)) {
2596 if (view_is_displayed(view)) {
2597 if (old_offset != view->offset) {
2598 redraw_view(view);
2599 } else {
2600 draw_view_line(view, old_lineno - view->offset);
2601 draw_view_line(view, view->lineno - view->offset);
2602 wnoutrefresh(view->win);
2603 }
2604 } else {
2605 view->ops->select(view, &view->line[view->lineno]);
2606 }
2607 }
2608 }
2610 static void
2611 find_next(struct view *view, enum request request)
2612 {
2613 unsigned long lineno = view->lineno;
2614 int direction;
2616 if (!*view->grep) {
2617 if (!*opt_search)
2618 report("No previous search");
2619 else
2620 search_view(view, request);
2621 return;
2622 }
2624 switch (request) {
2625 case REQ_SEARCH:
2626 case REQ_FIND_NEXT:
2627 direction = 1;
2628 break;
2630 case REQ_SEARCH_BACK:
2631 case REQ_FIND_PREV:
2632 direction = -1;
2633 break;
2635 default:
2636 return;
2637 }
2639 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2640 lineno += direction;
2642 /* Note, lineno is unsigned long so will wrap around in which case it
2643 * will become bigger than view->lines. */
2644 for (; lineno < view->lines; lineno += direction) {
2645 if (view->ops->grep(view, &view->line[lineno])) {
2646 select_view_line(view, lineno);
2647 report("Line %ld matches '%s'", lineno + 1, view->grep);
2648 return;
2649 }
2650 }
2652 report("No match found for '%s'", view->grep);
2653 }
2655 static void
2656 search_view(struct view *view, enum request request)
2657 {
2658 int regex_err;
2660 if (view->regex) {
2661 regfree(view->regex);
2662 *view->grep = 0;
2663 } else {
2664 view->regex = calloc(1, sizeof(*view->regex));
2665 if (!view->regex)
2666 return;
2667 }
2669 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2670 if (regex_err != 0) {
2671 char buf[SIZEOF_STR] = "unknown error";
2673 regerror(regex_err, view->regex, buf, sizeof(buf));
2674 report("Search failed: %s", buf);
2675 return;
2676 }
2678 string_copy(view->grep, opt_search);
2680 find_next(view, request);
2681 }
2683 /*
2684 * Incremental updating
2685 */
2687 static void
2688 reset_view(struct view *view)
2689 {
2690 int i;
2692 for (i = 0; i < view->lines; i++)
2693 free(view->line[i].data);
2694 free(view->line);
2696 view->p_offset = view->offset;
2697 view->p_yoffset = view->yoffset;
2698 view->p_lineno = view->lineno;
2700 view->line = NULL;
2701 view->offset = 0;
2702 view->yoffset = 0;
2703 view->lines = 0;
2704 view->lineno = 0;
2705 view->vid[0] = 0;
2706 view->update_secs = 0;
2707 }
2709 static void
2710 free_argv(const char *argv[])
2711 {
2712 int argc;
2714 for (argc = 0; argv[argc]; argc++)
2715 free((void *) argv[argc]);
2716 }
2718 static bool
2719 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2720 {
2721 char buf[SIZEOF_STR];
2722 int argc;
2723 bool noreplace = flags == FORMAT_NONE;
2725 free_argv(dst_argv);
2727 for (argc = 0; src_argv[argc]; argc++) {
2728 const char *arg = src_argv[argc];
2729 size_t bufpos = 0;
2731 while (arg) {
2732 char *next = strstr(arg, "%(");
2733 int len = next - arg;
2734 const char *value;
2736 if (!next || noreplace) {
2737 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2738 noreplace = TRUE;
2739 len = strlen(arg);
2740 value = "";
2742 } else if (!prefixcmp(next, "%(directory)")) {
2743 value = opt_path;
2745 } else if (!prefixcmp(next, "%(file)")) {
2746 value = opt_file;
2748 } else if (!prefixcmp(next, "%(ref)")) {
2749 value = *opt_ref ? opt_ref : "HEAD";
2751 } else if (!prefixcmp(next, "%(head)")) {
2752 value = ref_head;
2754 } else if (!prefixcmp(next, "%(commit)")) {
2755 value = ref_commit;
2757 } else if (!prefixcmp(next, "%(blob)")) {
2758 value = ref_blob;
2760 } else {
2761 report("Unknown replacement: `%s`", next);
2762 return FALSE;
2763 }
2765 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2766 return FALSE;
2768 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2769 }
2771 dst_argv[argc] = strdup(buf);
2772 if (!dst_argv[argc])
2773 break;
2774 }
2776 dst_argv[argc] = NULL;
2778 return src_argv[argc] == NULL;
2779 }
2781 static bool
2782 restore_view_position(struct view *view)
2783 {
2784 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2785 return FALSE;
2787 /* Changing the view position cancels the restoring. */
2788 /* FIXME: Changing back to the first line is not detected. */
2789 if (view->offset != 0 || view->lineno != 0) {
2790 view->p_restore = FALSE;
2791 return FALSE;
2792 }
2794 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2795 view_is_displayed(view))
2796 werase(view->win);
2798 view->yoffset = view->p_yoffset;
2799 view->p_restore = FALSE;
2801 return TRUE;
2802 }
2804 static void
2805 end_update(struct view *view, bool force)
2806 {
2807 if (!view->pipe)
2808 return;
2809 while (!view->ops->read(view, NULL))
2810 if (!force)
2811 return;
2812 set_nonblocking_input(FALSE);
2813 if (force)
2814 kill_io(view->pipe);
2815 done_io(view->pipe);
2816 view->pipe = NULL;
2817 }
2819 static void
2820 setup_update(struct view *view, const char *vid)
2821 {
2822 set_nonblocking_input(TRUE);
2823 reset_view(view);
2824 string_copy_rev(view->vid, vid);
2825 view->pipe = &view->io;
2826 view->start_time = time(NULL);
2827 }
2829 static bool
2830 prepare_update(struct view *view, const char *argv[], const char *dir,
2831 enum format_flags flags)
2832 {
2833 if (view->pipe)
2834 end_update(view, TRUE);
2835 return init_io_rd(&view->io, argv, dir, flags);
2836 }
2838 static bool
2839 prepare_update_file(struct view *view, const char *name)
2840 {
2841 if (view->pipe)
2842 end_update(view, TRUE);
2843 return io_open(&view->io, name);
2844 }
2846 static bool
2847 begin_update(struct view *view, bool refresh)
2848 {
2849 if (view->pipe)
2850 end_update(view, TRUE);
2852 if (refresh) {
2853 if (!start_io(&view->io))
2854 return FALSE;
2856 } else {
2857 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2858 opt_path[0] = 0;
2860 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2861 return FALSE;
2863 /* Put the current ref_* value to the view title ref
2864 * member. This is needed by the blob view. Most other
2865 * views sets it automatically after loading because the
2866 * first line is a commit line. */
2867 string_copy_rev(view->ref, view->id);
2868 }
2870 setup_update(view, view->id);
2872 return TRUE;
2873 }
2875 static bool
2876 update_view(struct view *view)
2877 {
2878 char out_buffer[BUFSIZ * 2];
2879 char *line;
2880 /* Clear the view and redraw everything since the tree sorting
2881 * might have rearranged things. */
2882 bool redraw = view->lines == 0;
2883 bool can_read = TRUE;
2885 if (!view->pipe)
2886 return TRUE;
2888 if (!io_can_read(view->pipe)) {
2889 if (view->lines == 0 && view_is_displayed(view)) {
2890 time_t secs = time(NULL) - view->start_time;
2892 if (secs > 1 && secs > view->update_secs) {
2893 if (view->update_secs == 0)
2894 redraw_view(view);
2895 update_view_title(view);
2896 view->update_secs = secs;
2897 }
2898 }
2899 return TRUE;
2900 }
2902 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2903 if (opt_iconv != ICONV_NONE) {
2904 ICONV_CONST char *inbuf = line;
2905 size_t inlen = strlen(line) + 1;
2907 char *outbuf = out_buffer;
2908 size_t outlen = sizeof(out_buffer);
2910 size_t ret;
2912 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2913 if (ret != (size_t) -1)
2914 line = out_buffer;
2915 }
2917 if (!view->ops->read(view, line)) {
2918 report("Allocation failure");
2919 end_update(view, TRUE);
2920 return FALSE;
2921 }
2922 }
2924 {
2925 unsigned long lines = view->lines;
2926 int digits;
2928 for (digits = 0; lines; digits++)
2929 lines /= 10;
2931 /* Keep the displayed view in sync with line number scaling. */
2932 if (digits != view->digits) {
2933 view->digits = digits;
2934 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2935 redraw = TRUE;
2936 }
2937 }
2939 if (io_error(view->pipe)) {
2940 report("Failed to read: %s", io_strerror(view->pipe));
2941 end_update(view, TRUE);
2943 } else if (io_eof(view->pipe)) {
2944 report("");
2945 end_update(view, FALSE);
2946 }
2948 if (restore_view_position(view))
2949 redraw = TRUE;
2951 if (!view_is_displayed(view))
2952 return TRUE;
2954 if (redraw)
2955 redraw_view_from(view, 0);
2956 else
2957 redraw_view_dirty(view);
2959 /* Update the title _after_ the redraw so that if the redraw picks up a
2960 * commit reference in view->ref it'll be available here. */
2961 update_view_title(view);
2962 return TRUE;
2963 }
2965 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2967 static struct line *
2968 add_line_data(struct view *view, void *data, enum line_type type)
2969 {
2970 struct line *line;
2972 if (!realloc_lines(&view->line, view->lines, 1))
2973 return NULL;
2975 line = &view->line[view->lines++];
2976 memset(line, 0, sizeof(*line));
2977 line->type = type;
2978 line->data = data;
2979 line->dirty = 1;
2981 return line;
2982 }
2984 static struct line *
2985 add_line_text(struct view *view, const char *text, enum line_type type)
2986 {
2987 char *data = text ? strdup(text) : NULL;
2989 return data ? add_line_data(view, data, type) : NULL;
2990 }
2992 static struct line *
2993 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2994 {
2995 char buf[SIZEOF_STR];
2996 va_list args;
2998 va_start(args, fmt);
2999 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3000 buf[0] = 0;
3001 va_end(args);
3003 return buf[0] ? add_line_text(view, buf, type) : NULL;
3004 }
3006 /*
3007 * View opening
3008 */
3010 enum open_flags {
3011 OPEN_DEFAULT = 0, /* Use default view switching. */
3012 OPEN_SPLIT = 1, /* Split current view. */
3013 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3014 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3015 OPEN_PREPARED = 32, /* Open already prepared command. */
3016 };
3018 static void
3019 open_view(struct view *prev, enum request request, enum open_flags flags)
3020 {
3021 bool split = !!(flags & OPEN_SPLIT);
3022 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3023 bool nomaximize = !!(flags & OPEN_REFRESH);
3024 struct view *view = VIEW(request);
3025 int nviews = displayed_views();
3026 struct view *base_view = display[0];
3028 if (view == prev && nviews == 1 && !reload) {
3029 report("Already in %s view", view->name);
3030 return;
3031 }
3033 if (view->git_dir && !opt_git_dir[0]) {
3034 report("The %s view is disabled in pager view", view->name);
3035 return;
3036 }
3038 if (split) {
3039 display[1] = view;
3040 current_view = 1;
3041 } else if (!nomaximize) {
3042 /* Maximize the current view. */
3043 memset(display, 0, sizeof(display));
3044 current_view = 0;
3045 display[current_view] = view;
3046 }
3048 /* Resize the view when switching between split- and full-screen,
3049 * or when switching between two different full-screen views. */
3050 if (nviews != displayed_views() ||
3051 (nviews == 1 && base_view != display[0]))
3052 resize_display();
3054 if (view->ops->open) {
3055 if (view->pipe)
3056 end_update(view, TRUE);
3057 if (!view->ops->open(view)) {
3058 report("Failed to load %s view", view->name);
3059 return;
3060 }
3061 restore_view_position(view);
3063 } else if ((reload || strcmp(view->vid, view->id)) &&
3064 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3065 report("Failed to load %s view", view->name);
3066 return;
3067 }
3069 if (split && prev->lineno - prev->offset >= prev->height) {
3070 /* Take the title line into account. */
3071 int lines = prev->lineno - prev->offset - prev->height + 1;
3073 /* Scroll the view that was split if the current line is
3074 * outside the new limited view. */
3075 do_scroll_view(prev, lines);
3076 }
3078 if (prev && view != prev) {
3079 if (split) {
3080 /* "Blur" the previous view. */
3081 update_view_title(prev);
3082 }
3084 view->parent = prev;
3085 }
3087 if (view->pipe && view->lines == 0) {
3088 /* Clear the old view and let the incremental updating refill
3089 * the screen. */
3090 werase(view->win);
3091 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3092 report("");
3093 } else if (view_is_displayed(view)) {
3094 redraw_view(view);
3095 report("");
3096 }
3097 }
3099 static void
3100 open_external_viewer(const char *argv[], const char *dir)
3101 {
3102 def_prog_mode(); /* save current tty modes */
3103 endwin(); /* restore original tty modes */
3104 run_io_fg(argv, dir);
3105 fprintf(stderr, "Press Enter to continue");
3106 getc(opt_tty);
3107 reset_prog_mode();
3108 redraw_display(TRUE);
3109 }
3111 static void
3112 open_mergetool(const char *file)
3113 {
3114 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3116 open_external_viewer(mergetool_argv, opt_cdup);
3117 }
3119 static void
3120 open_editor(bool from_root, const char *file)
3121 {
3122 const char *editor_argv[] = { "vi", file, NULL };
3123 const char *editor;
3125 editor = getenv("GIT_EDITOR");
3126 if (!editor && *opt_editor)
3127 editor = opt_editor;
3128 if (!editor)
3129 editor = getenv("VISUAL");
3130 if (!editor)
3131 editor = getenv("EDITOR");
3132 if (!editor)
3133 editor = "vi";
3135 editor_argv[0] = editor;
3136 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3137 }
3139 static void
3140 open_run_request(enum request request)
3141 {
3142 struct run_request *req = get_run_request(request);
3143 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3145 if (!req) {
3146 report("Unknown run request");
3147 return;
3148 }
3150 if (format_argv(argv, req->argv, FORMAT_ALL))
3151 open_external_viewer(argv, NULL);
3152 free_argv(argv);
3153 }
3155 /*
3156 * User request switch noodle
3157 */
3159 static int
3160 view_driver(struct view *view, enum request request)
3161 {
3162 int i;
3164 if (request == REQ_NONE)
3165 return TRUE;
3167 if (request > REQ_NONE) {
3168 open_run_request(request);
3169 /* FIXME: When all views can refresh always do this. */
3170 if (view == VIEW(REQ_VIEW_STATUS) ||
3171 view == VIEW(REQ_VIEW_MAIN) ||
3172 view == VIEW(REQ_VIEW_LOG) ||
3173 view == VIEW(REQ_VIEW_BRANCH) ||
3174 view == VIEW(REQ_VIEW_STAGE))
3175 request = REQ_REFRESH;
3176 else
3177 return TRUE;
3178 }
3180 if (view && view->lines) {
3181 request = view->ops->request(view, request, &view->line[view->lineno]);
3182 if (request == REQ_NONE)
3183 return TRUE;
3184 }
3186 switch (request) {
3187 case REQ_MOVE_UP:
3188 case REQ_MOVE_DOWN:
3189 case REQ_MOVE_PAGE_UP:
3190 case REQ_MOVE_PAGE_DOWN:
3191 case REQ_MOVE_FIRST_LINE:
3192 case REQ_MOVE_LAST_LINE:
3193 move_view(view, request);
3194 break;
3196 case REQ_SCROLL_LEFT:
3197 case REQ_SCROLL_RIGHT:
3198 case REQ_SCROLL_LINE_DOWN:
3199 case REQ_SCROLL_LINE_UP:
3200 case REQ_SCROLL_PAGE_DOWN:
3201 case REQ_SCROLL_PAGE_UP:
3202 scroll_view(view, request);
3203 break;
3205 case REQ_VIEW_BLAME:
3206 if (!opt_file[0]) {
3207 report("No file chosen, press %s to open tree view",
3208 get_key(REQ_VIEW_TREE));
3209 break;
3210 }
3211 open_view(view, request, OPEN_DEFAULT);
3212 break;
3214 case REQ_VIEW_BLOB:
3215 if (!ref_blob[0]) {
3216 report("No file chosen, press %s to open tree view",
3217 get_key(REQ_VIEW_TREE));
3218 break;
3219 }
3220 open_view(view, request, OPEN_DEFAULT);
3221 break;
3223 case REQ_VIEW_PAGER:
3224 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3225 report("No pager content, press %s to run command from prompt",
3226 get_key(REQ_PROMPT));
3227 break;
3228 }
3229 open_view(view, request, OPEN_DEFAULT);
3230 break;
3232 case REQ_VIEW_STAGE:
3233 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3234 report("No stage content, press %s to open the status view and choose file",
3235 get_key(REQ_VIEW_STATUS));
3236 break;
3237 }
3238 open_view(view, request, OPEN_DEFAULT);
3239 break;
3241 case REQ_VIEW_STATUS:
3242 if (opt_is_inside_work_tree == FALSE) {
3243 report("The status view requires a working tree");
3244 break;
3245 }
3246 open_view(view, request, OPEN_DEFAULT);
3247 break;
3249 case REQ_VIEW_MAIN:
3250 case REQ_VIEW_DIFF:
3251 case REQ_VIEW_LOG:
3252 case REQ_VIEW_TREE:
3253 case REQ_VIEW_HELP:
3254 case REQ_VIEW_BRANCH:
3255 open_view(view, request, OPEN_DEFAULT);
3256 break;
3258 case REQ_NEXT:
3259 case REQ_PREVIOUS:
3260 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3262 if ((view == VIEW(REQ_VIEW_DIFF) &&
3263 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3264 (view == VIEW(REQ_VIEW_DIFF) &&
3265 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3266 (view == VIEW(REQ_VIEW_STAGE) &&
3267 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3268 (view == VIEW(REQ_VIEW_BLOB) &&
3269 view->parent == VIEW(REQ_VIEW_TREE)) ||
3270 (view == VIEW(REQ_VIEW_MAIN) &&
3271 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3272 int line;
3274 view = view->parent;
3275 line = view->lineno;
3276 move_view(view, request);
3277 if (view_is_displayed(view))
3278 update_view_title(view);
3279 if (line != view->lineno)
3280 view->ops->request(view, REQ_ENTER,
3281 &view->line[view->lineno]);
3283 } else {
3284 move_view(view, request);
3285 }
3286 break;
3288 case REQ_VIEW_NEXT:
3289 {
3290 int nviews = displayed_views();
3291 int next_view = (current_view + 1) % nviews;
3293 if (next_view == current_view) {
3294 report("Only one view is displayed");
3295 break;
3296 }
3298 current_view = next_view;
3299 /* Blur out the title of the previous view. */
3300 update_view_title(view);
3301 report("");
3302 break;
3303 }
3304 case REQ_REFRESH:
3305 report("Refreshing is not yet supported for the %s view", view->name);
3306 break;
3308 case REQ_MAXIMIZE:
3309 if (displayed_views() == 2)
3310 maximize_view(view);
3311 break;
3313 case REQ_OPTIONS:
3314 open_option_menu();
3315 break;
3317 case REQ_TOGGLE_LINENO:
3318 toggle_view_option(&opt_line_number, "line numbers");
3319 break;
3321 case REQ_TOGGLE_DATE:
3322 toggle_view_option(&opt_date, "date display");
3323 break;
3325 case REQ_TOGGLE_AUTHOR:
3326 toggle_view_option(&opt_author, "author display");
3327 break;
3329 case REQ_TOGGLE_REV_GRAPH:
3330 toggle_view_option(&opt_rev_graph, "revision graph display");
3331 break;
3333 case REQ_TOGGLE_REFS:
3334 toggle_view_option(&opt_show_refs, "reference display");
3335 break;
3337 case REQ_TOGGLE_SORT_FIELD:
3338 case REQ_TOGGLE_SORT_ORDER:
3339 report("Sorting is not yet supported for the %s view", view->name);
3340 break;
3342 case REQ_SEARCH:
3343 case REQ_SEARCH_BACK:
3344 search_view(view, request);
3345 break;
3347 case REQ_FIND_NEXT:
3348 case REQ_FIND_PREV:
3349 find_next(view, request);
3350 break;
3352 case REQ_STOP_LOADING:
3353 for (i = 0; i < ARRAY_SIZE(views); i++) {
3354 view = &views[i];
3355 if (view->pipe)
3356 report("Stopped loading the %s view", view->name),
3357 end_update(view, TRUE);
3358 }
3359 break;
3361 case REQ_SHOW_VERSION:
3362 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3363 return TRUE;
3365 case REQ_SCREEN_REDRAW:
3366 redraw_display(TRUE);
3367 break;
3369 case REQ_EDIT:
3370 report("Nothing to edit");
3371 break;
3373 case REQ_ENTER:
3374 report("Nothing to enter");
3375 break;
3377 case REQ_VIEW_CLOSE:
3378 /* XXX: Mark closed views by letting view->parent point to the
3379 * view itself. Parents to closed view should never be
3380 * followed. */
3381 if (view->parent &&
3382 view->parent->parent != view->parent) {
3383 maximize_view(view->parent);
3384 view->parent = view;
3385 break;
3386 }
3387 /* Fall-through */
3388 case REQ_QUIT:
3389 return FALSE;
3391 default:
3392 report("Unknown key, press 'h' for help");
3393 return TRUE;
3394 }
3396 return TRUE;
3397 }
3400 /*
3401 * View backend utilities
3402 */
3404 enum sort_field {
3405 ORDERBY_NAME,
3406 ORDERBY_DATE,
3407 ORDERBY_AUTHOR,
3408 };
3410 struct sort_state {
3411 const enum sort_field *fields;
3412 size_t size, current;
3413 bool reverse;
3414 };
3416 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3417 #define get_sort_field(state) ((state).fields[(state).current])
3418 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3420 static void
3421 sort_view(struct view *view, enum request request, struct sort_state *state,
3422 int (*compare)(const void *, const void *))
3423 {
3424 switch (request) {
3425 case REQ_TOGGLE_SORT_FIELD:
3426 state->current = (state->current + 1) % state->size;
3427 break;
3429 case REQ_TOGGLE_SORT_ORDER:
3430 state->reverse = !state->reverse;
3431 break;
3432 default:
3433 die("Not a sort request");
3434 }
3436 qsort(view->line, view->lines, sizeof(*view->line), compare);
3437 redraw_view(view);
3438 }
3440 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3442 /* Small author cache to reduce memory consumption. It uses binary
3443 * search to lookup or find place to position new entries. No entries
3444 * are ever freed. */
3445 static const char *
3446 get_author(const char *name)
3447 {
3448 static const char **authors;
3449 static size_t authors_size;
3450 int from = 0, to = authors_size - 1;
3452 while (from <= to) {
3453 size_t pos = (to + from) / 2;
3454 int cmp = strcmp(name, authors[pos]);
3456 if (!cmp)
3457 return authors[pos];
3459 if (cmp < 0)
3460 to = pos - 1;
3461 else
3462 from = pos + 1;
3463 }
3465 if (!realloc_authors(&authors, authors_size, 1))
3466 return NULL;
3467 name = strdup(name);
3468 if (!name)
3469 return NULL;
3471 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3472 authors[from] = name;
3473 authors_size++;
3475 return name;
3476 }
3478 static void
3479 parse_timezone(time_t *time, const char *zone)
3480 {
3481 long tz;
3483 tz = ('0' - zone[1]) * 60 * 60 * 10;
3484 tz += ('0' - zone[2]) * 60 * 60;
3485 tz += ('0' - zone[3]) * 60;
3486 tz += ('0' - zone[4]);
3488 if (zone[0] == '-')
3489 tz = -tz;
3491 *time -= tz;
3492 }
3494 /* Parse author lines where the name may be empty:
3495 * author <email@address.tld> 1138474660 +0100
3496 */
3497 static void
3498 parse_author_line(char *ident, const char **author, time_t *time)
3499 {
3500 char *nameend = strchr(ident, '<');
3501 char *emailend = strchr(ident, '>');
3503 if (nameend && emailend)
3504 *nameend = *emailend = 0;
3505 ident = chomp_string(ident);
3506 if (!*ident) {
3507 if (nameend)
3508 ident = chomp_string(nameend + 1);
3509 if (!*ident)
3510 ident = "Unknown";
3511 }
3513 *author = get_author(ident);
3515 /* Parse epoch and timezone */
3516 if (emailend && emailend[1] == ' ') {
3517 char *secs = emailend + 2;
3518 char *zone = strchr(secs, ' ');
3520 *time = (time_t) atol(secs);
3522 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3523 parse_timezone(time, zone + 1);
3524 }
3525 }
3527 static bool
3528 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3529 {
3530 char rev[SIZEOF_REV];
3531 const char *revlist_argv[] = {
3532 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3533 };
3534 struct menu_item *items;
3535 char text[SIZEOF_STR];
3536 bool ok = TRUE;
3537 int i;
3539 items = calloc(*parents + 1, sizeof(*items));
3540 if (!items)
3541 return FALSE;
3543 for (i = 0; i < *parents; i++) {
3544 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3545 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3546 !(items[i].text = strdup(text))) {
3547 ok = FALSE;
3548 break;
3549 }
3550 }
3552 if (ok) {
3553 *parents = 0;
3554 ok = prompt_menu("Select parent", items, parents);
3555 }
3556 for (i = 0; items[i].text; i++)
3557 free((char *) items[i].text);
3558 free(items);
3559 return ok;
3560 }
3562 static bool
3563 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3564 {
3565 char buf[SIZEOF_STR * 4];
3566 const char *revlist_argv[] = {
3567 "git", "log", "--no-color", "-1",
3568 "--pretty=format:%P", id, "--", path, NULL
3569 };
3570 int parents;
3572 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3573 (parents = strlen(buf) / 40) < 0) {
3574 report("Failed to get parent information");
3575 return FALSE;
3577 } else if (parents == 0) {
3578 if (path)
3579 report("Path '%s' does not exist in the parent", path);
3580 else
3581 report("The selected commit has no parents");
3582 return FALSE;
3583 }
3585 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3586 return FALSE;
3588 string_copy_rev(rev, &buf[41 * parents]);
3589 return TRUE;
3590 }
3592 /*
3593 * Pager backend
3594 */
3596 static bool
3597 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3598 {
3599 char text[SIZEOF_STR];
3601 if (opt_line_number && draw_lineno(view, lineno))
3602 return TRUE;
3604 string_expand(text, sizeof(text), line->data, opt_tab_size);
3605 draw_text(view, line->type, text, TRUE);
3606 return TRUE;
3607 }
3609 static bool
3610 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3611 {
3612 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3613 char ref[SIZEOF_STR];
3615 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3616 return TRUE;
3618 /* This is the only fatal call, since it can "corrupt" the buffer. */
3619 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3620 return FALSE;
3622 return TRUE;
3623 }
3625 static void
3626 add_pager_refs(struct view *view, struct line *line)
3627 {
3628 char buf[SIZEOF_STR];
3629 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3630 struct ref_list *list;
3631 size_t bufpos = 0, i;
3632 const char *sep = "Refs: ";
3633 bool is_tag = FALSE;
3635 assert(line->type == LINE_COMMIT);
3637 list = get_ref_list(commit_id);
3638 if (!list) {
3639 if (view == VIEW(REQ_VIEW_DIFF))
3640 goto try_add_describe_ref;
3641 return;
3642 }
3644 for (i = 0; i < list->size; i++) {
3645 struct ref *ref = list->refs[i];
3646 const char *fmt = ref->tag ? "%s[%s]" :
3647 ref->remote ? "%s<%s>" : "%s%s";
3649 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3650 return;
3651 sep = ", ";
3652 if (ref->tag)
3653 is_tag = TRUE;
3654 }
3656 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3657 try_add_describe_ref:
3658 /* Add <tag>-g<commit_id> "fake" reference. */
3659 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3660 return;
3661 }
3663 if (bufpos == 0)
3664 return;
3666 add_line_text(view, buf, LINE_PP_REFS);
3667 }
3669 static bool
3670 pager_read(struct view *view, char *data)
3671 {
3672 struct line *line;
3674 if (!data)
3675 return TRUE;
3677 line = add_line_text(view, data, get_line_type(data));
3678 if (!line)
3679 return FALSE;
3681 if (line->type == LINE_COMMIT &&
3682 (view == VIEW(REQ_VIEW_DIFF) ||
3683 view == VIEW(REQ_VIEW_LOG)))
3684 add_pager_refs(view, line);
3686 return TRUE;
3687 }
3689 static enum request
3690 pager_request(struct view *view, enum request request, struct line *line)
3691 {
3692 int split = 0;
3694 if (request != REQ_ENTER)
3695 return request;
3697 if (line->type == LINE_COMMIT &&
3698 (view == VIEW(REQ_VIEW_LOG) ||
3699 view == VIEW(REQ_VIEW_PAGER))) {
3700 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3701 split = 1;
3702 }
3704 /* Always scroll the view even if it was split. That way
3705 * you can use Enter to scroll through the log view and
3706 * split open each commit diff. */
3707 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3709 /* FIXME: A minor workaround. Scrolling the view will call report("")
3710 * but if we are scrolling a non-current view this won't properly
3711 * update the view title. */
3712 if (split)
3713 update_view_title(view);
3715 return REQ_NONE;
3716 }
3718 static bool
3719 pager_grep(struct view *view, struct line *line)
3720 {
3721 const char *text[] = { line->data, NULL };
3723 return grep_text(view, text);
3724 }
3726 static void
3727 pager_select(struct view *view, struct line *line)
3728 {
3729 if (line->type == LINE_COMMIT) {
3730 char *text = (char *)line->data + STRING_SIZE("commit ");
3732 if (view != VIEW(REQ_VIEW_PAGER))
3733 string_copy_rev(view->ref, text);
3734 string_copy_rev(ref_commit, text);
3735 }
3736 }
3738 static struct view_ops pager_ops = {
3739 "line",
3740 NULL,
3741 NULL,
3742 pager_read,
3743 pager_draw,
3744 pager_request,
3745 pager_grep,
3746 pager_select,
3747 };
3749 static const char *log_argv[SIZEOF_ARG] = {
3750 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3751 };
3753 static enum request
3754 log_request(struct view *view, enum request request, struct line *line)
3755 {
3756 switch (request) {
3757 case REQ_REFRESH:
3758 load_refs();
3759 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3760 return REQ_NONE;
3761 default:
3762 return pager_request(view, request, line);
3763 }
3764 }
3766 static struct view_ops log_ops = {
3767 "line",
3768 log_argv,
3769 NULL,
3770 pager_read,
3771 pager_draw,
3772 log_request,
3773 pager_grep,
3774 pager_select,
3775 };
3777 static const char *diff_argv[SIZEOF_ARG] = {
3778 "git", "show", "--pretty=fuller", "--no-color", "--root",
3779 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3780 };
3782 static struct view_ops diff_ops = {
3783 "line",
3784 diff_argv,
3785 NULL,
3786 pager_read,
3787 pager_draw,
3788 pager_request,
3789 pager_grep,
3790 pager_select,
3791 };
3793 /*
3794 * Help backend
3795 */
3797 static bool
3798 help_open(struct view *view)
3799 {
3800 char buf[SIZEOF_STR];
3801 size_t bufpos;
3802 int i;
3804 if (view->lines > 0)
3805 return TRUE;
3807 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3809 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3810 const char *key;
3812 if (req_info[i].request == REQ_NONE)
3813 continue;
3815 if (!req_info[i].request) {
3816 add_line_text(view, "", LINE_DEFAULT);
3817 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3818 continue;
3819 }
3821 key = get_key(req_info[i].request);
3822 if (!*key)
3823 key = "(no key defined)";
3825 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3826 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3827 if (buf[bufpos] == '_')
3828 buf[bufpos] = '-';
3829 }
3831 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3832 key, buf, req_info[i].help);
3833 }
3835 if (run_requests) {
3836 add_line_text(view, "", LINE_DEFAULT);
3837 add_line_text(view, "External commands:", LINE_DEFAULT);
3838 }
3840 for (i = 0; i < run_requests; i++) {
3841 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3842 const char *key;
3843 int argc;
3845 if (!req)
3846 continue;
3848 key = get_key_name(req->key);
3849 if (!*key)
3850 key = "(no key defined)";
3852 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3853 if (!string_format_from(buf, &bufpos, "%s%s",
3854 argc ? " " : "", req->argv[argc]))
3855 return REQ_NONE;
3857 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3858 keymap_table[req->keymap].name, key, buf);
3859 }
3861 return TRUE;
3862 }
3864 static struct view_ops help_ops = {
3865 "line",
3866 NULL,
3867 help_open,
3868 NULL,
3869 pager_draw,
3870 pager_request,
3871 pager_grep,
3872 pager_select,
3873 };
3876 /*
3877 * Tree backend
3878 */
3880 struct tree_stack_entry {
3881 struct tree_stack_entry *prev; /* Entry below this in the stack */
3882 unsigned long lineno; /* Line number to restore */
3883 char *name; /* Position of name in opt_path */
3884 };
3886 /* The top of the path stack. */
3887 static struct tree_stack_entry *tree_stack = NULL;
3888 unsigned long tree_lineno = 0;
3890 static void
3891 pop_tree_stack_entry(void)
3892 {
3893 struct tree_stack_entry *entry = tree_stack;
3895 tree_lineno = entry->lineno;
3896 entry->name[0] = 0;
3897 tree_stack = entry->prev;
3898 free(entry);
3899 }
3901 static void
3902 push_tree_stack_entry(const char *name, unsigned long lineno)
3903 {
3904 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3905 size_t pathlen = strlen(opt_path);
3907 if (!entry)
3908 return;
3910 entry->prev = tree_stack;
3911 entry->name = opt_path + pathlen;
3912 tree_stack = entry;
3914 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3915 pop_tree_stack_entry();
3916 return;
3917 }
3919 /* Move the current line to the first tree entry. */
3920 tree_lineno = 1;
3921 entry->lineno = lineno;
3922 }
3924 /* Parse output from git-ls-tree(1):
3925 *
3926 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3927 */
3929 #define SIZEOF_TREE_ATTR \
3930 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3932 #define SIZEOF_TREE_MODE \
3933 STRING_SIZE("100644 ")
3935 #define TREE_ID_OFFSET \
3936 STRING_SIZE("100644 blob ")
3938 struct tree_entry {
3939 char id[SIZEOF_REV];
3940 mode_t mode;
3941 time_t time; /* Date from the author ident. */
3942 const char *author; /* Author of the commit. */
3943 char name[1];
3944 };
3946 static const char *
3947 tree_path(const struct line *line)
3948 {
3949 return ((struct tree_entry *) line->data)->name;
3950 }
3952 static int
3953 tree_compare_entry(const struct line *line1, const struct line *line2)
3954 {
3955 if (line1->type != line2->type)
3956 return line1->type == LINE_TREE_DIR ? -1 : 1;
3957 return strcmp(tree_path(line1), tree_path(line2));
3958 }
3960 static const enum sort_field tree_sort_fields[] = {
3961 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
3962 };
3963 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
3965 static int
3966 tree_compare(const void *l1, const void *l2)
3967 {
3968 const struct line *line1 = (const struct line *) l1;
3969 const struct line *line2 = (const struct line *) l2;
3970 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
3971 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
3973 if (line1->type == LINE_TREE_HEAD)
3974 return -1;
3975 if (line2->type == LINE_TREE_HEAD)
3976 return 1;
3978 switch (get_sort_field(tree_sort_state)) {
3979 case ORDERBY_DATE:
3980 return sort_order(tree_sort_state, entry1->time - entry2->time);
3982 case ORDERBY_AUTHOR:
3983 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
3985 case ORDERBY_NAME:
3986 default:
3987 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
3988 }
3989 }
3992 static struct line *
3993 tree_entry(struct view *view, enum line_type type, const char *path,
3994 const char *mode, const char *id)
3995 {
3996 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3997 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3999 if (!entry || !line) {
4000 free(entry);
4001 return NULL;
4002 }
4004 strncpy(entry->name, path, strlen(path));
4005 if (mode)
4006 entry->mode = strtoul(mode, NULL, 8);
4007 if (id)
4008 string_copy_rev(entry->id, id);
4010 return line;
4011 }
4013 static bool
4014 tree_read_date(struct view *view, char *text, bool *read_date)
4015 {
4016 static const char *author_name;
4017 static time_t author_time;
4019 if (!text && *read_date) {
4020 *read_date = FALSE;
4021 return TRUE;
4023 } else if (!text) {
4024 char *path = *opt_path ? opt_path : ".";
4025 /* Find next entry to process */
4026 const char *log_file[] = {
4027 "git", "log", "--no-color", "--pretty=raw",
4028 "--cc", "--raw", view->id, "--", path, NULL
4029 };
4030 struct io io = {};
4032 if (!view->lines) {
4033 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4034 report("Tree is empty");
4035 return TRUE;
4036 }
4038 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
4039 report("Failed to load tree data");
4040 return TRUE;
4041 }
4043 done_io(view->pipe);
4044 view->io = io;
4045 *read_date = TRUE;
4046 return FALSE;
4048 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4049 parse_author_line(text + STRING_SIZE("author "),
4050 &author_name, &author_time);
4052 } else if (*text == ':') {
4053 char *pos;
4054 size_t annotated = 1;
4055 size_t i;
4057 pos = strchr(text, '\t');
4058 if (!pos)
4059 return TRUE;
4060 text = pos + 1;
4061 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4062 text += strlen(opt_prefix);
4063 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4064 text += strlen(opt_path);
4065 pos = strchr(text, '/');
4066 if (pos)
4067 *pos = 0;
4069 for (i = 1; i < view->lines; i++) {
4070 struct line *line = &view->line[i];
4071 struct tree_entry *entry = line->data;
4073 annotated += !!entry->author;
4074 if (entry->author || strcmp(entry->name, text))
4075 continue;
4077 entry->author = author_name;
4078 entry->time = author_time;
4079 line->dirty = 1;
4080 break;
4081 }
4083 if (annotated == view->lines)
4084 kill_io(view->pipe);
4085 }
4086 return TRUE;
4087 }
4089 static bool
4090 tree_read(struct view *view, char *text)
4091 {
4092 static bool read_date = FALSE;
4093 struct tree_entry *data;
4094 struct line *entry, *line;
4095 enum line_type type;
4096 size_t textlen = text ? strlen(text) : 0;
4097 char *path = text + SIZEOF_TREE_ATTR;
4099 if (read_date || !text)
4100 return tree_read_date(view, text, &read_date);
4102 if (textlen <= SIZEOF_TREE_ATTR)
4103 return FALSE;
4104 if (view->lines == 0 &&
4105 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4106 return FALSE;
4108 /* Strip the path part ... */
4109 if (*opt_path) {
4110 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4111 size_t striplen = strlen(opt_path);
4113 if (pathlen > striplen)
4114 memmove(path, path + striplen,
4115 pathlen - striplen + 1);
4117 /* Insert "link" to parent directory. */
4118 if (view->lines == 1 &&
4119 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4120 return FALSE;
4121 }
4123 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4124 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4125 if (!entry)
4126 return FALSE;
4127 data = entry->data;
4129 /* Skip "Directory ..." and ".." line. */
4130 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4131 if (tree_compare_entry(line, entry) <= 0)
4132 continue;
4134 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4136 line->data = data;
4137 line->type = type;
4138 for (; line <= entry; line++)
4139 line->dirty = line->cleareol = 1;
4140 return TRUE;
4141 }
4143 if (tree_lineno > view->lineno) {
4144 view->lineno = tree_lineno;
4145 tree_lineno = 0;
4146 }
4148 return TRUE;
4149 }
4151 static bool
4152 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4153 {
4154 struct tree_entry *entry = line->data;
4156 if (line->type == LINE_TREE_HEAD) {
4157 if (draw_text(view, line->type, "Directory path /", TRUE))
4158 return TRUE;
4159 } else {
4160 if (draw_mode(view, entry->mode))
4161 return TRUE;
4163 if (opt_author && draw_author(view, entry->author))
4164 return TRUE;
4166 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4167 return TRUE;
4168 }
4169 if (draw_text(view, line->type, entry->name, TRUE))
4170 return TRUE;
4171 return TRUE;
4172 }
4174 static void
4175 open_blob_editor()
4176 {
4177 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4178 int fd = mkstemp(file);
4180 if (fd == -1)
4181 report("Failed to create temporary file");
4182 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4183 report("Failed to save blob data to file");
4184 else
4185 open_editor(FALSE, file);
4186 if (fd != -1)
4187 unlink(file);
4188 }
4190 static enum request
4191 tree_request(struct view *view, enum request request, struct line *line)
4192 {
4193 enum open_flags flags;
4195 switch (request) {
4196 case REQ_VIEW_BLAME:
4197 if (line->type != LINE_TREE_FILE) {
4198 report("Blame only supported for files");
4199 return REQ_NONE;
4200 }
4202 string_copy(opt_ref, view->vid);
4203 return request;
4205 case REQ_EDIT:
4206 if (line->type != LINE_TREE_FILE) {
4207 report("Edit only supported for files");
4208 } else if (!is_head_commit(view->vid)) {
4209 open_blob_editor();
4210 } else {
4211 open_editor(TRUE, opt_file);
4212 }
4213 return REQ_NONE;
4215 case REQ_TOGGLE_SORT_FIELD:
4216 case REQ_TOGGLE_SORT_ORDER:
4217 sort_view(view, request, &tree_sort_state, tree_compare);
4218 return REQ_NONE;
4220 case REQ_PARENT:
4221 if (!*opt_path) {
4222 /* quit view if at top of tree */
4223 return REQ_VIEW_CLOSE;
4224 }
4225 /* fake 'cd ..' */
4226 line = &view->line[1];
4227 break;
4229 case REQ_ENTER:
4230 break;
4232 default:
4233 return request;
4234 }
4236 /* Cleanup the stack if the tree view is at a different tree. */
4237 while (!*opt_path && tree_stack)
4238 pop_tree_stack_entry();
4240 switch (line->type) {
4241 case LINE_TREE_DIR:
4242 /* Depending on whether it is a subdirectory or parent link
4243 * mangle the path buffer. */
4244 if (line == &view->line[1] && *opt_path) {
4245 pop_tree_stack_entry();
4247 } else {
4248 const char *basename = tree_path(line);
4250 push_tree_stack_entry(basename, view->lineno);
4251 }
4253 /* Trees and subtrees share the same ID, so they are not not
4254 * unique like blobs. */
4255 flags = OPEN_RELOAD;
4256 request = REQ_VIEW_TREE;
4257 break;
4259 case LINE_TREE_FILE:
4260 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4261 request = REQ_VIEW_BLOB;
4262 break;
4264 default:
4265 return REQ_NONE;
4266 }
4268 open_view(view, request, flags);
4269 if (request == REQ_VIEW_TREE)
4270 view->lineno = tree_lineno;
4272 return REQ_NONE;
4273 }
4275 static bool
4276 tree_grep(struct view *view, struct line *line)
4277 {
4278 struct tree_entry *entry = line->data;
4279 const char *text[] = {
4280 entry->name,
4281 opt_author ? entry->author : "",
4282 opt_date ? mkdate(&entry->time) : "",
4283 NULL
4284 };
4286 return grep_text(view, text);
4287 }
4289 static void
4290 tree_select(struct view *view, struct line *line)
4291 {
4292 struct tree_entry *entry = line->data;
4294 if (line->type == LINE_TREE_FILE) {
4295 string_copy_rev(ref_blob, entry->id);
4296 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4298 } else if (line->type != LINE_TREE_DIR) {
4299 return;
4300 }
4302 string_copy_rev(view->ref, entry->id);
4303 }
4305 static const char *tree_argv[SIZEOF_ARG] = {
4306 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4307 };
4309 static struct view_ops tree_ops = {
4310 "file",
4311 tree_argv,
4312 NULL,
4313 tree_read,
4314 tree_draw,
4315 tree_request,
4316 tree_grep,
4317 tree_select,
4318 };
4320 static bool
4321 blob_read(struct view *view, char *line)
4322 {
4323 if (!line)
4324 return TRUE;
4325 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4326 }
4328 static enum request
4329 blob_request(struct view *view, enum request request, struct line *line)
4330 {
4331 switch (request) {
4332 case REQ_EDIT:
4333 open_blob_editor();
4334 return REQ_NONE;
4335 default:
4336 return pager_request(view, request, line);
4337 }
4338 }
4340 static const char *blob_argv[SIZEOF_ARG] = {
4341 "git", "cat-file", "blob", "%(blob)", NULL
4342 };
4344 static struct view_ops blob_ops = {
4345 "line",
4346 blob_argv,
4347 NULL,
4348 blob_read,
4349 pager_draw,
4350 blob_request,
4351 pager_grep,
4352 pager_select,
4353 };
4355 /*
4356 * Blame backend
4357 *
4358 * Loading the blame view is a two phase job:
4359 *
4360 * 1. File content is read either using opt_file from the
4361 * filesystem or using git-cat-file.
4362 * 2. Then blame information is incrementally added by
4363 * reading output from git-blame.
4364 */
4366 static const char *blame_head_argv[] = {
4367 "git", "blame", "--incremental", "--", "%(file)", NULL
4368 };
4370 static const char *blame_ref_argv[] = {
4371 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4372 };
4374 static const char *blame_cat_file_argv[] = {
4375 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4376 };
4378 struct blame_commit {
4379 char id[SIZEOF_REV]; /* SHA1 ID. */
4380 char title[128]; /* First line of the commit message. */
4381 const char *author; /* Author of the commit. */
4382 time_t time; /* Date from the author ident. */
4383 char filename[128]; /* Name of file. */
4384 bool has_previous; /* Was a "previous" line detected. */
4385 };
4387 struct blame {
4388 struct blame_commit *commit;
4389 unsigned long lineno;
4390 char text[1];
4391 };
4393 static bool
4394 blame_open(struct view *view)
4395 {
4396 if (*opt_ref || !io_open(&view->io, opt_file)) {
4397 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4398 return FALSE;
4399 }
4401 setup_update(view, opt_file);
4402 string_format(view->ref, "%s ...", opt_file);
4404 return TRUE;
4405 }
4407 static struct blame_commit *
4408 get_blame_commit(struct view *view, const char *id)
4409 {
4410 size_t i;
4412 for (i = 0; i < view->lines; i++) {
4413 struct blame *blame = view->line[i].data;
4415 if (!blame->commit)
4416 continue;
4418 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4419 return blame->commit;
4420 }
4422 {
4423 struct blame_commit *commit = calloc(1, sizeof(*commit));
4425 if (commit)
4426 string_ncopy(commit->id, id, SIZEOF_REV);
4427 return commit;
4428 }
4429 }
4431 static bool
4432 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4433 {
4434 const char *pos = *posref;
4436 *posref = NULL;
4437 pos = strchr(pos + 1, ' ');
4438 if (!pos || !isdigit(pos[1]))
4439 return FALSE;
4440 *number = atoi(pos + 1);
4441 if (*number < min || *number > max)
4442 return FALSE;
4444 *posref = pos;
4445 return TRUE;
4446 }
4448 static struct blame_commit *
4449 parse_blame_commit(struct view *view, const char *text, int *blamed)
4450 {
4451 struct blame_commit *commit;
4452 struct blame *blame;
4453 const char *pos = text + SIZEOF_REV - 2;
4454 size_t orig_lineno = 0;
4455 size_t lineno;
4456 size_t group;
4458 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4459 return NULL;
4461 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4462 !parse_number(&pos, &lineno, 1, view->lines) ||
4463 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4464 return NULL;
4466 commit = get_blame_commit(view, text);
4467 if (!commit)
4468 return NULL;
4470 *blamed += group;
4471 while (group--) {
4472 struct line *line = &view->line[lineno + group - 1];
4474 blame = line->data;
4475 blame->commit = commit;
4476 blame->lineno = orig_lineno + group - 1;
4477 line->dirty = 1;
4478 }
4480 return commit;
4481 }
4483 static bool
4484 blame_read_file(struct view *view, const char *line, bool *read_file)
4485 {
4486 if (!line) {
4487 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4488 struct io io = {};
4490 if (view->lines == 0 && !view->parent)
4491 die("No blame exist for %s", view->vid);
4493 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4494 report("Failed to load blame data");
4495 return TRUE;
4496 }
4498 done_io(view->pipe);
4499 view->io = io;
4500 *read_file = FALSE;
4501 return FALSE;
4503 } else {
4504 size_t linelen = strlen(line);
4505 struct blame *blame = malloc(sizeof(*blame) + linelen);
4507 if (!blame)
4508 return FALSE;
4510 blame->commit = NULL;
4511 strncpy(blame->text, line, linelen);
4512 blame->text[linelen] = 0;
4513 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4514 }
4515 }
4517 static bool
4518 match_blame_header(const char *name, char **line)
4519 {
4520 size_t namelen = strlen(name);
4521 bool matched = !strncmp(name, *line, namelen);
4523 if (matched)
4524 *line += namelen;
4526 return matched;
4527 }
4529 static bool
4530 blame_read(struct view *view, char *line)
4531 {
4532 static struct blame_commit *commit = NULL;
4533 static int blamed = 0;
4534 static bool read_file = TRUE;
4536 if (read_file)
4537 return blame_read_file(view, line, &read_file);
4539 if (!line) {
4540 /* Reset all! */
4541 commit = NULL;
4542 blamed = 0;
4543 read_file = TRUE;
4544 string_format(view->ref, "%s", view->vid);
4545 if (view_is_displayed(view)) {
4546 update_view_title(view);
4547 redraw_view_from(view, 0);
4548 }
4549 return TRUE;
4550 }
4552 if (!commit) {
4553 commit = parse_blame_commit(view, line, &blamed);
4554 string_format(view->ref, "%s %2d%%", view->vid,
4555 view->lines ? blamed * 100 / view->lines : 0);
4557 } else if (match_blame_header("author ", &line)) {
4558 commit->author = get_author(line);
4560 } else if (match_blame_header("author-time ", &line)) {
4561 commit->time = (time_t) atol(line);
4563 } else if (match_blame_header("author-tz ", &line)) {
4564 parse_timezone(&commit->time, line);
4566 } else if (match_blame_header("summary ", &line)) {
4567 string_ncopy(commit->title, line, strlen(line));
4569 } else if (match_blame_header("previous ", &line)) {
4570 commit->has_previous = TRUE;
4572 } else if (match_blame_header("filename ", &line)) {
4573 string_ncopy(commit->filename, line, strlen(line));
4574 commit = NULL;
4575 }
4577 return TRUE;
4578 }
4580 static bool
4581 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4582 {
4583 struct blame *blame = line->data;
4584 time_t *time = NULL;
4585 const char *id = NULL, *author = NULL;
4586 char text[SIZEOF_STR];
4588 if (blame->commit && *blame->commit->filename) {
4589 id = blame->commit->id;
4590 author = blame->commit->author;
4591 time = &blame->commit->time;
4592 }
4594 if (opt_date && draw_date(view, time))
4595 return TRUE;
4597 if (opt_author && draw_author(view, author))
4598 return TRUE;
4600 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4601 return TRUE;
4603 if (draw_lineno(view, lineno))
4604 return TRUE;
4606 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4607 draw_text(view, LINE_DEFAULT, text, TRUE);
4608 return TRUE;
4609 }
4611 static bool
4612 check_blame_commit(struct blame *blame, bool check_null_id)
4613 {
4614 if (!blame->commit)
4615 report("Commit data not loaded yet");
4616 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4617 report("No commit exist for the selected line");
4618 else
4619 return TRUE;
4620 return FALSE;
4621 }
4623 static void
4624 setup_blame_parent_line(struct view *view, struct blame *blame)
4625 {
4626 const char *diff_tree_argv[] = {
4627 "git", "diff-tree", "-U0", blame->commit->id,
4628 "--", blame->commit->filename, NULL
4629 };
4630 struct io io = {};
4631 int parent_lineno = -1;
4632 int blamed_lineno = -1;
4633 char *line;
4635 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4636 return;
4638 while ((line = io_get(&io, '\n', TRUE))) {
4639 if (*line == '@') {
4640 char *pos = strchr(line, '+');
4642 parent_lineno = atoi(line + 4);
4643 if (pos)
4644 blamed_lineno = atoi(pos + 1);
4646 } else if (*line == '+' && parent_lineno != -1) {
4647 if (blame->lineno == blamed_lineno - 1 &&
4648 !strcmp(blame->text, line + 1)) {
4649 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4650 break;
4651 }
4652 blamed_lineno++;
4653 }
4654 }
4656 done_io(&io);
4657 }
4659 static enum request
4660 blame_request(struct view *view, enum request request, struct line *line)
4661 {
4662 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4663 struct blame *blame = line->data;
4665 switch (request) {
4666 case REQ_VIEW_BLAME:
4667 if (check_blame_commit(blame, TRUE)) {
4668 string_copy(opt_ref, blame->commit->id);
4669 string_copy(opt_file, blame->commit->filename);
4670 if (blame->lineno)
4671 view->lineno = blame->lineno;
4672 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4673 }
4674 break;
4676 case REQ_PARENT:
4677 if (check_blame_commit(blame, TRUE) &&
4678 select_commit_parent(blame->commit->id, opt_ref,
4679 blame->commit->filename)) {
4680 string_copy(opt_file, blame->commit->filename);
4681 setup_blame_parent_line(view, blame);
4682 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4683 }
4684 break;
4686 case REQ_ENTER:
4687 if (!check_blame_commit(blame, FALSE))
4688 break;
4690 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4691 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4692 break;
4694 if (!strcmp(blame->commit->id, NULL_ID)) {
4695 struct view *diff = VIEW(REQ_VIEW_DIFF);
4696 const char *diff_index_argv[] = {
4697 "git", "diff-index", "--root", "--patch-with-stat",
4698 "-C", "-M", "HEAD", "--", view->vid, NULL
4699 };
4701 if (!blame->commit->has_previous) {
4702 diff_index_argv[1] = "diff";
4703 diff_index_argv[2] = "--no-color";
4704 diff_index_argv[6] = "--";
4705 diff_index_argv[7] = "/dev/null";
4706 }
4708 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4709 report("Failed to allocate diff command");
4710 break;
4711 }
4712 flags |= OPEN_PREPARED;
4713 }
4715 open_view(view, REQ_VIEW_DIFF, flags);
4716 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4717 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4718 break;
4720 default:
4721 return request;
4722 }
4724 return REQ_NONE;
4725 }
4727 static bool
4728 blame_grep(struct view *view, struct line *line)
4729 {
4730 struct blame *blame = line->data;
4731 struct blame_commit *commit = blame->commit;
4732 const char *text[] = {
4733 blame->text,
4734 commit ? commit->title : "",
4735 commit ? commit->id : "",
4736 commit && opt_author ? commit->author : "",
4737 commit && opt_date ? mkdate(&commit->time) : "",
4738 NULL
4739 };
4741 return grep_text(view, text);
4742 }
4744 static void
4745 blame_select(struct view *view, struct line *line)
4746 {
4747 struct blame *blame = line->data;
4748 struct blame_commit *commit = blame->commit;
4750 if (!commit)
4751 return;
4753 if (!strcmp(commit->id, NULL_ID))
4754 string_ncopy(ref_commit, "HEAD", 4);
4755 else
4756 string_copy_rev(ref_commit, commit->id);
4757 }
4759 static struct view_ops blame_ops = {
4760 "line",
4761 NULL,
4762 blame_open,
4763 blame_read,
4764 blame_draw,
4765 blame_request,
4766 blame_grep,
4767 blame_select,
4768 };
4770 /*
4771 * Branch backend
4772 */
4774 struct branch {
4775 const char *author; /* Author of the last commit. */
4776 time_t time; /* Date of the last activity. */
4777 struct ref *ref; /* Name and commit ID information. */
4778 };
4780 static const enum sort_field branch_sort_fields[] = {
4781 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4782 };
4783 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4785 static int
4786 branch_compare(const void *l1, const void *l2)
4787 {
4788 const struct branch *branch1 = ((const struct line *) l1)->data;
4789 const struct branch *branch2 = ((const struct line *) l2)->data;
4791 switch (get_sort_field(branch_sort_state)) {
4792 case ORDERBY_DATE:
4793 return sort_order(branch_sort_state, branch1->time - branch2->time);
4795 case ORDERBY_AUTHOR:
4796 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4798 case ORDERBY_NAME:
4799 default:
4800 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4801 }
4802 }
4804 static bool
4805 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4806 {
4807 struct branch *branch = line->data;
4808 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4810 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4811 return TRUE;
4813 if (opt_author && draw_author(view, branch->author))
4814 return TRUE;
4816 draw_text(view, type, branch->ref->name, TRUE);
4817 return TRUE;
4818 }
4820 static enum request
4821 branch_request(struct view *view, enum request request, struct line *line)
4822 {
4823 switch (request) {
4824 case REQ_REFRESH:
4825 load_refs();
4826 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4827 return REQ_NONE;
4829 case REQ_TOGGLE_SORT_FIELD:
4830 case REQ_TOGGLE_SORT_ORDER:
4831 sort_view(view, request, &branch_sort_state, branch_compare);
4832 return REQ_NONE;
4834 case REQ_ENTER:
4835 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4836 return REQ_NONE;
4838 default:
4839 return request;
4840 }
4841 }
4843 static bool
4844 branch_read(struct view *view, char *line)
4845 {
4846 static char id[SIZEOF_REV];
4847 struct branch *reference;
4848 size_t i;
4850 if (!line)
4851 return TRUE;
4853 switch (get_line_type(line)) {
4854 case LINE_COMMIT:
4855 string_copy_rev(id, line + STRING_SIZE("commit "));
4856 return TRUE;
4858 case LINE_AUTHOR:
4859 for (i = 0, reference = NULL; i < view->lines; i++) {
4860 struct branch *branch = view->line[i].data;
4862 if (strcmp(branch->ref->id, id))
4863 continue;
4865 view->line[i].dirty = TRUE;
4866 if (reference) {
4867 branch->author = reference->author;
4868 branch->time = reference->time;
4869 continue;
4870 }
4872 parse_author_line(line + STRING_SIZE("author "),
4873 &branch->author, &branch->time);
4874 reference = branch;
4875 }
4876 return TRUE;
4878 default:
4879 return TRUE;
4880 }
4882 }
4884 static bool
4885 branch_open_visitor(void *data, struct ref *ref)
4886 {
4887 struct view *view = data;
4888 struct branch *branch;
4890 if (ref->tag || ref->ltag || ref->remote)
4891 return TRUE;
4893 branch = calloc(1, sizeof(*branch));
4894 if (!branch)
4895 return FALSE;
4897 branch->ref = ref;
4898 return !!add_line_data(view, branch, LINE_DEFAULT);
4899 }
4901 static bool
4902 branch_open(struct view *view)
4903 {
4904 const char *branch_log[] = {
4905 "git", "log", "--no-color", "--pretty=raw",
4906 "--simplify-by-decoration", "--all", NULL
4907 };
4909 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4910 report("Failed to load branch data");
4911 return TRUE;
4912 }
4914 setup_update(view, view->id);
4915 foreach_ref(branch_open_visitor, view);
4916 view->p_restore = TRUE;
4918 return TRUE;
4919 }
4921 static bool
4922 branch_grep(struct view *view, struct line *line)
4923 {
4924 struct branch *branch = line->data;
4925 const char *text[] = {
4926 branch->ref->name,
4927 branch->author,
4928 NULL
4929 };
4931 return grep_text(view, text);
4932 }
4934 static void
4935 branch_select(struct view *view, struct line *line)
4936 {
4937 struct branch *branch = line->data;
4939 string_copy_rev(view->ref, branch->ref->id);
4940 string_copy_rev(ref_commit, branch->ref->id);
4941 string_copy_rev(ref_head, branch->ref->id);
4942 }
4944 static struct view_ops branch_ops = {
4945 "branch",
4946 NULL,
4947 branch_open,
4948 branch_read,
4949 branch_draw,
4950 branch_request,
4951 branch_grep,
4952 branch_select,
4953 };
4955 /*
4956 * Status backend
4957 */
4959 struct status {
4960 char status;
4961 struct {
4962 mode_t mode;
4963 char rev[SIZEOF_REV];
4964 char name[SIZEOF_STR];
4965 } old;
4966 struct {
4967 mode_t mode;
4968 char rev[SIZEOF_REV];
4969 char name[SIZEOF_STR];
4970 } new;
4971 };
4973 static char status_onbranch[SIZEOF_STR];
4974 static struct status stage_status;
4975 static enum line_type stage_line_type;
4976 static size_t stage_chunks;
4977 static int *stage_chunk;
4979 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4981 /* This should work even for the "On branch" line. */
4982 static inline bool
4983 status_has_none(struct view *view, struct line *line)
4984 {
4985 return line < view->line + view->lines && !line[1].data;
4986 }
4988 /* Get fields from the diff line:
4989 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4990 */
4991 static inline bool
4992 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4993 {
4994 const char *old_mode = buf + 1;
4995 const char *new_mode = buf + 8;
4996 const char *old_rev = buf + 15;
4997 const char *new_rev = buf + 56;
4998 const char *status = buf + 97;
5000 if (bufsize < 98 ||
5001 old_mode[-1] != ':' ||
5002 new_mode[-1] != ' ' ||
5003 old_rev[-1] != ' ' ||
5004 new_rev[-1] != ' ' ||
5005 status[-1] != ' ')
5006 return FALSE;
5008 file->status = *status;
5010 string_copy_rev(file->old.rev, old_rev);
5011 string_copy_rev(file->new.rev, new_rev);
5013 file->old.mode = strtoul(old_mode, NULL, 8);
5014 file->new.mode = strtoul(new_mode, NULL, 8);
5016 file->old.name[0] = file->new.name[0] = 0;
5018 return TRUE;
5019 }
5021 static bool
5022 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5023 {
5024 struct status *unmerged = NULL;
5025 char *buf;
5026 struct io io = {};
5028 if (!run_io(&io, argv, NULL, IO_RD))
5029 return FALSE;
5031 add_line_data(view, NULL, type);
5033 while ((buf = io_get(&io, 0, TRUE))) {
5034 struct status *file = unmerged;
5036 if (!file) {
5037 file = calloc(1, sizeof(*file));
5038 if (!file || !add_line_data(view, file, type))
5039 goto error_out;
5040 }
5042 /* Parse diff info part. */
5043 if (status) {
5044 file->status = status;
5045 if (status == 'A')
5046 string_copy(file->old.rev, NULL_ID);
5048 } else if (!file->status || file == unmerged) {
5049 if (!status_get_diff(file, buf, strlen(buf)))
5050 goto error_out;
5052 buf = io_get(&io, 0, TRUE);
5053 if (!buf)
5054 break;
5056 /* Collapse all modified entries that follow an
5057 * associated unmerged entry. */
5058 if (unmerged == file) {
5059 unmerged->status = 'U';
5060 unmerged = NULL;
5061 } else if (file->status == 'U') {
5062 unmerged = file;
5063 }
5064 }
5066 /* Grab the old name for rename/copy. */
5067 if (!*file->old.name &&
5068 (file->status == 'R' || file->status == 'C')) {
5069 string_ncopy(file->old.name, buf, strlen(buf));
5071 buf = io_get(&io, 0, TRUE);
5072 if (!buf)
5073 break;
5074 }
5076 /* git-ls-files just delivers a NUL separated list of
5077 * file names similar to the second half of the
5078 * git-diff-* output. */
5079 string_ncopy(file->new.name, buf, strlen(buf));
5080 if (!*file->old.name)
5081 string_copy(file->old.name, file->new.name);
5082 file = NULL;
5083 }
5085 if (io_error(&io)) {
5086 error_out:
5087 done_io(&io);
5088 return FALSE;
5089 }
5091 if (!view->line[view->lines - 1].data)
5092 add_line_data(view, NULL, LINE_STAT_NONE);
5094 done_io(&io);
5095 return TRUE;
5096 }
5098 /* Don't show unmerged entries in the staged section. */
5099 static const char *status_diff_index_argv[] = {
5100 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5101 "--cached", "-M", "HEAD", NULL
5102 };
5104 static const char *status_diff_files_argv[] = {
5105 "git", "diff-files", "-z", NULL
5106 };
5108 static const char *status_list_other_argv[] = {
5109 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5110 };
5112 static const char *status_list_no_head_argv[] = {
5113 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5114 };
5116 static const char *update_index_argv[] = {
5117 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5118 };
5120 /* Restore the previous line number to stay in the context or select a
5121 * line with something that can be updated. */
5122 static void
5123 status_restore(struct view *view)
5124 {
5125 if (view->p_lineno >= view->lines)
5126 view->p_lineno = view->lines - 1;
5127 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5128 view->p_lineno++;
5129 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5130 view->p_lineno--;
5132 /* If the above fails, always skip the "On branch" line. */
5133 if (view->p_lineno < view->lines)
5134 view->lineno = view->p_lineno;
5135 else
5136 view->lineno = 1;
5138 if (view->lineno < view->offset)
5139 view->offset = view->lineno;
5140 else if (view->offset + view->height <= view->lineno)
5141 view->offset = view->lineno - view->height + 1;
5143 view->p_restore = FALSE;
5144 }
5146 static void
5147 status_update_onbranch(void)
5148 {
5149 static const char *paths[][2] = {
5150 { "rebase-apply/rebasing", "Rebasing" },
5151 { "rebase-apply/applying", "Applying mailbox" },
5152 { "rebase-apply/", "Rebasing mailbox" },
5153 { "rebase-merge/interactive", "Interactive rebase" },
5154 { "rebase-merge/", "Rebase merge" },
5155 { "MERGE_HEAD", "Merging" },
5156 { "BISECT_LOG", "Bisecting" },
5157 { "HEAD", "On branch" },
5158 };
5159 char buf[SIZEOF_STR];
5160 struct stat stat;
5161 int i;
5163 if (is_initial_commit()) {
5164 string_copy(status_onbranch, "Initial commit");
5165 return;
5166 }
5168 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5169 char *head = opt_head;
5171 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5172 lstat(buf, &stat) < 0)
5173 continue;
5175 if (!*opt_head) {
5176 struct io io = {};
5178 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5179 io_open(&io, buf) &&
5180 io_read_buf(&io, buf, sizeof(buf))) {
5181 head = buf;
5182 if (!prefixcmp(head, "refs/heads/"))
5183 head += STRING_SIZE("refs/heads/");
5184 }
5185 }
5187 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5188 string_copy(status_onbranch, opt_head);
5189 return;
5190 }
5192 string_copy(status_onbranch, "Not currently on any branch");
5193 }
5195 /* First parse staged info using git-diff-index(1), then parse unstaged
5196 * info using git-diff-files(1), and finally untracked files using
5197 * git-ls-files(1). */
5198 static bool
5199 status_open(struct view *view)
5200 {
5201 reset_view(view);
5203 add_line_data(view, NULL, LINE_STAT_HEAD);
5204 status_update_onbranch();
5206 run_io_bg(update_index_argv);
5208 if (is_initial_commit()) {
5209 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5210 return FALSE;
5211 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5212 return FALSE;
5213 }
5215 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5216 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5217 return FALSE;
5219 /* Restore the exact position or use the specialized restore
5220 * mode? */
5221 if (!view->p_restore)
5222 status_restore(view);
5223 return TRUE;
5224 }
5226 static bool
5227 status_draw(struct view *view, struct line *line, unsigned int lineno)
5228 {
5229 struct status *status = line->data;
5230 enum line_type type;
5231 const char *text;
5233 if (!status) {
5234 switch (line->type) {
5235 case LINE_STAT_STAGED:
5236 type = LINE_STAT_SECTION;
5237 text = "Changes to be committed:";
5238 break;
5240 case LINE_STAT_UNSTAGED:
5241 type = LINE_STAT_SECTION;
5242 text = "Changed but not updated:";
5243 break;
5245 case LINE_STAT_UNTRACKED:
5246 type = LINE_STAT_SECTION;
5247 text = "Untracked files:";
5248 break;
5250 case LINE_STAT_NONE:
5251 type = LINE_DEFAULT;
5252 text = " (no files)";
5253 break;
5255 case LINE_STAT_HEAD:
5256 type = LINE_STAT_HEAD;
5257 text = status_onbranch;
5258 break;
5260 default:
5261 return FALSE;
5262 }
5263 } else {
5264 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5266 buf[0] = status->status;
5267 if (draw_text(view, line->type, buf, TRUE))
5268 return TRUE;
5269 type = LINE_DEFAULT;
5270 text = status->new.name;
5271 }
5273 draw_text(view, type, text, TRUE);
5274 return TRUE;
5275 }
5277 static enum request
5278 status_load_error(struct view *view, struct view *stage, const char *path)
5279 {
5280 if (displayed_views() == 2 || display[current_view] != view)
5281 maximize_view(view);
5282 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5283 return REQ_NONE;
5284 }
5286 static enum request
5287 status_enter(struct view *view, struct line *line)
5288 {
5289 struct status *status = line->data;
5290 const char *oldpath = status ? status->old.name : NULL;
5291 /* Diffs for unmerged entries are empty when passing the new
5292 * path, so leave it empty. */
5293 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5294 const char *info;
5295 enum open_flags split;
5296 struct view *stage = VIEW(REQ_VIEW_STAGE);
5298 if (line->type == LINE_STAT_NONE ||
5299 (!status && line[1].type == LINE_STAT_NONE)) {
5300 report("No file to diff");
5301 return REQ_NONE;
5302 }
5304 switch (line->type) {
5305 case LINE_STAT_STAGED:
5306 if (is_initial_commit()) {
5307 const char *no_head_diff_argv[] = {
5308 "git", "diff", "--no-color", "--patch-with-stat",
5309 "--", "/dev/null", newpath, NULL
5310 };
5312 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5313 return status_load_error(view, stage, newpath);
5314 } else {
5315 const char *index_show_argv[] = {
5316 "git", "diff-index", "--root", "--patch-with-stat",
5317 "-C", "-M", "--cached", "HEAD", "--",
5318 oldpath, newpath, NULL
5319 };
5321 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5322 return status_load_error(view, stage, newpath);
5323 }
5325 if (status)
5326 info = "Staged changes to %s";
5327 else
5328 info = "Staged changes";
5329 break;
5331 case LINE_STAT_UNSTAGED:
5332 {
5333 const char *files_show_argv[] = {
5334 "git", "diff-files", "--root", "--patch-with-stat",
5335 "-C", "-M", "--", oldpath, newpath, NULL
5336 };
5338 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5339 return status_load_error(view, stage, newpath);
5340 if (status)
5341 info = "Unstaged changes to %s";
5342 else
5343 info = "Unstaged changes";
5344 break;
5345 }
5346 case LINE_STAT_UNTRACKED:
5347 if (!newpath) {
5348 report("No file to show");
5349 return REQ_NONE;
5350 }
5352 if (!suffixcmp(status->new.name, -1, "/")) {
5353 report("Cannot display a directory");
5354 return REQ_NONE;
5355 }
5357 if (!prepare_update_file(stage, newpath))
5358 return status_load_error(view, stage, newpath);
5359 info = "Untracked file %s";
5360 break;
5362 case LINE_STAT_HEAD:
5363 return REQ_NONE;
5365 default:
5366 die("line type %d not handled in switch", line->type);
5367 }
5369 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5370 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5371 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5372 if (status) {
5373 stage_status = *status;
5374 } else {
5375 memset(&stage_status, 0, sizeof(stage_status));
5376 }
5378 stage_line_type = line->type;
5379 stage_chunks = 0;
5380 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5381 }
5383 return REQ_NONE;
5384 }
5386 static bool
5387 status_exists(struct status *status, enum line_type type)
5388 {
5389 struct view *view = VIEW(REQ_VIEW_STATUS);
5390 unsigned long lineno;
5392 for (lineno = 0; lineno < view->lines; lineno++) {
5393 struct line *line = &view->line[lineno];
5394 struct status *pos = line->data;
5396 if (line->type != type)
5397 continue;
5398 if (!pos && (!status || !status->status) && line[1].data) {
5399 select_view_line(view, lineno);
5400 return TRUE;
5401 }
5402 if (pos && !strcmp(status->new.name, pos->new.name)) {
5403 select_view_line(view, lineno);
5404 return TRUE;
5405 }
5406 }
5408 return FALSE;
5409 }
5412 static bool
5413 status_update_prepare(struct io *io, enum line_type type)
5414 {
5415 const char *staged_argv[] = {
5416 "git", "update-index", "-z", "--index-info", NULL
5417 };
5418 const char *others_argv[] = {
5419 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5420 };
5422 switch (type) {
5423 case LINE_STAT_STAGED:
5424 return run_io(io, staged_argv, opt_cdup, IO_WR);
5426 case LINE_STAT_UNSTAGED:
5427 return run_io(io, others_argv, opt_cdup, IO_WR);
5429 case LINE_STAT_UNTRACKED:
5430 return run_io(io, others_argv, NULL, IO_WR);
5432 default:
5433 die("line type %d not handled in switch", type);
5434 return FALSE;
5435 }
5436 }
5438 static bool
5439 status_update_write(struct io *io, struct status *status, enum line_type type)
5440 {
5441 char buf[SIZEOF_STR];
5442 size_t bufsize = 0;
5444 switch (type) {
5445 case LINE_STAT_STAGED:
5446 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5447 status->old.mode,
5448 status->old.rev,
5449 status->old.name, 0))
5450 return FALSE;
5451 break;
5453 case LINE_STAT_UNSTAGED:
5454 case LINE_STAT_UNTRACKED:
5455 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5456 return FALSE;
5457 break;
5459 default:
5460 die("line type %d not handled in switch", type);
5461 }
5463 return io_write(io, buf, bufsize);
5464 }
5466 static bool
5467 status_update_file(struct status *status, enum line_type type)
5468 {
5469 struct io io = {};
5470 bool result;
5472 if (!status_update_prepare(&io, type))
5473 return FALSE;
5475 result = status_update_write(&io, status, type);
5476 return done_io(&io) && result;
5477 }
5479 static bool
5480 status_update_files(struct view *view, struct line *line)
5481 {
5482 char buf[sizeof(view->ref)];
5483 struct io io = {};
5484 bool result = TRUE;
5485 struct line *pos = view->line + view->lines;
5486 int files = 0;
5487 int file, done;
5488 int cursor_y, cursor_x;
5490 if (!status_update_prepare(&io, line->type))
5491 return FALSE;
5493 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5494 files++;
5496 string_copy(buf, view->ref);
5497 getsyx(cursor_y, cursor_x);
5498 for (file = 0, done = 5; result && file < files; line++, file++) {
5499 int almost_done = file * 100 / files;
5501 if (almost_done > done) {
5502 done = almost_done;
5503 string_format(view->ref, "updating file %u of %u (%d%% done)",
5504 file, files, done);
5505 update_view_title(view);
5506 setsyx(cursor_y, cursor_x);
5507 doupdate();
5508 }
5509 result = status_update_write(&io, line->data, line->type);
5510 }
5511 string_copy(view->ref, buf);
5513 return done_io(&io) && result;
5514 }
5516 static bool
5517 status_update(struct view *view)
5518 {
5519 struct line *line = &view->line[view->lineno];
5521 assert(view->lines);
5523 if (!line->data) {
5524 /* This should work even for the "On branch" line. */
5525 if (line < view->line + view->lines && !line[1].data) {
5526 report("Nothing to update");
5527 return FALSE;
5528 }
5530 if (!status_update_files(view, line + 1)) {
5531 report("Failed to update file status");
5532 return FALSE;
5533 }
5535 } else if (!status_update_file(line->data, line->type)) {
5536 report("Failed to update file status");
5537 return FALSE;
5538 }
5540 return TRUE;
5541 }
5543 static bool
5544 status_revert(struct status *status, enum line_type type, bool has_none)
5545 {
5546 if (!status || type != LINE_STAT_UNSTAGED) {
5547 if (type == LINE_STAT_STAGED) {
5548 report("Cannot revert changes to staged files");
5549 } else if (type == LINE_STAT_UNTRACKED) {
5550 report("Cannot revert changes to untracked files");
5551 } else if (has_none) {
5552 report("Nothing to revert");
5553 } else {
5554 report("Cannot revert changes to multiple files");
5555 }
5556 return FALSE;
5558 } else {
5559 char mode[10] = "100644";
5560 const char *reset_argv[] = {
5561 "git", "update-index", "--cacheinfo", mode,
5562 status->old.rev, status->old.name, NULL
5563 };
5564 const char *checkout_argv[] = {
5565 "git", "checkout", "--", status->old.name, NULL
5566 };
5568 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5569 return FALSE;
5570 string_format(mode, "%o", status->old.mode);
5571 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5572 run_io_fg(checkout_argv, opt_cdup);
5573 }
5574 }
5576 static enum request
5577 status_request(struct view *view, enum request request, struct line *line)
5578 {
5579 struct status *status = line->data;
5581 switch (request) {
5582 case REQ_STATUS_UPDATE:
5583 if (!status_update(view))
5584 return REQ_NONE;
5585 break;
5587 case REQ_STATUS_REVERT:
5588 if (!status_revert(status, line->type, status_has_none(view, line)))
5589 return REQ_NONE;
5590 break;
5592 case REQ_STATUS_MERGE:
5593 if (!status || status->status != 'U') {
5594 report("Merging only possible for files with unmerged status ('U').");
5595 return REQ_NONE;
5596 }
5597 open_mergetool(status->new.name);
5598 break;
5600 case REQ_EDIT:
5601 if (!status)
5602 return request;
5603 if (status->status == 'D') {
5604 report("File has been deleted.");
5605 return REQ_NONE;
5606 }
5608 open_editor(status->status != '?', status->new.name);
5609 break;
5611 case REQ_VIEW_BLAME:
5612 if (status) {
5613 string_copy(opt_file, status->new.name);
5614 opt_ref[0] = 0;
5615 }
5616 return request;
5618 case REQ_ENTER:
5619 /* After returning the status view has been split to
5620 * show the stage view. No further reloading is
5621 * necessary. */
5622 return status_enter(view, line);
5624 case REQ_REFRESH:
5625 /* Simply reload the view. */
5626 break;
5628 default:
5629 return request;
5630 }
5632 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5634 return REQ_NONE;
5635 }
5637 static void
5638 status_select(struct view *view, struct line *line)
5639 {
5640 struct status *status = line->data;
5641 char file[SIZEOF_STR] = "all files";
5642 const char *text;
5643 const char *key;
5645 if (status && !string_format(file, "'%s'", status->new.name))
5646 return;
5648 if (!status && line[1].type == LINE_STAT_NONE)
5649 line++;
5651 switch (line->type) {
5652 case LINE_STAT_STAGED:
5653 text = "Press %s to unstage %s for commit";
5654 break;
5656 case LINE_STAT_UNSTAGED:
5657 text = "Press %s to stage %s for commit";
5658 break;
5660 case LINE_STAT_UNTRACKED:
5661 text = "Press %s to stage %s for addition";
5662 break;
5664 case LINE_STAT_HEAD:
5665 case LINE_STAT_NONE:
5666 text = "Nothing to update";
5667 break;
5669 default:
5670 die("line type %d not handled in switch", line->type);
5671 }
5673 if (status && status->status == 'U') {
5674 text = "Press %s to resolve conflict in %s";
5675 key = get_key(REQ_STATUS_MERGE);
5677 } else {
5678 key = get_key(REQ_STATUS_UPDATE);
5679 }
5681 string_format(view->ref, text, key, file);
5682 }
5684 static bool
5685 status_grep(struct view *view, struct line *line)
5686 {
5687 struct status *status = line->data;
5689 if (status) {
5690 const char buf[2] = { status->status, 0 };
5691 const char *text[] = { status->new.name, buf, NULL };
5693 return grep_text(view, text);
5694 }
5696 return FALSE;
5697 }
5699 static struct view_ops status_ops = {
5700 "file",
5701 NULL,
5702 status_open,
5703 NULL,
5704 status_draw,
5705 status_request,
5706 status_grep,
5707 status_select,
5708 };
5711 static bool
5712 stage_diff_write(struct io *io, struct line *line, struct line *end)
5713 {
5714 while (line < end) {
5715 if (!io_write(io, line->data, strlen(line->data)) ||
5716 !io_write(io, "\n", 1))
5717 return FALSE;
5718 line++;
5719 if (line->type == LINE_DIFF_CHUNK ||
5720 line->type == LINE_DIFF_HEADER)
5721 break;
5722 }
5724 return TRUE;
5725 }
5727 static struct line *
5728 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5729 {
5730 for (; view->line < line; line--)
5731 if (line->type == type)
5732 return line;
5734 return NULL;
5735 }
5737 static bool
5738 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5739 {
5740 const char *apply_argv[SIZEOF_ARG] = {
5741 "git", "apply", "--whitespace=nowarn", NULL
5742 };
5743 struct line *diff_hdr;
5744 struct io io = {};
5745 int argc = 3;
5747 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5748 if (!diff_hdr)
5749 return FALSE;
5751 if (!revert)
5752 apply_argv[argc++] = "--cached";
5753 if (revert || stage_line_type == LINE_STAT_STAGED)
5754 apply_argv[argc++] = "-R";
5755 apply_argv[argc++] = "-";
5756 apply_argv[argc++] = NULL;
5757 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5758 return FALSE;
5760 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5761 !stage_diff_write(&io, chunk, view->line + view->lines))
5762 chunk = NULL;
5764 done_io(&io);
5765 run_io_bg(update_index_argv);
5767 return chunk ? TRUE : FALSE;
5768 }
5770 static bool
5771 stage_update(struct view *view, struct line *line)
5772 {
5773 struct line *chunk = NULL;
5775 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5776 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5778 if (chunk) {
5779 if (!stage_apply_chunk(view, chunk, FALSE)) {
5780 report("Failed to apply chunk");
5781 return FALSE;
5782 }
5784 } else if (!stage_status.status) {
5785 view = VIEW(REQ_VIEW_STATUS);
5787 for (line = view->line; line < view->line + view->lines; line++)
5788 if (line->type == stage_line_type)
5789 break;
5791 if (!status_update_files(view, line + 1)) {
5792 report("Failed to update files");
5793 return FALSE;
5794 }
5796 } else if (!status_update_file(&stage_status, stage_line_type)) {
5797 report("Failed to update file");
5798 return FALSE;
5799 }
5801 return TRUE;
5802 }
5804 static bool
5805 stage_revert(struct view *view, struct line *line)
5806 {
5807 struct line *chunk = NULL;
5809 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5810 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5812 if (chunk) {
5813 if (!prompt_yesno("Are you sure you want to revert changes?"))
5814 return FALSE;
5816 if (!stage_apply_chunk(view, chunk, TRUE)) {
5817 report("Failed to revert chunk");
5818 return FALSE;
5819 }
5820 return TRUE;
5822 } else {
5823 return status_revert(stage_status.status ? &stage_status : NULL,
5824 stage_line_type, FALSE);
5825 }
5826 }
5829 static void
5830 stage_next(struct view *view, struct line *line)
5831 {
5832 int i;
5834 if (!stage_chunks) {
5835 for (line = view->line; line < view->line + view->lines; line++) {
5836 if (line->type != LINE_DIFF_CHUNK)
5837 continue;
5839 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5840 report("Allocation failure");
5841 return;
5842 }
5844 stage_chunk[stage_chunks++] = line - view->line;
5845 }
5846 }
5848 for (i = 0; i < stage_chunks; i++) {
5849 if (stage_chunk[i] > view->lineno) {
5850 do_scroll_view(view, stage_chunk[i] - view->lineno);
5851 report("Chunk %d of %d", i + 1, stage_chunks);
5852 return;
5853 }
5854 }
5856 report("No next chunk found");
5857 }
5859 static enum request
5860 stage_request(struct view *view, enum request request, struct line *line)
5861 {
5862 switch (request) {
5863 case REQ_STATUS_UPDATE:
5864 if (!stage_update(view, line))
5865 return REQ_NONE;
5866 break;
5868 case REQ_STATUS_REVERT:
5869 if (!stage_revert(view, line))
5870 return REQ_NONE;
5871 break;
5873 case REQ_STAGE_NEXT:
5874 if (stage_line_type == LINE_STAT_UNTRACKED) {
5875 report("File is untracked; press %s to add",
5876 get_key(REQ_STATUS_UPDATE));
5877 return REQ_NONE;
5878 }
5879 stage_next(view, line);
5880 return REQ_NONE;
5882 case REQ_EDIT:
5883 if (!stage_status.new.name[0])
5884 return request;
5885 if (stage_status.status == 'D') {
5886 report("File has been deleted.");
5887 return REQ_NONE;
5888 }
5890 open_editor(stage_status.status != '?', stage_status.new.name);
5891 break;
5893 case REQ_REFRESH:
5894 /* Reload everything ... */
5895 break;
5897 case REQ_VIEW_BLAME:
5898 if (stage_status.new.name[0]) {
5899 string_copy(opt_file, stage_status.new.name);
5900 opt_ref[0] = 0;
5901 }
5902 return request;
5904 case REQ_ENTER:
5905 return pager_request(view, request, line);
5907 default:
5908 return request;
5909 }
5911 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5912 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5914 /* Check whether the staged entry still exists, and close the
5915 * stage view if it doesn't. */
5916 if (!status_exists(&stage_status, stage_line_type)) {
5917 status_restore(VIEW(REQ_VIEW_STATUS));
5918 return REQ_VIEW_CLOSE;
5919 }
5921 if (stage_line_type == LINE_STAT_UNTRACKED) {
5922 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5923 report("Cannot display a directory");
5924 return REQ_NONE;
5925 }
5927 if (!prepare_update_file(view, stage_status.new.name)) {
5928 report("Failed to open file: %s", strerror(errno));
5929 return REQ_NONE;
5930 }
5931 }
5932 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5934 return REQ_NONE;
5935 }
5937 static struct view_ops stage_ops = {
5938 "line",
5939 NULL,
5940 NULL,
5941 pager_read,
5942 pager_draw,
5943 stage_request,
5944 pager_grep,
5945 pager_select,
5946 };
5949 /*
5950 * Revision graph
5951 */
5953 struct commit {
5954 char id[SIZEOF_REV]; /* SHA1 ID. */
5955 char title[128]; /* First line of the commit message. */
5956 const char *author; /* Author of the commit. */
5957 time_t time; /* Date from the author ident. */
5958 struct ref_list *refs; /* Repository references. */
5959 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5960 size_t graph_size; /* The width of the graph array. */
5961 bool has_parents; /* Rewritten --parents seen. */
5962 };
5964 /* Size of rev graph with no "padding" columns */
5965 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5967 struct rev_graph {
5968 struct rev_graph *prev, *next, *parents;
5969 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5970 size_t size;
5971 struct commit *commit;
5972 size_t pos;
5973 unsigned int boundary:1;
5974 };
5976 /* Parents of the commit being visualized. */
5977 static struct rev_graph graph_parents[4];
5979 /* The current stack of revisions on the graph. */
5980 static struct rev_graph graph_stacks[4] = {
5981 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5982 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5983 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5984 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5985 };
5987 static inline bool
5988 graph_parent_is_merge(struct rev_graph *graph)
5989 {
5990 return graph->parents->size > 1;
5991 }
5993 static inline void
5994 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5995 {
5996 struct commit *commit = graph->commit;
5998 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5999 commit->graph[commit->graph_size++] = symbol;
6000 }
6002 static void
6003 clear_rev_graph(struct rev_graph *graph)
6004 {
6005 graph->boundary = 0;
6006 graph->size = graph->pos = 0;
6007 graph->commit = NULL;
6008 memset(graph->parents, 0, sizeof(*graph->parents));
6009 }
6011 static void
6012 done_rev_graph(struct rev_graph *graph)
6013 {
6014 if (graph_parent_is_merge(graph) &&
6015 graph->pos < graph->size - 1 &&
6016 graph->next->size == graph->size + graph->parents->size - 1) {
6017 size_t i = graph->pos + graph->parents->size - 1;
6019 graph->commit->graph_size = i * 2;
6020 while (i < graph->next->size - 1) {
6021 append_to_rev_graph(graph, ' ');
6022 append_to_rev_graph(graph, '\\');
6023 i++;
6024 }
6025 }
6027 clear_rev_graph(graph);
6028 }
6030 static void
6031 push_rev_graph(struct rev_graph *graph, const char *parent)
6032 {
6033 int i;
6035 /* "Collapse" duplicate parents lines.
6036 *
6037 * FIXME: This needs to also update update the drawn graph but
6038 * for now it just serves as a method for pruning graph lines. */
6039 for (i = 0; i < graph->size; i++)
6040 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6041 return;
6043 if (graph->size < SIZEOF_REVITEMS) {
6044 string_copy_rev(graph->rev[graph->size++], parent);
6045 }
6046 }
6048 static chtype
6049 get_rev_graph_symbol(struct rev_graph *graph)
6050 {
6051 chtype symbol;
6053 if (graph->boundary)
6054 symbol = REVGRAPH_BOUND;
6055 else if (graph->parents->size == 0)
6056 symbol = REVGRAPH_INIT;
6057 else if (graph_parent_is_merge(graph))
6058 symbol = REVGRAPH_MERGE;
6059 else if (graph->pos >= graph->size)
6060 symbol = REVGRAPH_BRANCH;
6061 else
6062 symbol = REVGRAPH_COMMIT;
6064 return symbol;
6065 }
6067 static void
6068 draw_rev_graph(struct rev_graph *graph)
6069 {
6070 struct rev_filler {
6071 chtype separator, line;
6072 };
6073 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6074 static struct rev_filler fillers[] = {
6075 { ' ', '|' },
6076 { '`', '.' },
6077 { '\'', ' ' },
6078 { '/', ' ' },
6079 };
6080 chtype symbol = get_rev_graph_symbol(graph);
6081 struct rev_filler *filler;
6082 size_t i;
6084 if (opt_line_graphics)
6085 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6087 filler = &fillers[DEFAULT];
6089 for (i = 0; i < graph->pos; i++) {
6090 append_to_rev_graph(graph, filler->line);
6091 if (graph_parent_is_merge(graph->prev) &&
6092 graph->prev->pos == i)
6093 filler = &fillers[RSHARP];
6095 append_to_rev_graph(graph, filler->separator);
6096 }
6098 /* Place the symbol for this revision. */
6099 append_to_rev_graph(graph, symbol);
6101 if (graph->prev->size > graph->size)
6102 filler = &fillers[RDIAG];
6103 else
6104 filler = &fillers[DEFAULT];
6106 i++;
6108 for (; i < graph->size; i++) {
6109 append_to_rev_graph(graph, filler->separator);
6110 append_to_rev_graph(graph, filler->line);
6111 if (graph_parent_is_merge(graph->prev) &&
6112 i < graph->prev->pos + graph->parents->size)
6113 filler = &fillers[RSHARP];
6114 if (graph->prev->size > graph->size)
6115 filler = &fillers[LDIAG];
6116 }
6118 if (graph->prev->size > graph->size) {
6119 append_to_rev_graph(graph, filler->separator);
6120 if (filler->line != ' ')
6121 append_to_rev_graph(graph, filler->line);
6122 }
6123 }
6125 /* Prepare the next rev graph */
6126 static void
6127 prepare_rev_graph(struct rev_graph *graph)
6128 {
6129 size_t i;
6131 /* First, traverse all lines of revisions up to the active one. */
6132 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6133 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6134 break;
6136 push_rev_graph(graph->next, graph->rev[graph->pos]);
6137 }
6139 /* Interleave the new revision parent(s). */
6140 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6141 push_rev_graph(graph->next, graph->parents->rev[i]);
6143 /* Lastly, put any remaining revisions. */
6144 for (i = graph->pos + 1; i < graph->size; i++)
6145 push_rev_graph(graph->next, graph->rev[i]);
6146 }
6148 static void
6149 update_rev_graph(struct view *view, struct rev_graph *graph)
6150 {
6151 /* If this is the finalizing update ... */
6152 if (graph->commit)
6153 prepare_rev_graph(graph);
6155 /* Graph visualization needs a one rev look-ahead,
6156 * so the first update doesn't visualize anything. */
6157 if (!graph->prev->commit)
6158 return;
6160 if (view->lines > 2)
6161 view->line[view->lines - 3].dirty = 1;
6162 if (view->lines > 1)
6163 view->line[view->lines - 2].dirty = 1;
6164 draw_rev_graph(graph->prev);
6165 done_rev_graph(graph->prev->prev);
6166 }
6169 /*
6170 * Main view backend
6171 */
6173 static const char *main_argv[SIZEOF_ARG] = {
6174 "git", "log", "--no-color", "--pretty=raw", "--parents",
6175 "--topo-order", "%(head)", NULL
6176 };
6178 static bool
6179 main_draw(struct view *view, struct line *line, unsigned int lineno)
6180 {
6181 struct commit *commit = line->data;
6183 if (!commit->author)
6184 return FALSE;
6186 if (opt_date && draw_date(view, &commit->time))
6187 return TRUE;
6189 if (opt_author && draw_author(view, commit->author))
6190 return TRUE;
6192 if (opt_rev_graph && commit->graph_size &&
6193 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6194 return TRUE;
6196 if (opt_show_refs && commit->refs) {
6197 size_t i;
6199 for (i = 0; i < commit->refs->size; i++) {
6200 struct ref *ref = commit->refs->refs[i];
6201 enum line_type type;
6203 if (ref->head)
6204 type = LINE_MAIN_HEAD;
6205 else if (ref->ltag)
6206 type = LINE_MAIN_LOCAL_TAG;
6207 else if (ref->tag)
6208 type = LINE_MAIN_TAG;
6209 else if (ref->tracked)
6210 type = LINE_MAIN_TRACKED;
6211 else if (ref->remote)
6212 type = LINE_MAIN_REMOTE;
6213 else
6214 type = LINE_MAIN_REF;
6216 if (draw_text(view, type, "[", TRUE) ||
6217 draw_text(view, type, ref->name, TRUE) ||
6218 draw_text(view, type, "]", TRUE))
6219 return TRUE;
6221 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6222 return TRUE;
6223 }
6224 }
6226 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6227 return TRUE;
6228 }
6230 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6231 static bool
6232 main_read(struct view *view, char *line)
6233 {
6234 static struct rev_graph *graph = graph_stacks;
6235 enum line_type type;
6236 struct commit *commit;
6238 if (!line) {
6239 int i;
6241 if (!view->lines && !view->parent)
6242 die("No revisions match the given arguments.");
6243 if (view->lines > 0) {
6244 commit = view->line[view->lines - 1].data;
6245 view->line[view->lines - 1].dirty = 1;
6246 if (!commit->author) {
6247 view->lines--;
6248 free(commit);
6249 graph->commit = NULL;
6250 }
6251 }
6252 update_rev_graph(view, graph);
6254 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6255 clear_rev_graph(&graph_stacks[i]);
6256 return TRUE;
6257 }
6259 type = get_line_type(line);
6260 if (type == LINE_COMMIT) {
6261 commit = calloc(1, sizeof(struct commit));
6262 if (!commit)
6263 return FALSE;
6265 line += STRING_SIZE("commit ");
6266 if (*line == '-') {
6267 graph->boundary = 1;
6268 line++;
6269 }
6271 string_copy_rev(commit->id, line);
6272 commit->refs = get_ref_list(commit->id);
6273 graph->commit = commit;
6274 add_line_data(view, commit, LINE_MAIN_COMMIT);
6276 while ((line = strchr(line, ' '))) {
6277 line++;
6278 push_rev_graph(graph->parents, line);
6279 commit->has_parents = TRUE;
6280 }
6281 return TRUE;
6282 }
6284 if (!view->lines)
6285 return TRUE;
6286 commit = view->line[view->lines - 1].data;
6288 switch (type) {
6289 case LINE_PARENT:
6290 if (commit->has_parents)
6291 break;
6292 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6293 break;
6295 case LINE_AUTHOR:
6296 parse_author_line(line + STRING_SIZE("author "),
6297 &commit->author, &commit->time);
6298 update_rev_graph(view, graph);
6299 graph = graph->next;
6300 break;
6302 default:
6303 /* Fill in the commit title if it has not already been set. */
6304 if (commit->title[0])
6305 break;
6307 /* Require titles to start with a non-space character at the
6308 * offset used by git log. */
6309 if (strncmp(line, " ", 4))
6310 break;
6311 line += 4;
6312 /* Well, if the title starts with a whitespace character,
6313 * try to be forgiving. Otherwise we end up with no title. */
6314 while (isspace(*line))
6315 line++;
6316 if (*line == '\0')
6317 break;
6318 /* FIXME: More graceful handling of titles; append "..." to
6319 * shortened titles, etc. */
6321 string_expand(commit->title, sizeof(commit->title), line, 1);
6322 view->line[view->lines - 1].dirty = 1;
6323 }
6325 return TRUE;
6326 }
6328 static enum request
6329 main_request(struct view *view, enum request request, struct line *line)
6330 {
6331 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6333 switch (request) {
6334 case REQ_ENTER:
6335 open_view(view, REQ_VIEW_DIFF, flags);
6336 break;
6337 case REQ_REFRESH:
6338 load_refs();
6339 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6340 break;
6341 default:
6342 return request;
6343 }
6345 return REQ_NONE;
6346 }
6348 static bool
6349 grep_refs(struct ref_list *list, regex_t *regex)
6350 {
6351 regmatch_t pmatch;
6352 size_t i;
6354 if (!opt_show_refs || !list)
6355 return FALSE;
6357 for (i = 0; i < list->size; i++) {
6358 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6359 return TRUE;
6360 }
6362 return FALSE;
6363 }
6365 static bool
6366 main_grep(struct view *view, struct line *line)
6367 {
6368 struct commit *commit = line->data;
6369 const char *text[] = {
6370 commit->title,
6371 opt_author ? commit->author : "",
6372 opt_date ? mkdate(&commit->time) : "",
6373 NULL
6374 };
6376 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6377 }
6379 static void
6380 main_select(struct view *view, struct line *line)
6381 {
6382 struct commit *commit = line->data;
6384 string_copy_rev(view->ref, commit->id);
6385 string_copy_rev(ref_commit, view->ref);
6386 }
6388 static struct view_ops main_ops = {
6389 "commit",
6390 main_argv,
6391 NULL,
6392 main_read,
6393 main_draw,
6394 main_request,
6395 main_grep,
6396 main_select,
6397 };
6400 /*
6401 * Unicode / UTF-8 handling
6402 *
6403 * NOTE: Much of the following code for dealing with Unicode is derived from
6404 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6405 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6406 */
6408 static inline int
6409 unicode_width(unsigned long c)
6410 {
6411 if (c >= 0x1100 &&
6412 (c <= 0x115f /* Hangul Jamo */
6413 || c == 0x2329
6414 || c == 0x232a
6415 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6416 /* CJK ... Yi */
6417 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6418 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6419 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6420 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6421 || (c >= 0xffe0 && c <= 0xffe6)
6422 || (c >= 0x20000 && c <= 0x2fffd)
6423 || (c >= 0x30000 && c <= 0x3fffd)))
6424 return 2;
6426 if (c == '\t')
6427 return opt_tab_size;
6429 return 1;
6430 }
6432 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6433 * Illegal bytes are set one. */
6434 static const unsigned char utf8_bytes[256] = {
6435 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,
6436 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,
6437 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,
6438 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,
6439 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,
6440 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,
6441 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,
6442 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,
6443 };
6445 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6446 static inline unsigned long
6447 utf8_to_unicode(const char *string, size_t length)
6448 {
6449 unsigned long unicode;
6451 switch (length) {
6452 case 1:
6453 unicode = string[0];
6454 break;
6455 case 2:
6456 unicode = (string[0] & 0x1f) << 6;
6457 unicode += (string[1] & 0x3f);
6458 break;
6459 case 3:
6460 unicode = (string[0] & 0x0f) << 12;
6461 unicode += ((string[1] & 0x3f) << 6);
6462 unicode += (string[2] & 0x3f);
6463 break;
6464 case 4:
6465 unicode = (string[0] & 0x0f) << 18;
6466 unicode += ((string[1] & 0x3f) << 12);
6467 unicode += ((string[2] & 0x3f) << 6);
6468 unicode += (string[3] & 0x3f);
6469 break;
6470 case 5:
6471 unicode = (string[0] & 0x0f) << 24;
6472 unicode += ((string[1] & 0x3f) << 18);
6473 unicode += ((string[2] & 0x3f) << 12);
6474 unicode += ((string[3] & 0x3f) << 6);
6475 unicode += (string[4] & 0x3f);
6476 break;
6477 case 6:
6478 unicode = (string[0] & 0x01) << 30;
6479 unicode += ((string[1] & 0x3f) << 24);
6480 unicode += ((string[2] & 0x3f) << 18);
6481 unicode += ((string[3] & 0x3f) << 12);
6482 unicode += ((string[4] & 0x3f) << 6);
6483 unicode += (string[5] & 0x3f);
6484 break;
6485 default:
6486 die("Invalid Unicode length");
6487 }
6489 /* Invalid characters could return the special 0xfffd value but NUL
6490 * should be just as good. */
6491 return unicode > 0xffff ? 0 : unicode;
6492 }
6494 /* Calculates how much of string can be shown within the given maximum width
6495 * and sets trimmed parameter to non-zero value if all of string could not be
6496 * shown. If the reserve flag is TRUE, it will reserve at least one
6497 * trailing character, which can be useful when drawing a delimiter.
6498 *
6499 * Returns the number of bytes to output from string to satisfy max_width. */
6500 static size_t
6501 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6502 {
6503 const char *string = *start;
6504 const char *end = strchr(string, '\0');
6505 unsigned char last_bytes = 0;
6506 size_t last_ucwidth = 0;
6508 *width = 0;
6509 *trimmed = 0;
6511 while (string < end) {
6512 int c = *(unsigned char *) string;
6513 unsigned char bytes = utf8_bytes[c];
6514 size_t ucwidth;
6515 unsigned long unicode;
6517 if (string + bytes > end)
6518 break;
6520 /* Change representation to figure out whether
6521 * it is a single- or double-width character. */
6523 unicode = utf8_to_unicode(string, bytes);
6524 /* FIXME: Graceful handling of invalid Unicode character. */
6525 if (!unicode)
6526 break;
6528 ucwidth = unicode_width(unicode);
6529 if (skip > 0) {
6530 skip -= ucwidth <= skip ? ucwidth : skip;
6531 *start += bytes;
6532 }
6533 *width += ucwidth;
6534 if (*width > max_width) {
6535 *trimmed = 1;
6536 *width -= ucwidth;
6537 if (reserve && *width == max_width) {
6538 string -= last_bytes;
6539 *width -= last_ucwidth;
6540 }
6541 break;
6542 }
6544 string += bytes;
6545 last_bytes = ucwidth ? bytes : 0;
6546 last_ucwidth = ucwidth;
6547 }
6549 return string - *start;
6550 }
6553 /*
6554 * Status management
6555 */
6557 /* Whether or not the curses interface has been initialized. */
6558 static bool cursed = FALSE;
6560 /* Terminal hacks and workarounds. */
6561 static bool use_scroll_redrawwin;
6562 static bool use_scroll_status_wclear;
6564 /* The status window is used for polling keystrokes. */
6565 static WINDOW *status_win;
6567 /* Reading from the prompt? */
6568 static bool input_mode = FALSE;
6570 static bool status_empty = FALSE;
6572 /* Update status and title window. */
6573 static void
6574 report(const char *msg, ...)
6575 {
6576 struct view *view = display[current_view];
6578 if (input_mode)
6579 return;
6581 if (!view) {
6582 char buf[SIZEOF_STR];
6583 va_list args;
6585 va_start(args, msg);
6586 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6587 buf[sizeof(buf) - 1] = 0;
6588 buf[sizeof(buf) - 2] = '.';
6589 buf[sizeof(buf) - 3] = '.';
6590 buf[sizeof(buf) - 4] = '.';
6591 }
6592 va_end(args);
6593 die("%s", buf);
6594 }
6596 if (!status_empty || *msg) {
6597 va_list args;
6599 va_start(args, msg);
6601 wmove(status_win, 0, 0);
6602 if (view->has_scrolled && use_scroll_status_wclear)
6603 wclear(status_win);
6604 if (*msg) {
6605 vwprintw(status_win, msg, args);
6606 status_empty = FALSE;
6607 } else {
6608 status_empty = TRUE;
6609 }
6610 wclrtoeol(status_win);
6611 wnoutrefresh(status_win);
6613 va_end(args);
6614 }
6616 update_view_title(view);
6617 }
6619 /* Controls when nodelay should be in effect when polling user input. */
6620 static void
6621 set_nonblocking_input(bool loading)
6622 {
6623 static unsigned int loading_views;
6625 if ((loading == FALSE && loading_views-- == 1) ||
6626 (loading == TRUE && loading_views++ == 0))
6627 nodelay(status_win, loading);
6628 }
6630 static void
6631 init_display(void)
6632 {
6633 const char *term;
6634 int x, y;
6636 /* Initialize the curses library */
6637 if (isatty(STDIN_FILENO)) {
6638 cursed = !!initscr();
6639 opt_tty = stdin;
6640 } else {
6641 /* Leave stdin and stdout alone when acting as a pager. */
6642 opt_tty = fopen("/dev/tty", "r+");
6643 if (!opt_tty)
6644 die("Failed to open /dev/tty");
6645 cursed = !!newterm(NULL, opt_tty, opt_tty);
6646 }
6648 if (!cursed)
6649 die("Failed to initialize curses");
6651 nonl(); /* Disable conversion and detect newlines from input. */
6652 cbreak(); /* Take input chars one at a time, no wait for \n */
6653 noecho(); /* Don't echo input */
6654 leaveok(stdscr, FALSE);
6656 if (has_colors())
6657 init_colors();
6659 getmaxyx(stdscr, y, x);
6660 status_win = newwin(1, 0, y - 1, 0);
6661 if (!status_win)
6662 die("Failed to create status window");
6664 /* Enable keyboard mapping */
6665 keypad(status_win, TRUE);
6666 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6668 TABSIZE = opt_tab_size;
6669 if (opt_line_graphics) {
6670 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6671 }
6673 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6674 if (term && !strcmp(term, "gnome-terminal")) {
6675 /* In the gnome-terminal-emulator, the message from
6676 * scrolling up one line when impossible followed by
6677 * scrolling down one line causes corruption of the
6678 * status line. This is fixed by calling wclear. */
6679 use_scroll_status_wclear = TRUE;
6680 use_scroll_redrawwin = FALSE;
6682 } else if (term && !strcmp(term, "xrvt-xpm")) {
6683 /* No problems with full optimizations in xrvt-(unicode)
6684 * and aterm. */
6685 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6687 } else {
6688 /* When scrolling in (u)xterm the last line in the
6689 * scrolling direction will update slowly. */
6690 use_scroll_redrawwin = TRUE;
6691 use_scroll_status_wclear = FALSE;
6692 }
6693 }
6695 static int
6696 get_input(int prompt_position)
6697 {
6698 struct view *view;
6699 int i, key, cursor_y, cursor_x;
6701 if (prompt_position)
6702 input_mode = TRUE;
6704 while (TRUE) {
6705 foreach_view (view, i) {
6706 update_view(view);
6707 if (view_is_displayed(view) && view->has_scrolled &&
6708 use_scroll_redrawwin)
6709 redrawwin(view->win);
6710 view->has_scrolled = FALSE;
6711 }
6713 /* Update the cursor position. */
6714 if (prompt_position) {
6715 getbegyx(status_win, cursor_y, cursor_x);
6716 cursor_x = prompt_position;
6717 } else {
6718 view = display[current_view];
6719 getbegyx(view->win, cursor_y, cursor_x);
6720 cursor_x = view->width - 1;
6721 cursor_y += view->lineno - view->offset;
6722 }
6723 setsyx(cursor_y, cursor_x);
6725 /* Refresh, accept single keystroke of input */
6726 doupdate();
6727 key = wgetch(status_win);
6729 /* wgetch() with nodelay() enabled returns ERR when
6730 * there's no input. */
6731 if (key == ERR) {
6733 } else if (key == KEY_RESIZE) {
6734 int height, width;
6736 getmaxyx(stdscr, height, width);
6738 wresize(status_win, 1, width);
6739 mvwin(status_win, height - 1, 0);
6740 wnoutrefresh(status_win);
6741 resize_display();
6742 redraw_display(TRUE);
6744 } else {
6745 input_mode = FALSE;
6746 return key;
6747 }
6748 }
6749 }
6751 static char *
6752 prompt_input(const char *prompt, input_handler handler, void *data)
6753 {
6754 enum input_status status = INPUT_OK;
6755 static char buf[SIZEOF_STR];
6756 size_t pos = 0;
6758 buf[pos] = 0;
6760 while (status == INPUT_OK || status == INPUT_SKIP) {
6761 int key;
6763 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6764 wclrtoeol(status_win);
6766 key = get_input(pos + 1);
6767 switch (key) {
6768 case KEY_RETURN:
6769 case KEY_ENTER:
6770 case '\n':
6771 status = pos ? INPUT_STOP : INPUT_CANCEL;
6772 break;
6774 case KEY_BACKSPACE:
6775 if (pos > 0)
6776 buf[--pos] = 0;
6777 else
6778 status = INPUT_CANCEL;
6779 break;
6781 case KEY_ESC:
6782 status = INPUT_CANCEL;
6783 break;
6785 default:
6786 if (pos >= sizeof(buf)) {
6787 report("Input string too long");
6788 return NULL;
6789 }
6791 status = handler(data, buf, key);
6792 if (status == INPUT_OK)
6793 buf[pos++] = (char) key;
6794 }
6795 }
6797 /* Clear the status window */
6798 status_empty = FALSE;
6799 report("");
6801 if (status == INPUT_CANCEL)
6802 return NULL;
6804 buf[pos++] = 0;
6806 return buf;
6807 }
6809 static enum input_status
6810 prompt_yesno_handler(void *data, char *buf, int c)
6811 {
6812 if (c == 'y' || c == 'Y')
6813 return INPUT_STOP;
6814 if (c == 'n' || c == 'N')
6815 return INPUT_CANCEL;
6816 return INPUT_SKIP;
6817 }
6819 static bool
6820 prompt_yesno(const char *prompt)
6821 {
6822 char prompt2[SIZEOF_STR];
6824 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6825 return FALSE;
6827 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6828 }
6830 static enum input_status
6831 read_prompt_handler(void *data, char *buf, int c)
6832 {
6833 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6834 }
6836 static char *
6837 read_prompt(const char *prompt)
6838 {
6839 return prompt_input(prompt, read_prompt_handler, NULL);
6840 }
6842 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
6843 {
6844 enum input_status status = INPUT_OK;
6845 int size = 0;
6847 while (items[size].text)
6848 size++;
6850 while (status == INPUT_OK) {
6851 const struct menu_item *item = &items[*selected];
6852 int key;
6853 int i;
6855 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
6856 prompt, *selected + 1, size);
6857 if (item->hotkey)
6858 wprintw(status_win, "[%c] ", (char) item->hotkey);
6859 wprintw(status_win, "%s", item->text);
6860 wclrtoeol(status_win);
6862 key = get_input(COLS - 1);
6863 switch (key) {
6864 case KEY_RETURN:
6865 case KEY_ENTER:
6866 case '\n':
6867 status = INPUT_STOP;
6868 break;
6870 case KEY_LEFT:
6871 case KEY_UP:
6872 *selected = *selected - 1;
6873 if (*selected < 0)
6874 *selected = size - 1;
6875 break;
6877 case KEY_RIGHT:
6878 case KEY_DOWN:
6879 *selected = (*selected + 1) % size;
6880 break;
6882 case KEY_ESC:
6883 status = INPUT_CANCEL;
6884 break;
6886 default:
6887 for (i = 0; items[i].text; i++)
6888 if (items[i].hotkey == key) {
6889 *selected = i;
6890 status = INPUT_STOP;
6891 break;
6892 }
6893 }
6894 }
6896 /* Clear the status window */
6897 status_empty = FALSE;
6898 report("");
6900 return status != INPUT_CANCEL;
6901 }
6903 /*
6904 * Repository properties
6905 */
6907 static struct ref **refs = NULL;
6908 static size_t refs_size = 0;
6910 static struct ref_list **ref_lists = NULL;
6911 static size_t ref_lists_size = 0;
6913 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
6914 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6915 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
6917 static int
6918 compare_refs(const void *ref1_, const void *ref2_)
6919 {
6920 const struct ref *ref1 = *(const struct ref **)ref1_;
6921 const struct ref *ref2 = *(const struct ref **)ref2_;
6923 if (ref1->tag != ref2->tag)
6924 return ref2->tag - ref1->tag;
6925 if (ref1->ltag != ref2->ltag)
6926 return ref2->ltag - ref2->ltag;
6927 if (ref1->head != ref2->head)
6928 return ref2->head - ref1->head;
6929 if (ref1->tracked != ref2->tracked)
6930 return ref2->tracked - ref1->tracked;
6931 if (ref1->remote != ref2->remote)
6932 return ref2->remote - ref1->remote;
6933 return strcmp(ref1->name, ref2->name);
6934 }
6936 static void
6937 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6938 {
6939 size_t i;
6941 for (i = 0; i < refs_size; i++)
6942 if (!visitor(data, refs[i]))
6943 break;
6944 }
6946 static struct ref_list *
6947 get_ref_list(const char *id)
6948 {
6949 struct ref_list *list;
6950 size_t i;
6952 for (i = 0; i < ref_lists_size; i++)
6953 if (!strcmp(id, ref_lists[i]->id))
6954 return ref_lists[i];
6956 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
6957 return NULL;
6958 list = calloc(1, sizeof(*list));
6959 if (!list)
6960 return NULL;
6962 for (i = 0; i < refs_size; i++) {
6963 if (!strcmp(id, refs[i]->id) &&
6964 realloc_refs_list(&list->refs, list->size, 1))
6965 list->refs[list->size++] = refs[i];
6966 }
6968 if (!list->refs) {
6969 free(list);
6970 return NULL;
6971 }
6973 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
6974 ref_lists[ref_lists_size++] = list;
6975 return list;
6976 }
6978 static int
6979 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6980 {
6981 struct ref *ref = NULL;
6982 bool tag = FALSE;
6983 bool ltag = FALSE;
6984 bool remote = FALSE;
6985 bool tracked = FALSE;
6986 bool head = FALSE;
6987 int from = 0, to = refs_size - 1;
6989 if (!prefixcmp(name, "refs/tags/")) {
6990 if (!suffixcmp(name, namelen, "^{}")) {
6991 namelen -= 3;
6992 name[namelen] = 0;
6993 } else {
6994 ltag = TRUE;
6995 }
6997 tag = TRUE;
6998 namelen -= STRING_SIZE("refs/tags/");
6999 name += STRING_SIZE("refs/tags/");
7001 } else if (!prefixcmp(name, "refs/remotes/")) {
7002 remote = TRUE;
7003 namelen -= STRING_SIZE("refs/remotes/");
7004 name += STRING_SIZE("refs/remotes/");
7005 tracked = !strcmp(opt_remote, name);
7007 } else if (!prefixcmp(name, "refs/heads/")) {
7008 namelen -= STRING_SIZE("refs/heads/");
7009 name += STRING_SIZE("refs/heads/");
7010 head = !strncmp(opt_head, name, namelen);
7012 } else if (!strcmp(name, "HEAD")) {
7013 string_ncopy(opt_head_rev, id, idlen);
7014 return OK;
7015 }
7017 /* If we are reloading or it's an annotated tag, replace the
7018 * previous SHA1 with the resolved commit id; relies on the fact
7019 * git-ls-remote lists the commit id of an annotated tag right
7020 * before the commit id it points to. */
7021 while (from <= to) {
7022 size_t pos = (to + from) / 2;
7023 int cmp = strcmp(name, refs[pos]->name);
7025 if (!cmp) {
7026 ref = refs[pos];
7027 break;
7028 }
7030 if (cmp < 0)
7031 to = pos - 1;
7032 else
7033 from = pos + 1;
7034 }
7036 if (!ref) {
7037 if (!realloc_refs(&refs, refs_size, 1))
7038 return ERR;
7039 ref = calloc(1, sizeof(*ref) + namelen);
7040 if (!ref)
7041 return ERR;
7042 memmove(refs + from + 1, refs + from,
7043 (refs_size - from) * sizeof(*refs));
7044 refs[from] = ref;
7045 strncpy(ref->name, name, namelen);
7046 refs_size++;
7047 }
7049 ref->head = head;
7050 ref->tag = tag;
7051 ref->ltag = ltag;
7052 ref->remote = remote;
7053 ref->tracked = tracked;
7054 string_copy_rev(ref->id, id);
7056 return OK;
7057 }
7059 static int
7060 load_refs(void)
7061 {
7062 const char *head_argv[] = {
7063 "git", "symbolic-ref", "HEAD", NULL
7064 };
7065 static const char *ls_remote_argv[SIZEOF_ARG] = {
7066 "git", "ls-remote", opt_git_dir, NULL
7067 };
7068 static bool init = FALSE;
7069 size_t i;
7071 if (!init) {
7072 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7073 init = TRUE;
7074 }
7076 if (!*opt_git_dir)
7077 return OK;
7079 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7080 !prefixcmp(opt_head, "refs/heads/")) {
7081 char *offset = opt_head + STRING_SIZE("refs/heads/");
7083 memmove(opt_head, offset, strlen(offset) + 1);
7084 }
7086 for (i = 0; i < refs_size; i++)
7087 refs[i]->id[0] = 0;
7089 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7090 return ERR;
7092 /* Update the ref lists to reflect changes. */
7093 for (i = 0; i < ref_lists_size; i++) {
7094 struct ref_list *list = ref_lists[i];
7095 size_t old, new;
7097 for (old = new = 0; old < list->size; old++)
7098 if (!strcmp(list->id, list->refs[old]->id))
7099 list->refs[new++] = list->refs[old];
7100 list->size = new;
7101 }
7103 return OK;
7104 }
7106 static void
7107 set_remote_branch(const char *name, const char *value, size_t valuelen)
7108 {
7109 if (!strcmp(name, ".remote")) {
7110 string_ncopy(opt_remote, value, valuelen);
7112 } else if (*opt_remote && !strcmp(name, ".merge")) {
7113 size_t from = strlen(opt_remote);
7115 if (!prefixcmp(value, "refs/heads/"))
7116 value += STRING_SIZE("refs/heads/");
7118 if (!string_format_from(opt_remote, &from, "/%s", value))
7119 opt_remote[0] = 0;
7120 }
7121 }
7123 static void
7124 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7125 {
7126 const char *argv[SIZEOF_ARG] = { name, "=" };
7127 int argc = 1 + (cmd == option_set_command);
7128 int error = ERR;
7130 if (!argv_from_string(argv, &argc, value))
7131 config_msg = "Too many option arguments";
7132 else
7133 error = cmd(argc, argv);
7135 if (error == ERR)
7136 warn("Option 'tig.%s': %s", name, config_msg);
7137 }
7139 static bool
7140 set_environment_variable(const char *name, const char *value)
7141 {
7142 size_t len = strlen(name) + 1 + strlen(value) + 1;
7143 char *env = malloc(len);
7145 if (env &&
7146 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7147 putenv(env) == 0)
7148 return TRUE;
7149 free(env);
7150 return FALSE;
7151 }
7153 static void
7154 set_work_tree(const char *value)
7155 {
7156 char cwd[SIZEOF_STR];
7158 if (!getcwd(cwd, sizeof(cwd)))
7159 die("Failed to get cwd path: %s", strerror(errno));
7160 if (chdir(opt_git_dir) < 0)
7161 die("Failed to chdir(%s): %s", strerror(errno));
7162 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7163 die("Failed to get git path: %s", strerror(errno));
7164 if (chdir(cwd) < 0)
7165 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7166 if (chdir(value) < 0)
7167 die("Failed to chdir(%s): %s", value, strerror(errno));
7168 if (!getcwd(cwd, sizeof(cwd)))
7169 die("Failed to get cwd path: %s", strerror(errno));
7170 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7171 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7172 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7173 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7174 opt_is_inside_work_tree = TRUE;
7175 }
7177 static int
7178 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7179 {
7180 if (!strcmp(name, "i18n.commitencoding"))
7181 string_ncopy(opt_encoding, value, valuelen);
7183 else if (!strcmp(name, "core.editor"))
7184 string_ncopy(opt_editor, value, valuelen);
7186 else if (!strcmp(name, "core.worktree"))
7187 set_work_tree(value);
7189 else if (!prefixcmp(name, "tig.color."))
7190 set_repo_config_option(name + 10, value, option_color_command);
7192 else if (!prefixcmp(name, "tig.bind."))
7193 set_repo_config_option(name + 9, value, option_bind_command);
7195 else if (!prefixcmp(name, "tig."))
7196 set_repo_config_option(name + 4, value, option_set_command);
7198 else if (*opt_head && !prefixcmp(name, "branch.") &&
7199 !strncmp(name + 7, opt_head, strlen(opt_head)))
7200 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7202 return OK;
7203 }
7205 static int
7206 load_git_config(void)
7207 {
7208 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7210 return run_io_load(config_list_argv, "=", read_repo_config_option);
7211 }
7213 static int
7214 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7215 {
7216 if (!opt_git_dir[0]) {
7217 string_ncopy(opt_git_dir, name, namelen);
7219 } else if (opt_is_inside_work_tree == -1) {
7220 /* This can be 3 different values depending on the
7221 * version of git being used. If git-rev-parse does not
7222 * understand --is-inside-work-tree it will simply echo
7223 * the option else either "true" or "false" is printed.
7224 * Default to true for the unknown case. */
7225 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7227 } else if (*name == '.') {
7228 string_ncopy(opt_cdup, name, namelen);
7230 } else {
7231 string_ncopy(opt_prefix, name, namelen);
7232 }
7234 return OK;
7235 }
7237 static int
7238 load_repo_info(void)
7239 {
7240 const char *rev_parse_argv[] = {
7241 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7242 "--show-cdup", "--show-prefix", NULL
7243 };
7245 return run_io_load(rev_parse_argv, "=", read_repo_info);
7246 }
7249 /*
7250 * Main
7251 */
7253 static const char usage[] =
7254 "tig " TIG_VERSION " (" __DATE__ ")\n"
7255 "\n"
7256 "Usage: tig [options] [revs] [--] [paths]\n"
7257 " or: tig show [options] [revs] [--] [paths]\n"
7258 " or: tig blame [rev] path\n"
7259 " or: tig status\n"
7260 " or: tig < [git command output]\n"
7261 "\n"
7262 "Options:\n"
7263 " -v, --version Show version and exit\n"
7264 " -h, --help Show help message and exit";
7266 static void __NORETURN
7267 quit(int sig)
7268 {
7269 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7270 if (cursed)
7271 endwin();
7272 exit(0);
7273 }
7275 static void __NORETURN
7276 die(const char *err, ...)
7277 {
7278 va_list args;
7280 endwin();
7282 va_start(args, err);
7283 fputs("tig: ", stderr);
7284 vfprintf(stderr, err, args);
7285 fputs("\n", stderr);
7286 va_end(args);
7288 exit(1);
7289 }
7291 static void
7292 warn(const char *msg, ...)
7293 {
7294 va_list args;
7296 va_start(args, msg);
7297 fputs("tig warning: ", stderr);
7298 vfprintf(stderr, msg, args);
7299 fputs("\n", stderr);
7300 va_end(args);
7301 }
7303 static enum request
7304 parse_options(int argc, const char *argv[])
7305 {
7306 enum request request = REQ_VIEW_MAIN;
7307 const char *subcommand;
7308 bool seen_dashdash = FALSE;
7309 /* XXX: This is vulnerable to the user overriding options
7310 * required for the main view parser. */
7311 const char *custom_argv[SIZEOF_ARG] = {
7312 "git", "log", "--no-color", "--pretty=raw", "--parents",
7313 "--topo-order", NULL
7314 };
7315 int i, j = 6;
7317 if (!isatty(STDIN_FILENO)) {
7318 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7319 return REQ_VIEW_PAGER;
7320 }
7322 if (argc <= 1)
7323 return REQ_NONE;
7325 subcommand = argv[1];
7326 if (!strcmp(subcommand, "status")) {
7327 if (argc > 2)
7328 warn("ignoring arguments after `%s'", subcommand);
7329 return REQ_VIEW_STATUS;
7331 } else if (!strcmp(subcommand, "blame")) {
7332 if (argc <= 2 || argc > 4)
7333 die("invalid number of options to blame\n\n%s", usage);
7335 i = 2;
7336 if (argc == 4) {
7337 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7338 i++;
7339 }
7341 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7342 return REQ_VIEW_BLAME;
7344 } else if (!strcmp(subcommand, "show")) {
7345 request = REQ_VIEW_DIFF;
7347 } else {
7348 subcommand = NULL;
7349 }
7351 if (subcommand) {
7352 custom_argv[1] = subcommand;
7353 j = 2;
7354 }
7356 for (i = 1 + !!subcommand; i < argc; i++) {
7357 const char *opt = argv[i];
7359 if (seen_dashdash || !strcmp(opt, "--")) {
7360 seen_dashdash = TRUE;
7362 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7363 printf("tig version %s\n", TIG_VERSION);
7364 quit(0);
7366 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7367 printf("%s\n", usage);
7368 quit(0);
7369 }
7371 custom_argv[j++] = opt;
7372 if (j >= ARRAY_SIZE(custom_argv))
7373 die("command too long");
7374 }
7376 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7377 die("Failed to format arguments");
7379 return request;
7380 }
7382 int
7383 main(int argc, const char *argv[])
7384 {
7385 enum request request = parse_options(argc, argv);
7386 struct view *view;
7387 size_t i;
7389 signal(SIGINT, quit);
7390 signal(SIGPIPE, SIG_IGN);
7392 if (setlocale(LC_ALL, "")) {
7393 char *codeset = nl_langinfo(CODESET);
7395 string_ncopy(opt_codeset, codeset, strlen(codeset));
7396 }
7398 if (load_repo_info() == ERR)
7399 die("Failed to load repo info.");
7401 if (load_options() == ERR)
7402 die("Failed to load user config.");
7404 if (load_git_config() == ERR)
7405 die("Failed to load repo config.");
7407 /* Require a git repository unless when running in pager mode. */
7408 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7409 die("Not a git repository");
7411 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7412 opt_utf8 = FALSE;
7414 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7415 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7416 if (opt_iconv == ICONV_NONE)
7417 die("Failed to initialize character set conversion");
7418 }
7420 if (load_refs() == ERR)
7421 die("Failed to load refs.");
7423 foreach_view (view, i)
7424 argv_from_env(view->ops->argv, view->cmd_env);
7426 init_display();
7428 if (request != REQ_NONE)
7429 open_view(NULL, request, OPEN_PREPARED);
7430 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7432 while (view_driver(display[current_view], request)) {
7433 int key = get_input(0);
7435 view = display[current_view];
7436 request = get_keybinding(view->keymap, key);
7438 /* Some low-level request handling. This keeps access to
7439 * status_win restricted. */
7440 switch (request) {
7441 case REQ_PROMPT:
7442 {
7443 char *cmd = read_prompt(":");
7445 if (cmd && isdigit(*cmd)) {
7446 int lineno = view->lineno + 1;
7448 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7449 select_view_line(view, lineno - 1);
7450 report("");
7451 } else {
7452 report("Unable to parse '%s' as a line number", cmd);
7453 }
7455 } else if (cmd) {
7456 struct view *next = VIEW(REQ_VIEW_PAGER);
7457 const char *argv[SIZEOF_ARG] = { "git" };
7458 int argc = 1;
7460 /* When running random commands, initially show the
7461 * command in the title. However, it maybe later be
7462 * overwritten if a commit line is selected. */
7463 string_ncopy(next->ref, cmd, strlen(cmd));
7465 if (!argv_from_string(argv, &argc, cmd)) {
7466 report("Too many arguments");
7467 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7468 report("Failed to format command");
7469 } else {
7470 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7471 }
7472 }
7474 request = REQ_NONE;
7475 break;
7476 }
7477 case REQ_SEARCH:
7478 case REQ_SEARCH_BACK:
7479 {
7480 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7481 char *search = read_prompt(prompt);
7483 if (search)
7484 string_ncopy(opt_search, search, strlen(search));
7485 else if (*opt_search)
7486 request = request == REQ_SEARCH ?
7487 REQ_FIND_NEXT :
7488 REQ_FIND_PREV;
7489 else
7490 request = REQ_NONE;
7491 break;
7492 }
7493 default:
7494 break;
7495 }
7496 }
7498 quit(0);
7500 return 0;
7501 }