a968d51bb7b464f65c0a2cabb38cd0fb70173b00
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 int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(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 AUTHOR_COLS 20
108 #define ID_COLS 8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
113 #define TAB_SIZE 8
115 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
117 #define NULL_ID "0000000000000000000000000000000000000000"
119 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
121 #ifndef GIT_CONFIG
122 #define GIT_CONFIG "config"
123 #endif
125 /* Some ASCII-shorthands fitted into the ncurses namespace. */
126 #define KEY_TAB '\t'
127 #define KEY_RETURN '\r'
128 #define KEY_ESC 27
131 struct ref {
132 char *name; /* Ref name; tag or head names are shortened. */
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 unsigned int head:1; /* Is it the current HEAD? */
135 unsigned int tag:1; /* Is it a tag? */
136 unsigned int ltag:1; /* If so, is the tag local? */
137 unsigned int remote:1; /* Is it a remote ref? */
138 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
140 };
142 static struct ref **get_refs(const char *id);
143 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
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 /*
166 * Allocation helpers ... Entering macro hell to never be seen again.
167 */
169 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
170 static type * \
171 name(type **mem, size_t size, size_t increase) \
172 { \
173 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
174 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
175 type *tmp = *mem; \
176 \
177 if (mem == NULL || num_chunks != num_chunks_new) { \
178 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
179 if (tmp) \
180 *mem = tmp; \
181 } \
182 \
183 return tmp; \
184 }
186 /*
187 * String helpers
188 */
190 static inline void
191 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192 {
193 if (srclen > dstlen - 1)
194 srclen = dstlen - 1;
196 strncpy(dst, src, srclen);
197 dst[srclen] = 0;
198 }
200 /* Shorthands for safely copying into a fixed buffer. */
202 #define string_copy(dst, src) \
203 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
205 #define string_ncopy(dst, src, srclen) \
206 string_ncopy_do(dst, sizeof(dst), src, srclen)
208 #define string_copy_rev(dst, src) \
209 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
211 #define string_add(dst, from, src) \
212 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
214 static void
215 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
216 {
217 size_t size, pos;
219 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
220 if (src[pos] == '\t') {
221 size_t expanded = tabsize - (size % tabsize);
223 if (expanded + size >= dstlen - 1)
224 expanded = dstlen - size - 1;
225 memcpy(dst + size, " ", expanded);
226 size += expanded;
227 } else {
228 dst[size++] = src[pos];
229 }
230 }
232 dst[size] = 0;
233 }
235 static char *
236 chomp_string(char *name)
237 {
238 int namelen;
240 while (isspace(*name))
241 name++;
243 namelen = strlen(name) - 1;
244 while (namelen > 0 && isspace(name[namelen]))
245 name[namelen--] = 0;
247 return name;
248 }
250 static bool
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
252 {
253 va_list args;
254 size_t pos = bufpos ? *bufpos : 0;
256 va_start(args, fmt);
257 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
258 va_end(args);
260 if (bufpos)
261 *bufpos = pos;
263 return pos >= bufsize ? FALSE : TRUE;
264 }
266 #define string_format(buf, fmt, args...) \
267 string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270 string_nformat(buf, sizeof(buf), from, fmt, args)
272 static int
273 string_enum_compare(const char *str1, const char *str2, int len)
274 {
275 size_t i;
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279 /* Diff-Header == DIFF_HEADER */
280 for (i = 0; i < len; i++) {
281 if (toupper(str1[i]) == toupper(str2[i]))
282 continue;
284 if (string_enum_sep(str1[i]) &&
285 string_enum_sep(str2[i]))
286 continue;
288 return str1[i] - str2[i];
289 }
291 return 0;
292 }
294 struct enum_map {
295 const char *name;
296 int namelen;
297 int value;
298 };
300 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
302 static bool
303 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
304 {
305 size_t namelen = strlen(name);
306 int i;
308 for (i = 0; i < map_size; i++)
309 if (namelen == map[i].namelen &&
310 !string_enum_compare(name, map[i].name, namelen)) {
311 *value = map[i].value;
312 return TRUE;
313 }
315 return FALSE;
316 }
318 #define map_enum(attr, map, name) \
319 map_enum_do(map, ARRAY_SIZE(map), attr, name)
321 #define prefixcmp(str1, str2) \
322 strncmp(str1, str2, STRING_SIZE(str2))
324 static inline int
325 suffixcmp(const char *str, int slen, const char *suffix)
326 {
327 size_t len = slen >= 0 ? slen : strlen(str);
328 size_t suffixlen = strlen(suffix);
330 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
331 }
334 static const char *
335 mkdate(const time_t *time)
336 {
337 static char buf[DATE_COLS + 1];
338 struct tm tm;
340 gmtime_r(time, &tm);
341 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
342 }
345 static bool
346 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
347 {
348 int valuelen;
350 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
351 bool advance = cmd[valuelen] != 0;
353 cmd[valuelen] = 0;
354 argv[(*argc)++] = chomp_string(cmd);
355 cmd = chomp_string(cmd + valuelen + advance);
356 }
358 if (*argc < SIZEOF_ARG)
359 argv[*argc] = NULL;
360 return *argc < SIZEOF_ARG;
361 }
363 static void
364 argv_from_env(const char **argv, const char *name)
365 {
366 char *env = argv ? getenv(name) : NULL;
367 int argc = 0;
369 if (env && *env)
370 env = strdup(env);
371 if (env && !argv_from_string(argv, &argc, env))
372 die("Too many arguments in the `%s` environment variable", name);
373 }
376 /*
377 * Executing external commands.
378 */
380 enum io_type {
381 IO_FD, /* File descriptor based IO. */
382 IO_BG, /* Execute command in the background. */
383 IO_FG, /* Execute command with same std{in,out,err}. */
384 IO_RD, /* Read only fork+exec IO. */
385 IO_WR, /* Write only fork+exec IO. */
386 IO_AP, /* Append fork+exec output to file. */
387 };
389 struct io {
390 enum io_type type; /* The requested type of pipe. */
391 const char *dir; /* Directory from which to execute. */
392 pid_t pid; /* Pipe for reading or writing. */
393 int pipe; /* Pipe end for reading or writing. */
394 int error; /* Error status. */
395 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
396 char *buf; /* Read buffer. */
397 size_t bufalloc; /* Allocated buffer size. */
398 size_t bufsize; /* Buffer content size. */
399 char *bufpos; /* Current buffer position. */
400 unsigned int eof:1; /* Has end of file been reached. */
401 };
403 static void
404 reset_io(struct io *io)
405 {
406 io->pipe = -1;
407 io->pid = 0;
408 io->buf = io->bufpos = NULL;
409 io->bufalloc = io->bufsize = 0;
410 io->error = 0;
411 io->eof = 0;
412 }
414 static void
415 init_io(struct io *io, const char *dir, enum io_type type)
416 {
417 reset_io(io);
418 io->type = type;
419 io->dir = dir;
420 }
422 static bool
423 init_io_rd(struct io *io, const char *argv[], const char *dir,
424 enum format_flags flags)
425 {
426 init_io(io, dir, IO_RD);
427 return format_argv(io->argv, argv, flags);
428 }
430 static bool
431 io_open(struct io *io, const char *name)
432 {
433 init_io(io, NULL, IO_FD);
434 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
435 if (io->pipe == -1)
436 io->error = errno;
437 return io->pipe != -1;
438 }
440 static bool
441 kill_io(struct io *io)
442 {
443 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
444 }
446 static bool
447 done_io(struct io *io)
448 {
449 pid_t pid = io->pid;
451 if (io->pipe != -1)
452 close(io->pipe);
453 free(io->buf);
454 reset_io(io);
456 while (pid > 0) {
457 int status;
458 pid_t waiting = waitpid(pid, &status, 0);
460 if (waiting < 0) {
461 if (errno == EINTR)
462 continue;
463 report("waitpid failed (%s)", strerror(errno));
464 return FALSE;
465 }
467 return waiting == pid &&
468 !WIFSIGNALED(status) &&
469 WIFEXITED(status) &&
470 !WEXITSTATUS(status);
471 }
473 return TRUE;
474 }
476 static bool
477 start_io(struct io *io)
478 {
479 int pipefds[2] = { -1, -1 };
481 if (io->type == IO_FD)
482 return TRUE;
484 if ((io->type == IO_RD || io->type == IO_WR) &&
485 pipe(pipefds) < 0)
486 return FALSE;
487 else if (io->type == IO_AP)
488 pipefds[1] = io->pipe;
490 if ((io->pid = fork())) {
491 if (pipefds[!(io->type == IO_WR)] != -1)
492 close(pipefds[!(io->type == IO_WR)]);
493 if (io->pid != -1) {
494 io->pipe = pipefds[!!(io->type == IO_WR)];
495 return TRUE;
496 }
498 } else {
499 if (io->type != IO_FG) {
500 int devnull = open("/dev/null", O_RDWR);
501 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
502 int writefd = (io->type == IO_RD || io->type == IO_AP)
503 ? pipefds[1] : devnull;
505 dup2(readfd, STDIN_FILENO);
506 dup2(writefd, STDOUT_FILENO);
507 dup2(devnull, STDERR_FILENO);
509 close(devnull);
510 if (pipefds[0] != -1)
511 close(pipefds[0]);
512 if (pipefds[1] != -1)
513 close(pipefds[1]);
514 }
516 if (io->dir && *io->dir && chdir(io->dir) == -1)
517 die("Failed to change directory: %s", strerror(errno));
519 execvp(io->argv[0], (char *const*) io->argv);
520 die("Failed to execute program: %s", strerror(errno));
521 }
523 if (pipefds[!!(io->type == IO_WR)] != -1)
524 close(pipefds[!!(io->type == IO_WR)]);
525 return FALSE;
526 }
528 static bool
529 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
530 {
531 init_io(io, dir, type);
532 if (!format_argv(io->argv, argv, FORMAT_NONE))
533 return FALSE;
534 return start_io(io);
535 }
537 static int
538 run_io_do(struct io *io)
539 {
540 return start_io(io) && done_io(io);
541 }
543 static int
544 run_io_bg(const char **argv)
545 {
546 struct io io = {};
548 init_io(&io, NULL, IO_BG);
549 if (!format_argv(io.argv, argv, FORMAT_NONE))
550 return FALSE;
551 return run_io_do(&io);
552 }
554 static bool
555 run_io_fg(const char **argv, const char *dir)
556 {
557 struct io io = {};
559 init_io(&io, dir, IO_FG);
560 if (!format_argv(io.argv, argv, FORMAT_NONE))
561 return FALSE;
562 return run_io_do(&io);
563 }
565 static bool
566 run_io_append(const char **argv, enum format_flags flags, int fd)
567 {
568 struct io io = {};
570 init_io(&io, NULL, IO_AP);
571 io.pipe = fd;
572 if (format_argv(io.argv, argv, flags))
573 return run_io_do(&io);
574 close(fd);
575 return FALSE;
576 }
578 static bool
579 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
580 {
581 return init_io_rd(io, argv, NULL, flags) && start_io(io);
582 }
584 static bool
585 io_eof(struct io *io)
586 {
587 return io->eof;
588 }
590 static int
591 io_error(struct io *io)
592 {
593 return io->error;
594 }
596 static char *
597 io_strerror(struct io *io)
598 {
599 return strerror(io->error);
600 }
602 static bool
603 io_can_read(struct io *io)
604 {
605 struct timeval tv = { 0, 500 };
606 fd_set fds;
608 FD_ZERO(&fds);
609 FD_SET(io->pipe, &fds);
611 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
612 }
614 static ssize_t
615 io_read(struct io *io, void *buf, size_t bufsize)
616 {
617 do {
618 ssize_t readsize = read(io->pipe, buf, bufsize);
620 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
621 continue;
622 else if (readsize == -1)
623 io->error = errno;
624 else if (readsize == 0)
625 io->eof = 1;
626 return readsize;
627 } while (1);
628 }
630 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
632 static char *
633 io_get(struct io *io, int c, bool can_read)
634 {
635 char *eol;
636 ssize_t readsize;
638 while (TRUE) {
639 if (io->bufsize > 0) {
640 eol = memchr(io->bufpos, c, io->bufsize);
641 if (eol) {
642 char *line = io->bufpos;
644 *eol = 0;
645 io->bufpos = eol + 1;
646 io->bufsize -= io->bufpos - line;
647 return line;
648 }
649 }
651 if (io_eof(io)) {
652 if (io->bufsize) {
653 io->bufpos[io->bufsize] = 0;
654 io->bufsize = 0;
655 return io->bufpos;
656 }
657 return NULL;
658 }
660 if (!can_read)
661 return NULL;
663 if (io->bufsize > 0 && io->bufpos > io->buf)
664 memmove(io->buf, io->bufpos, io->bufsize);
666 if (io->bufalloc == io->bufsize) {
667 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
668 return NULL;
669 io->bufalloc += BUFSIZ;
670 }
672 io->bufpos = io->buf;
673 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
674 if (io_error(io))
675 return NULL;
676 io->bufsize += readsize;
677 }
678 }
680 static bool
681 io_write(struct io *io, const void *buf, size_t bufsize)
682 {
683 size_t written = 0;
685 while (!io_error(io) && written < bufsize) {
686 ssize_t size;
688 size = write(io->pipe, buf + written, bufsize - written);
689 if (size < 0 && (errno == EAGAIN || errno == EINTR))
690 continue;
691 else if (size == -1)
692 io->error = errno;
693 else
694 written += size;
695 }
697 return written == bufsize;
698 }
700 static bool
701 io_read_buf(struct io *io, char buf[], size_t bufsize)
702 {
703 char *result = io_get(io, '\n', TRUE);
705 if (result) {
706 result = chomp_string(result);
707 string_ncopy_do(buf, bufsize, result, strlen(result));
708 }
710 return done_io(io) && result;
711 }
713 static bool
714 run_io_buf(const char **argv, char buf[], size_t bufsize)
715 {
716 struct io io = {};
718 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
719 }
721 static int
722 io_load(struct io *io, const char *separators,
723 int (*read_property)(char *, size_t, char *, size_t))
724 {
725 char *name;
726 int state = OK;
728 if (!start_io(io))
729 return ERR;
731 while (state == OK && (name = io_get(io, '\n', TRUE))) {
732 char *value;
733 size_t namelen;
734 size_t valuelen;
736 name = chomp_string(name);
737 namelen = strcspn(name, separators);
739 if (name[namelen]) {
740 name[namelen] = 0;
741 value = chomp_string(name + namelen + 1);
742 valuelen = strlen(value);
744 } else {
745 value = "";
746 valuelen = 0;
747 }
749 state = read_property(name, namelen, value, valuelen);
750 }
752 if (state != ERR && io_error(io))
753 state = ERR;
754 done_io(io);
756 return state;
757 }
759 static int
760 run_io_load(const char **argv, const char *separators,
761 int (*read_property)(char *, size_t, char *, size_t))
762 {
763 struct io io = {};
765 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
766 ? io_load(&io, separators, read_property) : ERR;
767 }
770 /*
771 * User requests
772 */
774 #define REQ_INFO \
775 /* XXX: Keep the view request first and in sync with views[]. */ \
776 REQ_GROUP("View switching") \
777 REQ_(VIEW_MAIN, "Show main view"), \
778 REQ_(VIEW_DIFF, "Show diff view"), \
779 REQ_(VIEW_LOG, "Show log view"), \
780 REQ_(VIEW_TREE, "Show tree view"), \
781 REQ_(VIEW_BLOB, "Show blob view"), \
782 REQ_(VIEW_BLAME, "Show blame view"), \
783 REQ_(VIEW_BRANCH, "Show branch view"), \
784 REQ_(VIEW_HELP, "Show help page"), \
785 REQ_(VIEW_PAGER, "Show pager view"), \
786 REQ_(VIEW_STATUS, "Show status view"), \
787 REQ_(VIEW_STAGE, "Show stage view"), \
788 \
789 REQ_GROUP("View manipulation") \
790 REQ_(ENTER, "Enter current line and scroll"), \
791 REQ_(NEXT, "Move to next"), \
792 REQ_(PREVIOUS, "Move to previous"), \
793 REQ_(PARENT, "Move to parent"), \
794 REQ_(VIEW_NEXT, "Move focus to next view"), \
795 REQ_(REFRESH, "Reload and refresh"), \
796 REQ_(MAXIMIZE, "Maximize the current view"), \
797 REQ_(VIEW_CLOSE, "Close the current view"), \
798 REQ_(QUIT, "Close all views and quit"), \
799 \
800 REQ_GROUP("View specific requests") \
801 REQ_(STATUS_UPDATE, "Update file status"), \
802 REQ_(STATUS_REVERT, "Revert file changes"), \
803 REQ_(STATUS_MERGE, "Merge file using external tool"), \
804 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
805 \
806 REQ_GROUP("Cursor navigation") \
807 REQ_(MOVE_UP, "Move cursor one line up"), \
808 REQ_(MOVE_DOWN, "Move cursor one line down"), \
809 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
810 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
811 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
812 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
813 \
814 REQ_GROUP("Scrolling") \
815 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
816 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
817 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
818 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
819 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
820 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
821 \
822 REQ_GROUP("Searching") \
823 REQ_(SEARCH, "Search the view"), \
824 REQ_(SEARCH_BACK, "Search backwards in the view"), \
825 REQ_(FIND_NEXT, "Find next search match"), \
826 REQ_(FIND_PREV, "Find previous search match"), \
827 \
828 REQ_GROUP("Option manipulation") \
829 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
830 REQ_(TOGGLE_DATE, "Toggle date display"), \
831 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
832 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
833 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
834 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
835 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
836 \
837 REQ_GROUP("Misc") \
838 REQ_(PROMPT, "Bring up the prompt"), \
839 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
840 REQ_(SHOW_VERSION, "Show version information"), \
841 REQ_(STOP_LOADING, "Stop all loading views"), \
842 REQ_(EDIT, "Open in editor"), \
843 REQ_(NONE, "Do nothing")
846 /* User action requests. */
847 enum request {
848 #define REQ_GROUP(help)
849 #define REQ_(req, help) REQ_##req
851 /* Offset all requests to avoid conflicts with ncurses getch values. */
852 REQ_OFFSET = KEY_MAX + 1,
853 REQ_INFO
855 #undef REQ_GROUP
856 #undef REQ_
857 };
859 struct request_info {
860 enum request request;
861 const char *name;
862 int namelen;
863 const char *help;
864 };
866 static const struct request_info req_info[] = {
867 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
868 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
869 REQ_INFO
870 #undef REQ_GROUP
871 #undef REQ_
872 };
874 static enum request
875 get_request(const char *name)
876 {
877 int namelen = strlen(name);
878 int i;
880 for (i = 0; i < ARRAY_SIZE(req_info); i++)
881 if (req_info[i].namelen == namelen &&
882 !string_enum_compare(req_info[i].name, name, namelen))
883 return req_info[i].request;
885 return REQ_NONE;
886 }
889 /*
890 * Options
891 */
893 /* Option and state variables. */
894 static bool opt_date = TRUE;
895 static bool opt_author = TRUE;
896 static bool opt_line_number = FALSE;
897 static bool opt_line_graphics = TRUE;
898 static bool opt_rev_graph = FALSE;
899 static bool opt_show_refs = TRUE;
900 static int opt_num_interval = NUMBER_INTERVAL;
901 static double opt_hscroll = 0.50;
902 static int opt_tab_size = TAB_SIZE;
903 static int opt_author_cols = AUTHOR_COLS-1;
904 static char opt_path[SIZEOF_STR] = "";
905 static char opt_file[SIZEOF_STR] = "";
906 static char opt_ref[SIZEOF_REF] = "";
907 static char opt_head[SIZEOF_REF] = "";
908 static char opt_head_rev[SIZEOF_REV] = "";
909 static char opt_remote[SIZEOF_REF] = "";
910 static char opt_encoding[20] = "UTF-8";
911 static bool opt_utf8 = TRUE;
912 static char opt_codeset[20] = "UTF-8";
913 static iconv_t opt_iconv = ICONV_NONE;
914 static char opt_search[SIZEOF_STR] = "";
915 static char opt_cdup[SIZEOF_STR] = "";
916 static char opt_prefix[SIZEOF_STR] = "";
917 static char opt_git_dir[SIZEOF_STR] = "";
918 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
919 static char opt_editor[SIZEOF_STR] = "";
920 static FILE *opt_tty = NULL;
922 #define is_initial_commit() (!*opt_head_rev)
923 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
926 /*
927 * Line-oriented content detection.
928 */
930 #define LINE_INFO \
931 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
932 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
933 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
934 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
935 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
936 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
937 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
938 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
939 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
940 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
941 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
943 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
944 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
945 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
946 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
947 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
948 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
950 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
951 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
952 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
953 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
954 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
955 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
956 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
957 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
958 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
959 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
960 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
961 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
962 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
963 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
964 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
965 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
966 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
967 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
968 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
969 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
970 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
971 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
972 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
973 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
974 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
975 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
976 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
977 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
978 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
979 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
980 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
981 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
982 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
983 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
984 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
985 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
987 enum line_type {
988 #define LINE(type, line, fg, bg, attr) \
989 LINE_##type
990 LINE_INFO,
991 LINE_NONE
992 #undef LINE
993 };
995 struct line_info {
996 const char *name; /* Option name. */
997 int namelen; /* Size of option name. */
998 const char *line; /* The start of line to match. */
999 int linelen; /* Size of string to match. */
1000 int fg, bg, attr; /* Color and text attributes for the lines. */
1001 };
1003 static struct line_info line_info[] = {
1004 #define LINE(type, line, fg, bg, attr) \
1005 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1006 LINE_INFO
1007 #undef LINE
1008 };
1010 static enum line_type
1011 get_line_type(const char *line)
1012 {
1013 int linelen = strlen(line);
1014 enum line_type type;
1016 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1017 /* Case insensitive search matches Signed-off-by lines better. */
1018 if (linelen >= line_info[type].linelen &&
1019 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1020 return type;
1022 return LINE_DEFAULT;
1023 }
1025 static inline int
1026 get_line_attr(enum line_type type)
1027 {
1028 assert(type < ARRAY_SIZE(line_info));
1029 return COLOR_PAIR(type) | line_info[type].attr;
1030 }
1032 static struct line_info *
1033 get_line_info(const char *name)
1034 {
1035 size_t namelen = strlen(name);
1036 enum line_type type;
1038 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1039 if (namelen == line_info[type].namelen &&
1040 !string_enum_compare(line_info[type].name, name, namelen))
1041 return &line_info[type];
1043 return NULL;
1044 }
1046 static void
1047 init_colors(void)
1048 {
1049 int default_bg = line_info[LINE_DEFAULT].bg;
1050 int default_fg = line_info[LINE_DEFAULT].fg;
1051 enum line_type type;
1053 start_color();
1055 if (assume_default_colors(default_fg, default_bg) == ERR) {
1056 default_bg = COLOR_BLACK;
1057 default_fg = COLOR_WHITE;
1058 }
1060 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1061 struct line_info *info = &line_info[type];
1062 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1063 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1065 init_pair(type, fg, bg);
1066 }
1067 }
1069 struct line {
1070 enum line_type type;
1072 /* State flags */
1073 unsigned int selected:1;
1074 unsigned int dirty:1;
1075 unsigned int cleareol:1;
1077 void *data; /* User data */
1078 };
1081 /*
1082 * Keys
1083 */
1085 struct keybinding {
1086 int alias;
1087 enum request request;
1088 };
1090 static const struct keybinding default_keybindings[] = {
1091 /* View switching */
1092 { 'm', REQ_VIEW_MAIN },
1093 { 'd', REQ_VIEW_DIFF },
1094 { 'l', REQ_VIEW_LOG },
1095 { 't', REQ_VIEW_TREE },
1096 { 'f', REQ_VIEW_BLOB },
1097 { 'B', REQ_VIEW_BLAME },
1098 { 'H', REQ_VIEW_BRANCH },
1099 { 'p', REQ_VIEW_PAGER },
1100 { 'h', REQ_VIEW_HELP },
1101 { 'S', REQ_VIEW_STATUS },
1102 { 'c', REQ_VIEW_STAGE },
1104 /* View manipulation */
1105 { 'q', REQ_VIEW_CLOSE },
1106 { KEY_TAB, REQ_VIEW_NEXT },
1107 { KEY_RETURN, REQ_ENTER },
1108 { KEY_UP, REQ_PREVIOUS },
1109 { KEY_DOWN, REQ_NEXT },
1110 { 'R', REQ_REFRESH },
1111 { KEY_F(5), REQ_REFRESH },
1112 { 'O', REQ_MAXIMIZE },
1114 /* Cursor navigation */
1115 { 'k', REQ_MOVE_UP },
1116 { 'j', REQ_MOVE_DOWN },
1117 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1118 { KEY_END, REQ_MOVE_LAST_LINE },
1119 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1120 { ' ', REQ_MOVE_PAGE_DOWN },
1121 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1122 { 'b', REQ_MOVE_PAGE_UP },
1123 { '-', REQ_MOVE_PAGE_UP },
1125 /* Scrolling */
1126 { KEY_LEFT, REQ_SCROLL_LEFT },
1127 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1128 { KEY_IC, REQ_SCROLL_LINE_UP },
1129 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1130 { 'w', REQ_SCROLL_PAGE_UP },
1131 { 's', REQ_SCROLL_PAGE_DOWN },
1133 /* Searching */
1134 { '/', REQ_SEARCH },
1135 { '?', REQ_SEARCH_BACK },
1136 { 'n', REQ_FIND_NEXT },
1137 { 'N', REQ_FIND_PREV },
1139 /* Misc */
1140 { 'Q', REQ_QUIT },
1141 { 'z', REQ_STOP_LOADING },
1142 { 'v', REQ_SHOW_VERSION },
1143 { 'r', REQ_SCREEN_REDRAW },
1144 { '.', REQ_TOGGLE_LINENO },
1145 { 'D', REQ_TOGGLE_DATE },
1146 { 'A', REQ_TOGGLE_AUTHOR },
1147 { 'g', REQ_TOGGLE_REV_GRAPH },
1148 { 'F', REQ_TOGGLE_REFS },
1149 { 'I', REQ_TOGGLE_SORT_ORDER },
1150 { 'i', REQ_TOGGLE_SORT_FIELD },
1151 { ':', REQ_PROMPT },
1152 { 'u', REQ_STATUS_UPDATE },
1153 { '!', REQ_STATUS_REVERT },
1154 { 'M', REQ_STATUS_MERGE },
1155 { '@', REQ_STAGE_NEXT },
1156 { ',', REQ_PARENT },
1157 { 'e', REQ_EDIT },
1158 };
1160 #define KEYMAP_INFO \
1161 KEYMAP_(GENERIC), \
1162 KEYMAP_(MAIN), \
1163 KEYMAP_(DIFF), \
1164 KEYMAP_(LOG), \
1165 KEYMAP_(TREE), \
1166 KEYMAP_(BLOB), \
1167 KEYMAP_(BLAME), \
1168 KEYMAP_(BRANCH), \
1169 KEYMAP_(PAGER), \
1170 KEYMAP_(HELP), \
1171 KEYMAP_(STATUS), \
1172 KEYMAP_(STAGE)
1174 enum keymap {
1175 #define KEYMAP_(name) KEYMAP_##name
1176 KEYMAP_INFO
1177 #undef KEYMAP_
1178 };
1180 static const struct enum_map keymap_table[] = {
1181 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1182 KEYMAP_INFO
1183 #undef KEYMAP_
1184 };
1186 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1188 struct keybinding_table {
1189 struct keybinding *data;
1190 size_t size;
1191 };
1193 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1195 static void
1196 add_keybinding(enum keymap keymap, enum request request, int key)
1197 {
1198 struct keybinding_table *table = &keybindings[keymap];
1200 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1201 if (!table->data)
1202 die("Failed to allocate keybinding");
1203 table->data[table->size].alias = key;
1204 table->data[table->size++].request = request;
1205 }
1207 /* Looks for a key binding first in the given map, then in the generic map, and
1208 * lastly in the default keybindings. */
1209 static enum request
1210 get_keybinding(enum keymap keymap, int key)
1211 {
1212 size_t i;
1214 for (i = 0; i < keybindings[keymap].size; i++)
1215 if (keybindings[keymap].data[i].alias == key)
1216 return keybindings[keymap].data[i].request;
1218 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1219 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1220 return keybindings[KEYMAP_GENERIC].data[i].request;
1222 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1223 if (default_keybindings[i].alias == key)
1224 return default_keybindings[i].request;
1226 return (enum request) key;
1227 }
1230 struct key {
1231 const char *name;
1232 int value;
1233 };
1235 static const struct key key_table[] = {
1236 { "Enter", KEY_RETURN },
1237 { "Space", ' ' },
1238 { "Backspace", KEY_BACKSPACE },
1239 { "Tab", KEY_TAB },
1240 { "Escape", KEY_ESC },
1241 { "Left", KEY_LEFT },
1242 { "Right", KEY_RIGHT },
1243 { "Up", KEY_UP },
1244 { "Down", KEY_DOWN },
1245 { "Insert", KEY_IC },
1246 { "Delete", KEY_DC },
1247 { "Hash", '#' },
1248 { "Home", KEY_HOME },
1249 { "End", KEY_END },
1250 { "PageUp", KEY_PPAGE },
1251 { "PageDown", KEY_NPAGE },
1252 { "F1", KEY_F(1) },
1253 { "F2", KEY_F(2) },
1254 { "F3", KEY_F(3) },
1255 { "F4", KEY_F(4) },
1256 { "F5", KEY_F(5) },
1257 { "F6", KEY_F(6) },
1258 { "F7", KEY_F(7) },
1259 { "F8", KEY_F(8) },
1260 { "F9", KEY_F(9) },
1261 { "F10", KEY_F(10) },
1262 { "F11", KEY_F(11) },
1263 { "F12", KEY_F(12) },
1264 };
1266 static int
1267 get_key_value(const char *name)
1268 {
1269 int i;
1271 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1272 if (!strcasecmp(key_table[i].name, name))
1273 return key_table[i].value;
1275 if (strlen(name) == 1 && isprint(*name))
1276 return (int) *name;
1278 return ERR;
1279 }
1281 static const char *
1282 get_key_name(int key_value)
1283 {
1284 static char key_char[] = "'X'";
1285 const char *seq = NULL;
1286 int key;
1288 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1289 if (key_table[key].value == key_value)
1290 seq = key_table[key].name;
1292 if (seq == NULL &&
1293 key_value < 127 &&
1294 isprint(key_value)) {
1295 key_char[1] = (char) key_value;
1296 seq = key_char;
1297 }
1299 return seq ? seq : "(no key)";
1300 }
1302 static const char *
1303 get_key(enum request request)
1304 {
1305 static char buf[BUFSIZ];
1306 size_t pos = 0;
1307 char *sep = "";
1308 int i;
1310 buf[pos] = 0;
1312 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1313 const struct keybinding *keybinding = &default_keybindings[i];
1315 if (keybinding->request != request)
1316 continue;
1318 if (!string_format_from(buf, &pos, "%s%s", sep,
1319 get_key_name(keybinding->alias)))
1320 return "Too many keybindings!";
1321 sep = ", ";
1322 }
1324 return buf;
1325 }
1327 struct run_request {
1328 enum keymap keymap;
1329 int key;
1330 const char *argv[SIZEOF_ARG];
1331 };
1333 static struct run_request *run_request;
1334 static size_t run_requests;
1336 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1338 static enum request
1339 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1340 {
1341 struct run_request *req;
1343 if (argc >= ARRAY_SIZE(req->argv) - 1)
1344 return REQ_NONE;
1346 if (!realloc_run_requests(&run_request, run_requests, 1))
1347 return REQ_NONE;
1349 req = &run_request[run_requests];
1350 req->keymap = keymap;
1351 req->key = key;
1352 req->argv[0] = NULL;
1354 if (!format_argv(req->argv, argv, FORMAT_NONE))
1355 return REQ_NONE;
1357 return REQ_NONE + ++run_requests;
1358 }
1360 static struct run_request *
1361 get_run_request(enum request request)
1362 {
1363 if (request <= REQ_NONE)
1364 return NULL;
1365 return &run_request[request - REQ_NONE - 1];
1366 }
1368 static void
1369 add_builtin_run_requests(void)
1370 {
1371 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1372 const char *gc[] = { "git", "gc", NULL };
1373 struct {
1374 enum keymap keymap;
1375 int key;
1376 int argc;
1377 const char **argv;
1378 } reqs[] = {
1379 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1380 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1381 };
1382 int i;
1384 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1385 enum request req;
1387 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1388 if (req != REQ_NONE)
1389 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1390 }
1391 }
1393 /*
1394 * User config file handling.
1395 */
1397 static int config_lineno;
1398 static bool config_errors;
1399 static const char *config_msg;
1401 static const struct enum_map color_map[] = {
1402 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1403 COLOR_MAP(DEFAULT),
1404 COLOR_MAP(BLACK),
1405 COLOR_MAP(BLUE),
1406 COLOR_MAP(CYAN),
1407 COLOR_MAP(GREEN),
1408 COLOR_MAP(MAGENTA),
1409 COLOR_MAP(RED),
1410 COLOR_MAP(WHITE),
1411 COLOR_MAP(YELLOW),
1412 };
1414 static const struct enum_map attr_map[] = {
1415 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1416 ATTR_MAP(NORMAL),
1417 ATTR_MAP(BLINK),
1418 ATTR_MAP(BOLD),
1419 ATTR_MAP(DIM),
1420 ATTR_MAP(REVERSE),
1421 ATTR_MAP(STANDOUT),
1422 ATTR_MAP(UNDERLINE),
1423 };
1425 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1427 static int parse_step(double *opt, const char *arg)
1428 {
1429 *opt = atoi(arg);
1430 if (!strchr(arg, '%'))
1431 return OK;
1433 /* "Shift down" so 100% and 1 does not conflict. */
1434 *opt = (*opt - 1) / 100;
1435 if (*opt >= 1.0) {
1436 *opt = 0.99;
1437 config_msg = "Step value larger than 100%";
1438 return ERR;
1439 }
1440 if (*opt < 0.0) {
1441 *opt = 1;
1442 config_msg = "Invalid step value";
1443 return ERR;
1444 }
1445 return OK;
1446 }
1448 static int
1449 parse_int(int *opt, const char *arg, int min, int max)
1450 {
1451 int value = atoi(arg);
1453 if (min <= value && value <= max) {
1454 *opt = value;
1455 return OK;
1456 }
1458 config_msg = "Integer value out of bound";
1459 return ERR;
1460 }
1462 static bool
1463 set_color(int *color, const char *name)
1464 {
1465 if (map_enum(color, color_map, name))
1466 return TRUE;
1467 if (!prefixcmp(name, "color"))
1468 return parse_int(color, name + 5, 0, 255) == OK;
1469 return FALSE;
1470 }
1472 /* Wants: object fgcolor bgcolor [attribute] */
1473 static int
1474 option_color_command(int argc, const char *argv[])
1475 {
1476 struct line_info *info;
1478 if (argc != 3 && argc != 4) {
1479 config_msg = "Wrong number of arguments given to color command";
1480 return ERR;
1481 }
1483 info = get_line_info(argv[0]);
1484 if (!info) {
1485 static const struct enum_map obsolete[] = {
1486 ENUM_MAP("main-delim", LINE_DELIMITER),
1487 ENUM_MAP("main-date", LINE_DATE),
1488 ENUM_MAP("main-author", LINE_AUTHOR),
1489 };
1490 int index;
1492 if (!map_enum(&index, obsolete, argv[0])) {
1493 config_msg = "Unknown color name";
1494 return ERR;
1495 }
1496 info = &line_info[index];
1497 }
1499 if (!set_color(&info->fg, argv[1]) ||
1500 !set_color(&info->bg, argv[2])) {
1501 config_msg = "Unknown color";
1502 return ERR;
1503 }
1505 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1506 config_msg = "Unknown attribute";
1507 return ERR;
1508 }
1510 return OK;
1511 }
1513 static int parse_bool(bool *opt, const char *arg)
1514 {
1515 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1516 ? TRUE : FALSE;
1517 return OK;
1518 }
1520 static int
1521 parse_string(char *opt, const char *arg, size_t optsize)
1522 {
1523 int arglen = strlen(arg);
1525 switch (arg[0]) {
1526 case '\"':
1527 case '\'':
1528 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1529 config_msg = "Unmatched quotation";
1530 return ERR;
1531 }
1532 arg += 1; arglen -= 2;
1533 default:
1534 string_ncopy_do(opt, optsize, arg, arglen);
1535 return OK;
1536 }
1537 }
1539 /* Wants: name = value */
1540 static int
1541 option_set_command(int argc, const char *argv[])
1542 {
1543 if (argc != 3) {
1544 config_msg = "Wrong number of arguments given to set command";
1545 return ERR;
1546 }
1548 if (strcmp(argv[1], "=")) {
1549 config_msg = "No value assigned";
1550 return ERR;
1551 }
1553 if (!strcmp(argv[0], "show-author"))
1554 return parse_bool(&opt_author, argv[2]);
1556 if (!strcmp(argv[0], "show-date"))
1557 return parse_bool(&opt_date, argv[2]);
1559 if (!strcmp(argv[0], "show-rev-graph"))
1560 return parse_bool(&opt_rev_graph, argv[2]);
1562 if (!strcmp(argv[0], "show-refs"))
1563 return parse_bool(&opt_show_refs, argv[2]);
1565 if (!strcmp(argv[0], "show-line-numbers"))
1566 return parse_bool(&opt_line_number, argv[2]);
1568 if (!strcmp(argv[0], "line-graphics"))
1569 return parse_bool(&opt_line_graphics, argv[2]);
1571 if (!strcmp(argv[0], "line-number-interval"))
1572 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1574 if (!strcmp(argv[0], "author-width"))
1575 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1577 if (!strcmp(argv[0], "horizontal-scroll"))
1578 return parse_step(&opt_hscroll, argv[2]);
1580 if (!strcmp(argv[0], "tab-size"))
1581 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1583 if (!strcmp(argv[0], "commit-encoding"))
1584 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1586 config_msg = "Unknown variable name";
1587 return ERR;
1588 }
1590 /* Wants: mode request key */
1591 static int
1592 option_bind_command(int argc, const char *argv[])
1593 {
1594 enum request request;
1595 int keymap;
1596 int key;
1598 if (argc < 3) {
1599 config_msg = "Wrong number of arguments given to bind command";
1600 return ERR;
1601 }
1603 if (set_keymap(&keymap, argv[0]) == ERR) {
1604 config_msg = "Unknown key map";
1605 return ERR;
1606 }
1608 key = get_key_value(argv[1]);
1609 if (key == ERR) {
1610 config_msg = "Unknown key";
1611 return ERR;
1612 }
1614 request = get_request(argv[2]);
1615 if (request == REQ_NONE) {
1616 static const struct enum_map obsolete[] = {
1617 ENUM_MAP("cherry-pick", REQ_NONE),
1618 ENUM_MAP("screen-resize", REQ_NONE),
1619 ENUM_MAP("tree-parent", REQ_PARENT),
1620 };
1621 int alias;
1623 if (map_enum(&alias, obsolete, argv[2])) {
1624 if (alias != REQ_NONE)
1625 add_keybinding(keymap, alias, key);
1626 config_msg = "Obsolete request name";
1627 return ERR;
1628 }
1629 }
1630 if (request == REQ_NONE && *argv[2]++ == '!')
1631 request = add_run_request(keymap, key, argc - 2, argv + 2);
1632 if (request == REQ_NONE) {
1633 config_msg = "Unknown request name";
1634 return ERR;
1635 }
1637 add_keybinding(keymap, request, key);
1639 return OK;
1640 }
1642 static int
1643 set_option(const char *opt, char *value)
1644 {
1645 const char *argv[SIZEOF_ARG];
1646 int argc = 0;
1648 if (!argv_from_string(argv, &argc, value)) {
1649 config_msg = "Too many option arguments";
1650 return ERR;
1651 }
1653 if (!strcmp(opt, "color"))
1654 return option_color_command(argc, argv);
1656 if (!strcmp(opt, "set"))
1657 return option_set_command(argc, argv);
1659 if (!strcmp(opt, "bind"))
1660 return option_bind_command(argc, argv);
1662 config_msg = "Unknown option command";
1663 return ERR;
1664 }
1666 static int
1667 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1668 {
1669 int status = OK;
1671 config_lineno++;
1672 config_msg = "Internal error";
1674 /* Check for comment markers, since read_properties() will
1675 * only ensure opt and value are split at first " \t". */
1676 optlen = strcspn(opt, "#");
1677 if (optlen == 0)
1678 return OK;
1680 if (opt[optlen] != 0) {
1681 config_msg = "No option value";
1682 status = ERR;
1684 } else {
1685 /* Look for comment endings in the value. */
1686 size_t len = strcspn(value, "#");
1688 if (len < valuelen) {
1689 valuelen = len;
1690 value[valuelen] = 0;
1691 }
1693 status = set_option(opt, value);
1694 }
1696 if (status == ERR) {
1697 warn("Error on line %d, near '%.*s': %s",
1698 config_lineno, (int) optlen, opt, config_msg);
1699 config_errors = TRUE;
1700 }
1702 /* Always keep going if errors are encountered. */
1703 return OK;
1704 }
1706 static void
1707 load_option_file(const char *path)
1708 {
1709 struct io io = {};
1711 /* It's OK that the file doesn't exist. */
1712 if (!io_open(&io, path))
1713 return;
1715 config_lineno = 0;
1716 config_errors = FALSE;
1718 if (io_load(&io, " \t", read_option) == ERR ||
1719 config_errors == TRUE)
1720 warn("Errors while loading %s.", path);
1721 }
1723 static int
1724 load_options(void)
1725 {
1726 const char *home = getenv("HOME");
1727 const char *tigrc_user = getenv("TIGRC_USER");
1728 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1729 char buf[SIZEOF_STR];
1731 add_builtin_run_requests();
1733 if (!tigrc_system)
1734 tigrc_system = SYSCONFDIR "/tigrc";
1735 load_option_file(tigrc_system);
1737 if (!tigrc_user) {
1738 if (!home || !string_format(buf, "%s/.tigrc", home))
1739 return ERR;
1740 tigrc_user = buf;
1741 }
1742 load_option_file(tigrc_user);
1744 return OK;
1745 }
1748 /*
1749 * The viewer
1750 */
1752 struct view;
1753 struct view_ops;
1755 /* The display array of active views and the index of the current view. */
1756 static struct view *display[2];
1757 static unsigned int current_view;
1759 #define foreach_displayed_view(view, i) \
1760 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1762 #define displayed_views() (display[1] != NULL ? 2 : 1)
1764 /* Current head and commit ID */
1765 static char ref_blob[SIZEOF_REF] = "";
1766 static char ref_commit[SIZEOF_REF] = "HEAD";
1767 static char ref_head[SIZEOF_REF] = "HEAD";
1769 struct view {
1770 const char *name; /* View name */
1771 const char *cmd_env; /* Command line set via environment */
1772 const char *id; /* Points to either of ref_{head,commit,blob} */
1774 struct view_ops *ops; /* View operations */
1776 enum keymap keymap; /* What keymap does this view have */
1777 bool git_dir; /* Whether the view requires a git directory. */
1779 char ref[SIZEOF_REF]; /* Hovered commit reference */
1780 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1782 int height, width; /* The width and height of the main window */
1783 WINDOW *win; /* The main window */
1784 WINDOW *title; /* The title window living below the main window */
1786 /* Navigation */
1787 unsigned long offset; /* Offset of the window top */
1788 unsigned long yoffset; /* Offset from the window side. */
1789 unsigned long lineno; /* Current line number */
1790 unsigned long p_offset; /* Previous offset of the window top */
1791 unsigned long p_yoffset;/* Previous offset from the window side */
1792 unsigned long p_lineno; /* Previous current line number */
1793 bool p_restore; /* Should the previous position be restored. */
1795 /* Searching */
1796 char grep[SIZEOF_STR]; /* Search string */
1797 regex_t *regex; /* Pre-compiled regexp */
1799 /* If non-NULL, points to the view that opened this view. If this view
1800 * is closed tig will switch back to the parent view. */
1801 struct view *parent;
1803 /* Buffering */
1804 size_t lines; /* Total number of lines */
1805 struct line *line; /* Line index */
1806 unsigned int digits; /* Number of digits in the lines member. */
1808 /* Drawing */
1809 struct line *curline; /* Line currently being drawn. */
1810 enum line_type curtype; /* Attribute currently used for drawing. */
1811 unsigned long col; /* Column when drawing. */
1812 bool has_scrolled; /* View was scrolled. */
1814 /* Loading */
1815 struct io io;
1816 struct io *pipe;
1817 time_t start_time;
1818 time_t update_secs;
1819 };
1821 struct view_ops {
1822 /* What type of content being displayed. Used in the title bar. */
1823 const char *type;
1824 /* Default command arguments. */
1825 const char **argv;
1826 /* Open and reads in all view content. */
1827 bool (*open)(struct view *view);
1828 /* Read one line; updates view->line. */
1829 bool (*read)(struct view *view, char *data);
1830 /* Draw one line; @lineno must be < view->height. */
1831 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1832 /* Depending on view handle a special requests. */
1833 enum request (*request)(struct view *view, enum request request, struct line *line);
1834 /* Search for regexp in a line. */
1835 bool (*grep)(struct view *view, struct line *line);
1836 /* Select line */
1837 void (*select)(struct view *view, struct line *line);
1838 };
1840 static struct view_ops blame_ops;
1841 static struct view_ops blob_ops;
1842 static struct view_ops diff_ops;
1843 static struct view_ops help_ops;
1844 static struct view_ops log_ops;
1845 static struct view_ops main_ops;
1846 static struct view_ops pager_ops;
1847 static struct view_ops stage_ops;
1848 static struct view_ops status_ops;
1849 static struct view_ops tree_ops;
1850 static struct view_ops branch_ops;
1852 #define VIEW_STR(name, env, ref, ops, map, git) \
1853 { name, #env, ref, ops, map, git }
1855 #define VIEW_(id, name, ops, git, ref) \
1856 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1859 static struct view views[] = {
1860 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1861 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1862 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1863 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1864 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1865 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1866 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
1867 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1868 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1869 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1870 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1871 };
1873 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1874 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1876 #define foreach_view(view, i) \
1877 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1879 #define view_is_displayed(view) \
1880 (view == display[0] || view == display[1])
1883 enum line_graphic {
1884 LINE_GRAPHIC_VLINE
1885 };
1887 static chtype line_graphics[] = {
1888 /* LINE_GRAPHIC_VLINE: */ '|'
1889 };
1891 static inline void
1892 set_view_attr(struct view *view, enum line_type type)
1893 {
1894 if (!view->curline->selected && view->curtype != type) {
1895 wattrset(view->win, get_line_attr(type));
1896 wchgat(view->win, -1, 0, type, NULL);
1897 view->curtype = type;
1898 }
1899 }
1901 static int
1902 draw_chars(struct view *view, enum line_type type, const char *string,
1903 int max_len, bool use_tilde)
1904 {
1905 int len = 0;
1906 int col = 0;
1907 int trimmed = FALSE;
1908 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1910 if (max_len <= 0)
1911 return 0;
1913 if (opt_utf8) {
1914 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1915 } else {
1916 col = len = strlen(string);
1917 if (len > max_len) {
1918 if (use_tilde) {
1919 max_len -= 1;
1920 }
1921 col = len = max_len;
1922 trimmed = TRUE;
1923 }
1924 }
1926 set_view_attr(view, type);
1927 if (len > 0)
1928 waddnstr(view->win, string, len);
1929 if (trimmed && use_tilde) {
1930 set_view_attr(view, LINE_DELIMITER);
1931 waddch(view->win, '~');
1932 col++;
1933 }
1935 return col;
1936 }
1938 static int
1939 draw_space(struct view *view, enum line_type type, int max, int spaces)
1940 {
1941 static char space[] = " ";
1942 int col = 0;
1944 spaces = MIN(max, spaces);
1946 while (spaces > 0) {
1947 int len = MIN(spaces, sizeof(space) - 1);
1949 col += draw_chars(view, type, space, len, FALSE);
1950 spaces -= len;
1951 }
1953 return col;
1954 }
1956 static bool
1957 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1958 {
1959 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1960 return view->width + view->yoffset <= view->col;
1961 }
1963 static bool
1964 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1965 {
1966 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1967 int max = view->width + view->yoffset - view->col;
1968 int i;
1970 if (max < size)
1971 size = max;
1973 set_view_attr(view, type);
1974 /* Using waddch() instead of waddnstr() ensures that
1975 * they'll be rendered correctly for the cursor line. */
1976 for (i = skip; i < size; i++)
1977 waddch(view->win, graphic[i]);
1979 view->col += size;
1980 if (size < max && skip <= size)
1981 waddch(view->win, ' ');
1982 view->col++;
1984 return view->width + view->yoffset <= view->col;
1985 }
1987 static bool
1988 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1989 {
1990 int max = MIN(view->width + view->yoffset - view->col, len);
1991 int col;
1993 if (text)
1994 col = draw_chars(view, type, text, max - 1, trim);
1995 else
1996 col = draw_space(view, type, max - 1, max - 1);
1998 view->col += col;
1999 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2000 return view->width + view->yoffset <= view->col;
2001 }
2003 static bool
2004 draw_date(struct view *view, time_t *time)
2005 {
2006 const char *date = mkdate(time);
2008 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2009 }
2011 static bool
2012 draw_author(struct view *view, const char *author)
2013 {
2014 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2016 if (!trim) {
2017 static char initials[10];
2018 size_t pos;
2020 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2022 memset(initials, 0, sizeof(initials));
2023 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2024 while (is_initial_sep(*author))
2025 author++;
2026 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2027 while (*author && !is_initial_sep(author[1]))
2028 author++;
2029 }
2031 author = initials;
2032 }
2034 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2035 }
2037 static bool
2038 draw_mode(struct view *view, mode_t mode)
2039 {
2040 const char *str;
2042 if (S_ISDIR(mode))
2043 str = "drwxr-xr-x";
2044 else if (S_ISLNK(mode))
2045 str = "lrwxrwxrwx";
2046 else if (S_ISGITLINK(mode))
2047 str = "m---------";
2048 else if (S_ISREG(mode) && mode & S_IXUSR)
2049 str = "-rwxr-xr-x";
2050 else if (S_ISREG(mode))
2051 str = "-rw-r--r--";
2052 else
2053 str = "----------";
2055 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2056 }
2058 static bool
2059 draw_lineno(struct view *view, unsigned int lineno)
2060 {
2061 char number[10];
2062 int digits3 = view->digits < 3 ? 3 : view->digits;
2063 int max = MIN(view->width + view->yoffset - view->col, digits3);
2064 char *text = NULL;
2066 lineno += view->offset + 1;
2067 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2068 static char fmt[] = "%1ld";
2070 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2071 if (string_format(number, fmt, lineno))
2072 text = number;
2073 }
2074 if (text)
2075 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2076 else
2077 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2078 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2079 }
2081 static bool
2082 draw_view_line(struct view *view, unsigned int lineno)
2083 {
2084 struct line *line;
2085 bool selected = (view->offset + lineno == view->lineno);
2087 assert(view_is_displayed(view));
2089 if (view->offset + lineno >= view->lines)
2090 return FALSE;
2092 line = &view->line[view->offset + lineno];
2094 wmove(view->win, lineno, 0);
2095 if (line->cleareol)
2096 wclrtoeol(view->win);
2097 view->col = 0;
2098 view->curline = line;
2099 view->curtype = LINE_NONE;
2100 line->selected = FALSE;
2101 line->dirty = line->cleareol = 0;
2103 if (selected) {
2104 set_view_attr(view, LINE_CURSOR);
2105 line->selected = TRUE;
2106 view->ops->select(view, line);
2107 }
2109 return view->ops->draw(view, line, lineno);
2110 }
2112 static void
2113 redraw_view_dirty(struct view *view)
2114 {
2115 bool dirty = FALSE;
2116 int lineno;
2118 for (lineno = 0; lineno < view->height; lineno++) {
2119 if (view->offset + lineno >= view->lines)
2120 break;
2121 if (!view->line[view->offset + lineno].dirty)
2122 continue;
2123 dirty = TRUE;
2124 if (!draw_view_line(view, lineno))
2125 break;
2126 }
2128 if (!dirty)
2129 return;
2130 wnoutrefresh(view->win);
2131 }
2133 static void
2134 redraw_view_from(struct view *view, int lineno)
2135 {
2136 assert(0 <= lineno && lineno < view->height);
2138 for (; lineno < view->height; lineno++) {
2139 if (!draw_view_line(view, lineno))
2140 break;
2141 }
2143 wnoutrefresh(view->win);
2144 }
2146 static void
2147 redraw_view(struct view *view)
2148 {
2149 werase(view->win);
2150 redraw_view_from(view, 0);
2151 }
2154 static void
2155 update_view_title(struct view *view)
2156 {
2157 char buf[SIZEOF_STR];
2158 char state[SIZEOF_STR];
2159 size_t bufpos = 0, statelen = 0;
2161 assert(view_is_displayed(view));
2163 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2164 unsigned int view_lines = view->offset + view->height;
2165 unsigned int lines = view->lines
2166 ? MIN(view_lines, view->lines) * 100 / view->lines
2167 : 0;
2169 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2170 view->ops->type,
2171 view->lineno + 1,
2172 view->lines,
2173 lines);
2175 }
2177 if (view->pipe) {
2178 time_t secs = time(NULL) - view->start_time;
2180 /* Three git seconds are a long time ... */
2181 if (secs > 2)
2182 string_format_from(state, &statelen, " loading %lds", secs);
2183 }
2185 string_format_from(buf, &bufpos, "[%s]", view->name);
2186 if (*view->ref && bufpos < view->width) {
2187 size_t refsize = strlen(view->ref);
2188 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2190 if (minsize < view->width)
2191 refsize = view->width - minsize + 7;
2192 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2193 }
2195 if (statelen && bufpos < view->width) {
2196 string_format_from(buf, &bufpos, "%s", state);
2197 }
2199 if (view == display[current_view])
2200 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2201 else
2202 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2204 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2205 wclrtoeol(view->title);
2206 wnoutrefresh(view->title);
2207 }
2209 static void
2210 resize_display(void)
2211 {
2212 int offset, i;
2213 struct view *base = display[0];
2214 struct view *view = display[1] ? display[1] : display[0];
2216 /* Setup window dimensions */
2218 getmaxyx(stdscr, base->height, base->width);
2220 /* Make room for the status window. */
2221 base->height -= 1;
2223 if (view != base) {
2224 /* Horizontal split. */
2225 view->width = base->width;
2226 view->height = SCALE_SPLIT_VIEW(base->height);
2227 base->height -= view->height;
2229 /* Make room for the title bar. */
2230 view->height -= 1;
2231 }
2233 /* Make room for the title bar. */
2234 base->height -= 1;
2236 offset = 0;
2238 foreach_displayed_view (view, i) {
2239 if (!view->win) {
2240 view->win = newwin(view->height, 0, offset, 0);
2241 if (!view->win)
2242 die("Failed to create %s view", view->name);
2244 scrollok(view->win, FALSE);
2246 view->title = newwin(1, 0, offset + view->height, 0);
2247 if (!view->title)
2248 die("Failed to create title window");
2250 } else {
2251 wresize(view->win, view->height, view->width);
2252 mvwin(view->win, offset, 0);
2253 mvwin(view->title, offset + view->height, 0);
2254 }
2256 offset += view->height + 1;
2257 }
2258 }
2260 static void
2261 redraw_display(bool clear)
2262 {
2263 struct view *view;
2264 int i;
2266 foreach_displayed_view (view, i) {
2267 if (clear)
2268 wclear(view->win);
2269 redraw_view(view);
2270 update_view_title(view);
2271 }
2272 }
2274 static void
2275 toggle_view_option(bool *option, const char *help)
2276 {
2277 *option = !*option;
2278 redraw_display(FALSE);
2279 report("%sabling %s", *option ? "En" : "Dis", help);
2280 }
2282 static void
2283 maximize_view(struct view *view)
2284 {
2285 memset(display, 0, sizeof(display));
2286 current_view = 0;
2287 display[current_view] = view;
2288 resize_display();
2289 redraw_display(FALSE);
2290 report("");
2291 }
2294 /*
2295 * Navigation
2296 */
2298 static bool
2299 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2300 {
2301 if (lineno >= view->lines)
2302 lineno = view->lines > 0 ? view->lines - 1 : 0;
2304 if (offset > lineno || offset + view->height <= lineno) {
2305 unsigned long half = view->height / 2;
2307 if (lineno > half)
2308 offset = lineno - half;
2309 else
2310 offset = 0;
2311 }
2313 if (offset != view->offset || lineno != view->lineno) {
2314 view->offset = offset;
2315 view->lineno = lineno;
2316 return TRUE;
2317 }
2319 return FALSE;
2320 }
2322 static int
2323 apply_step(double step, int value)
2324 {
2325 if (step >= 1)
2326 return (int) step;
2327 value *= step + 0.01;
2328 return value ? value : 1;
2329 }
2331 /* Scrolling backend */
2332 static void
2333 do_scroll_view(struct view *view, int lines)
2334 {
2335 bool redraw_current_line = FALSE;
2337 /* The rendering expects the new offset. */
2338 view->offset += lines;
2340 assert(0 <= view->offset && view->offset < view->lines);
2341 assert(lines);
2343 /* Move current line into the view. */
2344 if (view->lineno < view->offset) {
2345 view->lineno = view->offset;
2346 redraw_current_line = TRUE;
2347 } else if (view->lineno >= view->offset + view->height) {
2348 view->lineno = view->offset + view->height - 1;
2349 redraw_current_line = TRUE;
2350 }
2352 assert(view->offset <= view->lineno && view->lineno < view->lines);
2354 /* Redraw the whole screen if scrolling is pointless. */
2355 if (view->height < ABS(lines)) {
2356 redraw_view(view);
2358 } else {
2359 int line = lines > 0 ? view->height - lines : 0;
2360 int end = line + ABS(lines);
2362 scrollok(view->win, TRUE);
2363 wscrl(view->win, lines);
2364 scrollok(view->win, FALSE);
2366 while (line < end && draw_view_line(view, line))
2367 line++;
2369 if (redraw_current_line)
2370 draw_view_line(view, view->lineno - view->offset);
2371 wnoutrefresh(view->win);
2372 }
2374 view->has_scrolled = TRUE;
2375 report("");
2376 }
2378 /* Scroll frontend */
2379 static void
2380 scroll_view(struct view *view, enum request request)
2381 {
2382 int lines = 1;
2384 assert(view_is_displayed(view));
2386 switch (request) {
2387 case REQ_SCROLL_LEFT:
2388 if (view->yoffset == 0) {
2389 report("Cannot scroll beyond the first column");
2390 return;
2391 }
2392 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2393 view->yoffset = 0;
2394 else
2395 view->yoffset -= apply_step(opt_hscroll, view->width);
2396 redraw_view_from(view, 0);
2397 report("");
2398 return;
2399 case REQ_SCROLL_RIGHT:
2400 view->yoffset += apply_step(opt_hscroll, view->width);
2401 redraw_view(view);
2402 report("");
2403 return;
2404 case REQ_SCROLL_PAGE_DOWN:
2405 lines = view->height;
2406 case REQ_SCROLL_LINE_DOWN:
2407 if (view->offset + lines > view->lines)
2408 lines = view->lines - view->offset;
2410 if (lines == 0 || view->offset + view->height >= view->lines) {
2411 report("Cannot scroll beyond the last line");
2412 return;
2413 }
2414 break;
2416 case REQ_SCROLL_PAGE_UP:
2417 lines = view->height;
2418 case REQ_SCROLL_LINE_UP:
2419 if (lines > view->offset)
2420 lines = view->offset;
2422 if (lines == 0) {
2423 report("Cannot scroll beyond the first line");
2424 return;
2425 }
2427 lines = -lines;
2428 break;
2430 default:
2431 die("request %d not handled in switch", request);
2432 }
2434 do_scroll_view(view, lines);
2435 }
2437 /* Cursor moving */
2438 static void
2439 move_view(struct view *view, enum request request)
2440 {
2441 int scroll_steps = 0;
2442 int steps;
2444 switch (request) {
2445 case REQ_MOVE_FIRST_LINE:
2446 steps = -view->lineno;
2447 break;
2449 case REQ_MOVE_LAST_LINE:
2450 steps = view->lines - view->lineno - 1;
2451 break;
2453 case REQ_MOVE_PAGE_UP:
2454 steps = view->height > view->lineno
2455 ? -view->lineno : -view->height;
2456 break;
2458 case REQ_MOVE_PAGE_DOWN:
2459 steps = view->lineno + view->height >= view->lines
2460 ? view->lines - view->lineno - 1 : view->height;
2461 break;
2463 case REQ_MOVE_UP:
2464 steps = -1;
2465 break;
2467 case REQ_MOVE_DOWN:
2468 steps = 1;
2469 break;
2471 default:
2472 die("request %d not handled in switch", request);
2473 }
2475 if (steps <= 0 && view->lineno == 0) {
2476 report("Cannot move beyond the first line");
2477 return;
2479 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2480 report("Cannot move beyond the last line");
2481 return;
2482 }
2484 /* Move the current line */
2485 view->lineno += steps;
2486 assert(0 <= view->lineno && view->lineno < view->lines);
2488 /* Check whether the view needs to be scrolled */
2489 if (view->lineno < view->offset ||
2490 view->lineno >= view->offset + view->height) {
2491 scroll_steps = steps;
2492 if (steps < 0 && -steps > view->offset) {
2493 scroll_steps = -view->offset;
2495 } else if (steps > 0) {
2496 if (view->lineno == view->lines - 1 &&
2497 view->lines > view->height) {
2498 scroll_steps = view->lines - view->offset - 1;
2499 if (scroll_steps >= view->height)
2500 scroll_steps -= view->height - 1;
2501 }
2502 }
2503 }
2505 if (!view_is_displayed(view)) {
2506 view->offset += scroll_steps;
2507 assert(0 <= view->offset && view->offset < view->lines);
2508 view->ops->select(view, &view->line[view->lineno]);
2509 return;
2510 }
2512 /* Repaint the old "current" line if we be scrolling */
2513 if (ABS(steps) < view->height)
2514 draw_view_line(view, view->lineno - steps - view->offset);
2516 if (scroll_steps) {
2517 do_scroll_view(view, scroll_steps);
2518 return;
2519 }
2521 /* Draw the current line */
2522 draw_view_line(view, view->lineno - view->offset);
2524 wnoutrefresh(view->win);
2525 report("");
2526 }
2529 /*
2530 * Searching
2531 */
2533 static void search_view(struct view *view, enum request request);
2535 static bool
2536 grep_text(struct view *view, const char *text[])
2537 {
2538 regmatch_t pmatch;
2539 size_t i;
2541 for (i = 0; text[i]; i++)
2542 if (*text[i] &&
2543 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2544 return TRUE;
2545 return FALSE;
2546 }
2548 static void
2549 select_view_line(struct view *view, unsigned long lineno)
2550 {
2551 unsigned long old_lineno = view->lineno;
2552 unsigned long old_offset = view->offset;
2554 if (goto_view_line(view, view->offset, lineno)) {
2555 if (view_is_displayed(view)) {
2556 if (old_offset != view->offset) {
2557 redraw_view(view);
2558 } else {
2559 draw_view_line(view, old_lineno - view->offset);
2560 draw_view_line(view, view->lineno - view->offset);
2561 wnoutrefresh(view->win);
2562 }
2563 } else {
2564 view->ops->select(view, &view->line[view->lineno]);
2565 }
2566 }
2567 }
2569 static void
2570 find_next(struct view *view, enum request request)
2571 {
2572 unsigned long lineno = view->lineno;
2573 int direction;
2575 if (!*view->grep) {
2576 if (!*opt_search)
2577 report("No previous search");
2578 else
2579 search_view(view, request);
2580 return;
2581 }
2583 switch (request) {
2584 case REQ_SEARCH:
2585 case REQ_FIND_NEXT:
2586 direction = 1;
2587 break;
2589 case REQ_SEARCH_BACK:
2590 case REQ_FIND_PREV:
2591 direction = -1;
2592 break;
2594 default:
2595 return;
2596 }
2598 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2599 lineno += direction;
2601 /* Note, lineno is unsigned long so will wrap around in which case it
2602 * will become bigger than view->lines. */
2603 for (; lineno < view->lines; lineno += direction) {
2604 if (view->ops->grep(view, &view->line[lineno])) {
2605 select_view_line(view, lineno);
2606 report("Line %ld matches '%s'", lineno + 1, view->grep);
2607 return;
2608 }
2609 }
2611 report("No match found for '%s'", view->grep);
2612 }
2614 static void
2615 search_view(struct view *view, enum request request)
2616 {
2617 int regex_err;
2619 if (view->regex) {
2620 regfree(view->regex);
2621 *view->grep = 0;
2622 } else {
2623 view->regex = calloc(1, sizeof(*view->regex));
2624 if (!view->regex)
2625 return;
2626 }
2628 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2629 if (regex_err != 0) {
2630 char buf[SIZEOF_STR] = "unknown error";
2632 regerror(regex_err, view->regex, buf, sizeof(buf));
2633 report("Search failed: %s", buf);
2634 return;
2635 }
2637 string_copy(view->grep, opt_search);
2639 find_next(view, request);
2640 }
2642 /*
2643 * Incremental updating
2644 */
2646 static void
2647 reset_view(struct view *view)
2648 {
2649 int i;
2651 for (i = 0; i < view->lines; i++)
2652 free(view->line[i].data);
2653 free(view->line);
2655 view->p_offset = view->offset;
2656 view->p_yoffset = view->yoffset;
2657 view->p_lineno = view->lineno;
2659 view->line = NULL;
2660 view->offset = 0;
2661 view->yoffset = 0;
2662 view->lines = 0;
2663 view->lineno = 0;
2664 view->vid[0] = 0;
2665 view->update_secs = 0;
2666 }
2668 static void
2669 free_argv(const char *argv[])
2670 {
2671 int argc;
2673 for (argc = 0; argv[argc]; argc++)
2674 free((void *) argv[argc]);
2675 }
2677 static bool
2678 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2679 {
2680 char buf[SIZEOF_STR];
2681 int argc;
2682 bool noreplace = flags == FORMAT_NONE;
2684 free_argv(dst_argv);
2686 for (argc = 0; src_argv[argc]; argc++) {
2687 const char *arg = src_argv[argc];
2688 size_t bufpos = 0;
2690 while (arg) {
2691 char *next = strstr(arg, "%(");
2692 int len = next - arg;
2693 const char *value;
2695 if (!next || noreplace) {
2696 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2697 noreplace = TRUE;
2698 len = strlen(arg);
2699 value = "";
2701 } else if (!prefixcmp(next, "%(directory)")) {
2702 value = opt_path;
2704 } else if (!prefixcmp(next, "%(file)")) {
2705 value = opt_file;
2707 } else if (!prefixcmp(next, "%(ref)")) {
2708 value = *opt_ref ? opt_ref : "HEAD";
2710 } else if (!prefixcmp(next, "%(head)")) {
2711 value = ref_head;
2713 } else if (!prefixcmp(next, "%(commit)")) {
2714 value = ref_commit;
2716 } else if (!prefixcmp(next, "%(blob)")) {
2717 value = ref_blob;
2719 } else {
2720 report("Unknown replacement: `%s`", next);
2721 return FALSE;
2722 }
2724 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2725 return FALSE;
2727 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2728 }
2730 dst_argv[argc] = strdup(buf);
2731 if (!dst_argv[argc])
2732 break;
2733 }
2735 dst_argv[argc] = NULL;
2737 return src_argv[argc] == NULL;
2738 }
2740 static bool
2741 restore_view_position(struct view *view)
2742 {
2743 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2744 return FALSE;
2746 /* Changing the view position cancels the restoring. */
2747 /* FIXME: Changing back to the first line is not detected. */
2748 if (view->offset != 0 || view->lineno != 0) {
2749 view->p_restore = FALSE;
2750 return FALSE;
2751 }
2753 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2754 view_is_displayed(view))
2755 werase(view->win);
2757 view->yoffset = view->p_yoffset;
2758 view->p_restore = FALSE;
2760 return TRUE;
2761 }
2763 static void
2764 end_update(struct view *view, bool force)
2765 {
2766 if (!view->pipe)
2767 return;
2768 while (!view->ops->read(view, NULL))
2769 if (!force)
2770 return;
2771 set_nonblocking_input(FALSE);
2772 if (force)
2773 kill_io(view->pipe);
2774 done_io(view->pipe);
2775 view->pipe = NULL;
2776 }
2778 static void
2779 setup_update(struct view *view, const char *vid)
2780 {
2781 set_nonblocking_input(TRUE);
2782 reset_view(view);
2783 string_copy_rev(view->vid, vid);
2784 view->pipe = &view->io;
2785 view->start_time = time(NULL);
2786 }
2788 static bool
2789 prepare_update(struct view *view, const char *argv[], const char *dir,
2790 enum format_flags flags)
2791 {
2792 if (view->pipe)
2793 end_update(view, TRUE);
2794 return init_io_rd(&view->io, argv, dir, flags);
2795 }
2797 static bool
2798 prepare_update_file(struct view *view, const char *name)
2799 {
2800 if (view->pipe)
2801 end_update(view, TRUE);
2802 return io_open(&view->io, name);
2803 }
2805 static bool
2806 begin_update(struct view *view, bool refresh)
2807 {
2808 if (view->pipe)
2809 end_update(view, TRUE);
2811 if (refresh) {
2812 if (!start_io(&view->io))
2813 return FALSE;
2815 } else {
2816 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2817 opt_path[0] = 0;
2819 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2820 return FALSE;
2822 /* Put the current ref_* value to the view title ref
2823 * member. This is needed by the blob view. Most other
2824 * views sets it automatically after loading because the
2825 * first line is a commit line. */
2826 string_copy_rev(view->ref, view->id);
2827 }
2829 setup_update(view, view->id);
2831 return TRUE;
2832 }
2834 static bool
2835 update_view(struct view *view)
2836 {
2837 char out_buffer[BUFSIZ * 2];
2838 char *line;
2839 /* Clear the view and redraw everything since the tree sorting
2840 * might have rearranged things. */
2841 bool redraw = view->lines == 0;
2842 bool can_read = TRUE;
2844 if (!view->pipe)
2845 return TRUE;
2847 if (!io_can_read(view->pipe)) {
2848 if (view->lines == 0 && view_is_displayed(view)) {
2849 time_t secs = time(NULL) - view->start_time;
2851 if (secs > 1 && secs > view->update_secs) {
2852 if (view->update_secs == 0)
2853 redraw_view(view);
2854 update_view_title(view);
2855 view->update_secs = secs;
2856 }
2857 }
2858 return TRUE;
2859 }
2861 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2862 if (opt_iconv != ICONV_NONE) {
2863 ICONV_CONST char *inbuf = line;
2864 size_t inlen = strlen(line) + 1;
2866 char *outbuf = out_buffer;
2867 size_t outlen = sizeof(out_buffer);
2869 size_t ret;
2871 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2872 if (ret != (size_t) -1)
2873 line = out_buffer;
2874 }
2876 if (!view->ops->read(view, line)) {
2877 report("Allocation failure");
2878 end_update(view, TRUE);
2879 return FALSE;
2880 }
2881 }
2883 {
2884 unsigned long lines = view->lines;
2885 int digits;
2887 for (digits = 0; lines; digits++)
2888 lines /= 10;
2890 /* Keep the displayed view in sync with line number scaling. */
2891 if (digits != view->digits) {
2892 view->digits = digits;
2893 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2894 redraw = TRUE;
2895 }
2896 }
2898 if (io_error(view->pipe)) {
2899 report("Failed to read: %s", io_strerror(view->pipe));
2900 end_update(view, TRUE);
2902 } else if (io_eof(view->pipe)) {
2903 report("");
2904 end_update(view, FALSE);
2905 }
2907 if (restore_view_position(view))
2908 redraw = TRUE;
2910 if (!view_is_displayed(view))
2911 return TRUE;
2913 if (redraw)
2914 redraw_view_from(view, 0);
2915 else
2916 redraw_view_dirty(view);
2918 /* Update the title _after_ the redraw so that if the redraw picks up a
2919 * commit reference in view->ref it'll be available here. */
2920 update_view_title(view);
2921 return TRUE;
2922 }
2924 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2926 static struct line *
2927 add_line_data(struct view *view, void *data, enum line_type type)
2928 {
2929 struct line *line;
2931 if (!realloc_lines(&view->line, view->lines, 1))
2932 return NULL;
2934 line = &view->line[view->lines++];
2935 memset(line, 0, sizeof(*line));
2936 line->type = type;
2937 line->data = data;
2938 line->dirty = 1;
2940 return line;
2941 }
2943 static struct line *
2944 add_line_text(struct view *view, const char *text, enum line_type type)
2945 {
2946 char *data = text ? strdup(text) : NULL;
2948 return data ? add_line_data(view, data, type) : NULL;
2949 }
2951 static struct line *
2952 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2953 {
2954 char buf[SIZEOF_STR];
2955 va_list args;
2957 va_start(args, fmt);
2958 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2959 buf[0] = 0;
2960 va_end(args);
2962 return buf[0] ? add_line_text(view, buf, type) : NULL;
2963 }
2965 /*
2966 * View opening
2967 */
2969 enum open_flags {
2970 OPEN_DEFAULT = 0, /* Use default view switching. */
2971 OPEN_SPLIT = 1, /* Split current view. */
2972 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2973 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2974 OPEN_PREPARED = 32, /* Open already prepared command. */
2975 };
2977 static void
2978 open_view(struct view *prev, enum request request, enum open_flags flags)
2979 {
2980 bool split = !!(flags & OPEN_SPLIT);
2981 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2982 bool nomaximize = !!(flags & OPEN_REFRESH);
2983 struct view *view = VIEW(request);
2984 int nviews = displayed_views();
2985 struct view *base_view = display[0];
2987 if (view == prev && nviews == 1 && !reload) {
2988 report("Already in %s view", view->name);
2989 return;
2990 }
2992 if (view->git_dir && !opt_git_dir[0]) {
2993 report("The %s view is disabled in pager view", view->name);
2994 return;
2995 }
2997 if (split) {
2998 display[1] = view;
2999 current_view = 1;
3000 } else if (!nomaximize) {
3001 /* Maximize the current view. */
3002 memset(display, 0, sizeof(display));
3003 current_view = 0;
3004 display[current_view] = view;
3005 }
3007 /* Resize the view when switching between split- and full-screen,
3008 * or when switching between two different full-screen views. */
3009 if (nviews != displayed_views() ||
3010 (nviews == 1 && base_view != display[0]))
3011 resize_display();
3013 if (view->ops->open) {
3014 if (view->pipe)
3015 end_update(view, TRUE);
3016 if (!view->ops->open(view)) {
3017 report("Failed to load %s view", view->name);
3018 return;
3019 }
3020 restore_view_position(view);
3022 } else if ((reload || strcmp(view->vid, view->id)) &&
3023 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3024 report("Failed to load %s view", view->name);
3025 return;
3026 }
3028 if (split && prev->lineno - prev->offset >= prev->height) {
3029 /* Take the title line into account. */
3030 int lines = prev->lineno - prev->offset - prev->height + 1;
3032 /* Scroll the view that was split if the current line is
3033 * outside the new limited view. */
3034 do_scroll_view(prev, lines);
3035 }
3037 if (prev && view != prev) {
3038 if (split) {
3039 /* "Blur" the previous view. */
3040 update_view_title(prev);
3041 }
3043 view->parent = prev;
3044 }
3046 if (view->pipe && view->lines == 0) {
3047 /* Clear the old view and let the incremental updating refill
3048 * the screen. */
3049 werase(view->win);
3050 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3051 report("");
3052 } else if (view_is_displayed(view)) {
3053 redraw_view(view);
3054 report("");
3055 }
3056 }
3058 static void
3059 open_external_viewer(const char *argv[], const char *dir)
3060 {
3061 def_prog_mode(); /* save current tty modes */
3062 endwin(); /* restore original tty modes */
3063 run_io_fg(argv, dir);
3064 fprintf(stderr, "Press Enter to continue");
3065 getc(opt_tty);
3066 reset_prog_mode();
3067 redraw_display(TRUE);
3068 }
3070 static void
3071 open_mergetool(const char *file)
3072 {
3073 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3075 open_external_viewer(mergetool_argv, opt_cdup);
3076 }
3078 static void
3079 open_editor(bool from_root, const char *file)
3080 {
3081 const char *editor_argv[] = { "vi", file, NULL };
3082 const char *editor;
3084 editor = getenv("GIT_EDITOR");
3085 if (!editor && *opt_editor)
3086 editor = opt_editor;
3087 if (!editor)
3088 editor = getenv("VISUAL");
3089 if (!editor)
3090 editor = getenv("EDITOR");
3091 if (!editor)
3092 editor = "vi";
3094 editor_argv[0] = editor;
3095 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3096 }
3098 static void
3099 open_run_request(enum request request)
3100 {
3101 struct run_request *req = get_run_request(request);
3102 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3104 if (!req) {
3105 report("Unknown run request");
3106 return;
3107 }
3109 if (format_argv(argv, req->argv, FORMAT_ALL))
3110 open_external_viewer(argv, NULL);
3111 free_argv(argv);
3112 }
3114 /*
3115 * User request switch noodle
3116 */
3118 static int
3119 view_driver(struct view *view, enum request request)
3120 {
3121 int i;
3123 if (request == REQ_NONE)
3124 return TRUE;
3126 if (request > REQ_NONE) {
3127 open_run_request(request);
3128 /* FIXME: When all views can refresh always do this. */
3129 if (view == VIEW(REQ_VIEW_STATUS) ||
3130 view == VIEW(REQ_VIEW_MAIN) ||
3131 view == VIEW(REQ_VIEW_LOG) ||
3132 view == VIEW(REQ_VIEW_BRANCH) ||
3133 view == VIEW(REQ_VIEW_STAGE))
3134 request = REQ_REFRESH;
3135 else
3136 return TRUE;
3137 }
3139 if (view && view->lines) {
3140 request = view->ops->request(view, request, &view->line[view->lineno]);
3141 if (request == REQ_NONE)
3142 return TRUE;
3143 }
3145 switch (request) {
3146 case REQ_MOVE_UP:
3147 case REQ_MOVE_DOWN:
3148 case REQ_MOVE_PAGE_UP:
3149 case REQ_MOVE_PAGE_DOWN:
3150 case REQ_MOVE_FIRST_LINE:
3151 case REQ_MOVE_LAST_LINE:
3152 move_view(view, request);
3153 break;
3155 case REQ_SCROLL_LEFT:
3156 case REQ_SCROLL_RIGHT:
3157 case REQ_SCROLL_LINE_DOWN:
3158 case REQ_SCROLL_LINE_UP:
3159 case REQ_SCROLL_PAGE_DOWN:
3160 case REQ_SCROLL_PAGE_UP:
3161 scroll_view(view, request);
3162 break;
3164 case REQ_VIEW_BLAME:
3165 if (!opt_file[0]) {
3166 report("No file chosen, press %s to open tree view",
3167 get_key(REQ_VIEW_TREE));
3168 break;
3169 }
3170 open_view(view, request, OPEN_DEFAULT);
3171 break;
3173 case REQ_VIEW_BLOB:
3174 if (!ref_blob[0]) {
3175 report("No file chosen, press %s to open tree view",
3176 get_key(REQ_VIEW_TREE));
3177 break;
3178 }
3179 open_view(view, request, OPEN_DEFAULT);
3180 break;
3182 case REQ_VIEW_PAGER:
3183 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3184 report("No pager content, press %s to run command from prompt",
3185 get_key(REQ_PROMPT));
3186 break;
3187 }
3188 open_view(view, request, OPEN_DEFAULT);
3189 break;
3191 case REQ_VIEW_STAGE:
3192 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3193 report("No stage content, press %s to open the status view and choose file",
3194 get_key(REQ_VIEW_STATUS));
3195 break;
3196 }
3197 open_view(view, request, OPEN_DEFAULT);
3198 break;
3200 case REQ_VIEW_STATUS:
3201 if (opt_is_inside_work_tree == FALSE) {
3202 report("The status view requires a working tree");
3203 break;
3204 }
3205 open_view(view, request, OPEN_DEFAULT);
3206 break;
3208 case REQ_VIEW_MAIN:
3209 case REQ_VIEW_DIFF:
3210 case REQ_VIEW_LOG:
3211 case REQ_VIEW_TREE:
3212 case REQ_VIEW_HELP:
3213 case REQ_VIEW_BRANCH:
3214 open_view(view, request, OPEN_DEFAULT);
3215 break;
3217 case REQ_NEXT:
3218 case REQ_PREVIOUS:
3219 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3221 if ((view == VIEW(REQ_VIEW_DIFF) &&
3222 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3223 (view == VIEW(REQ_VIEW_DIFF) &&
3224 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3225 (view == VIEW(REQ_VIEW_STAGE) &&
3226 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3227 (view == VIEW(REQ_VIEW_BLOB) &&
3228 view->parent == VIEW(REQ_VIEW_TREE)) ||
3229 (view == VIEW(REQ_VIEW_MAIN) &&
3230 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3231 int line;
3233 view = view->parent;
3234 line = view->lineno;
3235 move_view(view, request);
3236 if (view_is_displayed(view))
3237 update_view_title(view);
3238 if (line != view->lineno)
3239 view->ops->request(view, REQ_ENTER,
3240 &view->line[view->lineno]);
3242 } else {
3243 move_view(view, request);
3244 }
3245 break;
3247 case REQ_VIEW_NEXT:
3248 {
3249 int nviews = displayed_views();
3250 int next_view = (current_view + 1) % nviews;
3252 if (next_view == current_view) {
3253 report("Only one view is displayed");
3254 break;
3255 }
3257 current_view = next_view;
3258 /* Blur out the title of the previous view. */
3259 update_view_title(view);
3260 report("");
3261 break;
3262 }
3263 case REQ_REFRESH:
3264 report("Refreshing is not yet supported for the %s view", view->name);
3265 break;
3267 case REQ_MAXIMIZE:
3268 if (displayed_views() == 2)
3269 maximize_view(view);
3270 break;
3272 case REQ_TOGGLE_LINENO:
3273 toggle_view_option(&opt_line_number, "line numbers");
3274 break;
3276 case REQ_TOGGLE_DATE:
3277 toggle_view_option(&opt_date, "date display");
3278 break;
3280 case REQ_TOGGLE_AUTHOR:
3281 toggle_view_option(&opt_author, "author display");
3282 break;
3284 case REQ_TOGGLE_REV_GRAPH:
3285 toggle_view_option(&opt_rev_graph, "revision graph display");
3286 break;
3288 case REQ_TOGGLE_REFS:
3289 toggle_view_option(&opt_show_refs, "reference display");
3290 break;
3292 case REQ_TOGGLE_SORT_FIELD:
3293 case REQ_TOGGLE_SORT_ORDER:
3294 report("Sorting is not yet supported for the %s view", view->name);
3295 break;
3297 case REQ_SEARCH:
3298 case REQ_SEARCH_BACK:
3299 search_view(view, request);
3300 break;
3302 case REQ_FIND_NEXT:
3303 case REQ_FIND_PREV:
3304 find_next(view, request);
3305 break;
3307 case REQ_STOP_LOADING:
3308 for (i = 0; i < ARRAY_SIZE(views); i++) {
3309 view = &views[i];
3310 if (view->pipe)
3311 report("Stopped loading the %s view", view->name),
3312 end_update(view, TRUE);
3313 }
3314 break;
3316 case REQ_SHOW_VERSION:
3317 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3318 return TRUE;
3320 case REQ_SCREEN_REDRAW:
3321 redraw_display(TRUE);
3322 break;
3324 case REQ_EDIT:
3325 report("Nothing to edit");
3326 break;
3328 case REQ_ENTER:
3329 report("Nothing to enter");
3330 break;
3332 case REQ_VIEW_CLOSE:
3333 /* XXX: Mark closed views by letting view->parent point to the
3334 * view itself. Parents to closed view should never be
3335 * followed. */
3336 if (view->parent &&
3337 view->parent->parent != view->parent) {
3338 maximize_view(view->parent);
3339 view->parent = view;
3340 break;
3341 }
3342 /* Fall-through */
3343 case REQ_QUIT:
3344 return FALSE;
3346 default:
3347 report("Unknown key, press 'h' for help");
3348 return TRUE;
3349 }
3351 return TRUE;
3352 }
3355 /*
3356 * View backend utilities
3357 */
3359 enum sort_field {
3360 ORDERBY_NAME,
3361 ORDERBY_DATE,
3362 ORDERBY_AUTHOR,
3363 };
3365 struct sort_state {
3366 const enum sort_field *fields;
3367 size_t size, current;
3368 bool reverse;
3369 };
3371 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3372 #define get_sort_field(state) ((state).fields[(state).current])
3373 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3375 static void
3376 sort_view(struct view *view, enum request request, struct sort_state *state,
3377 int (*compare)(const void *, const void *))
3378 {
3379 switch (request) {
3380 case REQ_TOGGLE_SORT_FIELD:
3381 state->current = (state->current + 1) % state->size;
3382 break;
3384 case REQ_TOGGLE_SORT_ORDER:
3385 state->reverse = !state->reverse;
3386 break;
3387 default:
3388 die("Not a sort request");
3389 }
3391 qsort(view->line, view->lines, sizeof(*view->line), compare);
3392 redraw_view(view);
3393 }
3395 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3397 /* Small author cache to reduce memory consumption. It uses binary
3398 * search to lookup or find place to position new entries. No entries
3399 * are ever freed. */
3400 static const char *
3401 get_author(const char *name)
3402 {
3403 static const char **authors;
3404 static size_t authors_size;
3405 int from = 0, to = authors_size - 1;
3407 while (from <= to) {
3408 size_t pos = (to + from) / 2;
3409 int cmp = strcmp(name, authors[pos]);
3411 if (!cmp)
3412 return authors[pos];
3414 if (cmp < 0)
3415 to = pos - 1;
3416 else
3417 from = pos + 1;
3418 }
3420 if (!realloc_authors(&authors, authors_size, 1))
3421 return NULL;
3422 name = strdup(name);
3423 if (!name)
3424 return NULL;
3426 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3427 authors[from] = name;
3428 authors_size++;
3430 return name;
3431 }
3433 static void
3434 parse_timezone(time_t *time, const char *zone)
3435 {
3436 long tz;
3438 tz = ('0' - zone[1]) * 60 * 60 * 10;
3439 tz += ('0' - zone[2]) * 60 * 60;
3440 tz += ('0' - zone[3]) * 60;
3441 tz += ('0' - zone[4]);
3443 if (zone[0] == '-')
3444 tz = -tz;
3446 *time -= tz;
3447 }
3449 /* Parse author lines where the name may be empty:
3450 * author <email@address.tld> 1138474660 +0100
3451 */
3452 static void
3453 parse_author_line(char *ident, const char **author, time_t *time)
3454 {
3455 char *nameend = strchr(ident, '<');
3456 char *emailend = strchr(ident, '>');
3458 if (nameend && emailend)
3459 *nameend = *emailend = 0;
3460 ident = chomp_string(ident);
3461 if (!*ident) {
3462 if (nameend)
3463 ident = chomp_string(nameend + 1);
3464 if (!*ident)
3465 ident = "Unknown";
3466 }
3468 *author = get_author(ident);
3470 /* Parse epoch and timezone */
3471 if (emailend && emailend[1] == ' ') {
3472 char *secs = emailend + 2;
3473 char *zone = strchr(secs, ' ');
3475 *time = (time_t) atol(secs);
3477 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3478 parse_timezone(time, zone + 1);
3479 }
3480 }
3482 static enum input_status
3483 select_commit_parent_handler(void *data, char *buf, int c)
3484 {
3485 size_t parents = *(size_t *) data;
3486 int parent = 0;
3488 if (!isdigit(c))
3489 return INPUT_SKIP;
3491 if (*buf)
3492 parent = atoi(buf) * 10;
3493 parent += c - '0';
3495 if (parent > parents)
3496 return INPUT_SKIP;
3497 return INPUT_OK;
3498 }
3500 static bool
3501 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3502 {
3503 char buf[SIZEOF_STR * 4];
3504 const char *revlist_argv[] = {
3505 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3506 };
3507 int parents;
3509 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3510 (parents = (strlen(buf) / 40) - 1) < 0) {
3511 report("Failed to get parent information");
3512 return FALSE;
3514 } else if (parents == 0) {
3515 if (path)
3516 report("Path '%s' does not exist in the parent", path);
3517 else
3518 report("The selected commit has no parents");
3519 return FALSE;
3520 }
3522 if (parents > 1) {
3523 char prompt[SIZEOF_STR];
3524 char *result;
3526 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3527 return FALSE;
3528 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3529 if (!result)
3530 return FALSE;
3531 parents = atoi(result);
3532 }
3534 string_copy_rev(rev, &buf[41 * parents]);
3535 return TRUE;
3536 }
3538 /*
3539 * Pager backend
3540 */
3542 static bool
3543 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3544 {
3545 char text[SIZEOF_STR];
3547 if (opt_line_number && draw_lineno(view, lineno))
3548 return TRUE;
3550 string_expand(text, sizeof(text), line->data, opt_tab_size);
3551 draw_text(view, line->type, text, TRUE);
3552 return TRUE;
3553 }
3555 static bool
3556 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3557 {
3558 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3559 char ref[SIZEOF_STR];
3561 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3562 return TRUE;
3564 /* This is the only fatal call, since it can "corrupt" the buffer. */
3565 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3566 return FALSE;
3568 return TRUE;
3569 }
3571 static void
3572 add_pager_refs(struct view *view, struct line *line)
3573 {
3574 char buf[SIZEOF_STR];
3575 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3576 struct ref **refs;
3577 size_t bufpos = 0, refpos = 0;
3578 const char *sep = "Refs: ";
3579 bool is_tag = FALSE;
3581 assert(line->type == LINE_COMMIT);
3583 refs = get_refs(commit_id);
3584 if (!refs) {
3585 if (view == VIEW(REQ_VIEW_DIFF))
3586 goto try_add_describe_ref;
3587 return;
3588 }
3590 do {
3591 struct ref *ref = refs[refpos];
3592 const char *fmt = ref->tag ? "%s[%s]" :
3593 ref->remote ? "%s<%s>" : "%s%s";
3595 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3596 return;
3597 sep = ", ";
3598 if (ref->tag)
3599 is_tag = TRUE;
3600 } while (refs[refpos++]->next);
3602 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3603 try_add_describe_ref:
3604 /* Add <tag>-g<commit_id> "fake" reference. */
3605 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3606 return;
3607 }
3609 if (bufpos == 0)
3610 return;
3612 add_line_text(view, buf, LINE_PP_REFS);
3613 }
3615 static bool
3616 pager_read(struct view *view, char *data)
3617 {
3618 struct line *line;
3620 if (!data)
3621 return TRUE;
3623 line = add_line_text(view, data, get_line_type(data));
3624 if (!line)
3625 return FALSE;
3627 if (line->type == LINE_COMMIT &&
3628 (view == VIEW(REQ_VIEW_DIFF) ||
3629 view == VIEW(REQ_VIEW_LOG)))
3630 add_pager_refs(view, line);
3632 return TRUE;
3633 }
3635 static enum request
3636 pager_request(struct view *view, enum request request, struct line *line)
3637 {
3638 int split = 0;
3640 if (request != REQ_ENTER)
3641 return request;
3643 if (line->type == LINE_COMMIT &&
3644 (view == VIEW(REQ_VIEW_LOG) ||
3645 view == VIEW(REQ_VIEW_PAGER))) {
3646 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3647 split = 1;
3648 }
3650 /* Always scroll the view even if it was split. That way
3651 * you can use Enter to scroll through the log view and
3652 * split open each commit diff. */
3653 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3655 /* FIXME: A minor workaround. Scrolling the view will call report("")
3656 * but if we are scrolling a non-current view this won't properly
3657 * update the view title. */
3658 if (split)
3659 update_view_title(view);
3661 return REQ_NONE;
3662 }
3664 static bool
3665 pager_grep(struct view *view, struct line *line)
3666 {
3667 const char *text[] = { line->data, NULL };
3669 return grep_text(view, text);
3670 }
3672 static void
3673 pager_select(struct view *view, struct line *line)
3674 {
3675 if (line->type == LINE_COMMIT) {
3676 char *text = (char *)line->data + STRING_SIZE("commit ");
3678 if (view != VIEW(REQ_VIEW_PAGER))
3679 string_copy_rev(view->ref, text);
3680 string_copy_rev(ref_commit, text);
3681 }
3682 }
3684 static struct view_ops pager_ops = {
3685 "line",
3686 NULL,
3687 NULL,
3688 pager_read,
3689 pager_draw,
3690 pager_request,
3691 pager_grep,
3692 pager_select,
3693 };
3695 static const char *log_argv[SIZEOF_ARG] = {
3696 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3697 };
3699 static enum request
3700 log_request(struct view *view, enum request request, struct line *line)
3701 {
3702 switch (request) {
3703 case REQ_REFRESH:
3704 load_refs();
3705 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3706 return REQ_NONE;
3707 default:
3708 return pager_request(view, request, line);
3709 }
3710 }
3712 static struct view_ops log_ops = {
3713 "line",
3714 log_argv,
3715 NULL,
3716 pager_read,
3717 pager_draw,
3718 log_request,
3719 pager_grep,
3720 pager_select,
3721 };
3723 static const char *diff_argv[SIZEOF_ARG] = {
3724 "git", "show", "--pretty=fuller", "--no-color", "--root",
3725 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3726 };
3728 static struct view_ops diff_ops = {
3729 "line",
3730 diff_argv,
3731 NULL,
3732 pager_read,
3733 pager_draw,
3734 pager_request,
3735 pager_grep,
3736 pager_select,
3737 };
3739 /*
3740 * Help backend
3741 */
3743 static bool
3744 help_open(struct view *view)
3745 {
3746 char buf[SIZEOF_STR];
3747 size_t bufpos;
3748 int i;
3750 if (view->lines > 0)
3751 return TRUE;
3753 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3755 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3756 const char *key;
3758 if (req_info[i].request == REQ_NONE)
3759 continue;
3761 if (!req_info[i].request) {
3762 add_line_text(view, "", LINE_DEFAULT);
3763 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3764 continue;
3765 }
3767 key = get_key(req_info[i].request);
3768 if (!*key)
3769 key = "(no key defined)";
3771 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3772 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3773 if (buf[bufpos] == '_')
3774 buf[bufpos] = '-';
3775 }
3777 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3778 key, buf, req_info[i].help);
3779 }
3781 if (run_requests) {
3782 add_line_text(view, "", LINE_DEFAULT);
3783 add_line_text(view, "External commands:", LINE_DEFAULT);
3784 }
3786 for (i = 0; i < run_requests; i++) {
3787 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3788 const char *key;
3789 int argc;
3791 if (!req)
3792 continue;
3794 key = get_key_name(req->key);
3795 if (!*key)
3796 key = "(no key defined)";
3798 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3799 if (!string_format_from(buf, &bufpos, "%s%s",
3800 argc ? " " : "", req->argv[argc]))
3801 return REQ_NONE;
3803 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3804 keymap_table[req->keymap].name, key, buf);
3805 }
3807 return TRUE;
3808 }
3810 static struct view_ops help_ops = {
3811 "line",
3812 NULL,
3813 help_open,
3814 NULL,
3815 pager_draw,
3816 pager_request,
3817 pager_grep,
3818 pager_select,
3819 };
3822 /*
3823 * Tree backend
3824 */
3826 struct tree_stack_entry {
3827 struct tree_stack_entry *prev; /* Entry below this in the stack */
3828 unsigned long lineno; /* Line number to restore */
3829 char *name; /* Position of name in opt_path */
3830 };
3832 /* The top of the path stack. */
3833 static struct tree_stack_entry *tree_stack = NULL;
3834 unsigned long tree_lineno = 0;
3836 static void
3837 pop_tree_stack_entry(void)
3838 {
3839 struct tree_stack_entry *entry = tree_stack;
3841 tree_lineno = entry->lineno;
3842 entry->name[0] = 0;
3843 tree_stack = entry->prev;
3844 free(entry);
3845 }
3847 static void
3848 push_tree_stack_entry(const char *name, unsigned long lineno)
3849 {
3850 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3851 size_t pathlen = strlen(opt_path);
3853 if (!entry)
3854 return;
3856 entry->prev = tree_stack;
3857 entry->name = opt_path + pathlen;
3858 tree_stack = entry;
3860 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3861 pop_tree_stack_entry();
3862 return;
3863 }
3865 /* Move the current line to the first tree entry. */
3866 tree_lineno = 1;
3867 entry->lineno = lineno;
3868 }
3870 /* Parse output from git-ls-tree(1):
3871 *
3872 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3873 */
3875 #define SIZEOF_TREE_ATTR \
3876 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3878 #define SIZEOF_TREE_MODE \
3879 STRING_SIZE("100644 ")
3881 #define TREE_ID_OFFSET \
3882 STRING_SIZE("100644 blob ")
3884 struct tree_entry {
3885 char id[SIZEOF_REV];
3886 mode_t mode;
3887 time_t time; /* Date from the author ident. */
3888 const char *author; /* Author of the commit. */
3889 char name[1];
3890 };
3892 static const char *
3893 tree_path(const struct line *line)
3894 {
3895 return ((struct tree_entry *) line->data)->name;
3896 }
3898 static int
3899 tree_compare_entry(const struct line *line1, const struct line *line2)
3900 {
3901 if (line1->type != line2->type)
3902 return line1->type == LINE_TREE_DIR ? -1 : 1;
3903 return strcmp(tree_path(line1), tree_path(line2));
3904 }
3906 static const enum sort_field tree_sort_fields[] = {
3907 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
3908 };
3909 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
3911 static int
3912 tree_compare(const void *l1, const void *l2)
3913 {
3914 const struct line *line1 = (const struct line *) l1;
3915 const struct line *line2 = (const struct line *) l2;
3916 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
3917 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
3919 if (line1->type == LINE_TREE_HEAD)
3920 return -1;
3921 if (line2->type == LINE_TREE_HEAD)
3922 return 1;
3924 switch (get_sort_field(tree_sort_state)) {
3925 case ORDERBY_DATE:
3926 return sort_order(tree_sort_state, entry1->time - entry2->time);
3928 case ORDERBY_AUTHOR:
3929 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
3931 case ORDERBY_NAME:
3932 default:
3933 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
3934 }
3935 }
3938 static struct line *
3939 tree_entry(struct view *view, enum line_type type, const char *path,
3940 const char *mode, const char *id)
3941 {
3942 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3943 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3945 if (!entry || !line) {
3946 free(entry);
3947 return NULL;
3948 }
3950 strncpy(entry->name, path, strlen(path));
3951 if (mode)
3952 entry->mode = strtoul(mode, NULL, 8);
3953 if (id)
3954 string_copy_rev(entry->id, id);
3956 return line;
3957 }
3959 static bool
3960 tree_read_date(struct view *view, char *text, bool *read_date)
3961 {
3962 static const char *author_name;
3963 static time_t author_time;
3965 if (!text && *read_date) {
3966 *read_date = FALSE;
3967 return TRUE;
3969 } else if (!text) {
3970 char *path = *opt_path ? opt_path : ".";
3971 /* Find next entry to process */
3972 const char *log_file[] = {
3973 "git", "log", "--no-color", "--pretty=raw",
3974 "--cc", "--raw", view->id, "--", path, NULL
3975 };
3976 struct io io = {};
3978 if (!view->lines) {
3979 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3980 report("Tree is empty");
3981 return TRUE;
3982 }
3984 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3985 report("Failed to load tree data");
3986 return TRUE;
3987 }
3989 done_io(view->pipe);
3990 view->io = io;
3991 *read_date = TRUE;
3992 return FALSE;
3994 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3995 parse_author_line(text + STRING_SIZE("author "),
3996 &author_name, &author_time);
3998 } else if (*text == ':') {
3999 char *pos;
4000 size_t annotated = 1;
4001 size_t i;
4003 pos = strchr(text, '\t');
4004 if (!pos)
4005 return TRUE;
4006 text = pos + 1;
4007 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4008 text += strlen(opt_prefix);
4009 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4010 text += strlen(opt_path);
4011 pos = strchr(text, '/');
4012 if (pos)
4013 *pos = 0;
4015 for (i = 1; i < view->lines; i++) {
4016 struct line *line = &view->line[i];
4017 struct tree_entry *entry = line->data;
4019 annotated += !!entry->author;
4020 if (entry->author || strcmp(entry->name, text))
4021 continue;
4023 entry->author = author_name;
4024 entry->time = author_time;
4025 line->dirty = 1;
4026 break;
4027 }
4029 if (annotated == view->lines)
4030 kill_io(view->pipe);
4031 }
4032 return TRUE;
4033 }
4035 static bool
4036 tree_read(struct view *view, char *text)
4037 {
4038 static bool read_date = FALSE;
4039 struct tree_entry *data;
4040 struct line *entry, *line;
4041 enum line_type type;
4042 size_t textlen = text ? strlen(text) : 0;
4043 char *path = text + SIZEOF_TREE_ATTR;
4045 if (read_date || !text)
4046 return tree_read_date(view, text, &read_date);
4048 if (textlen <= SIZEOF_TREE_ATTR)
4049 return FALSE;
4050 if (view->lines == 0 &&
4051 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4052 return FALSE;
4054 /* Strip the path part ... */
4055 if (*opt_path) {
4056 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4057 size_t striplen = strlen(opt_path);
4059 if (pathlen > striplen)
4060 memmove(path, path + striplen,
4061 pathlen - striplen + 1);
4063 /* Insert "link" to parent directory. */
4064 if (view->lines == 1 &&
4065 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4066 return FALSE;
4067 }
4069 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4070 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4071 if (!entry)
4072 return FALSE;
4073 data = entry->data;
4075 /* Skip "Directory ..." and ".." line. */
4076 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4077 if (tree_compare_entry(line, entry) <= 0)
4078 continue;
4080 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4082 line->data = data;
4083 line->type = type;
4084 for (; line <= entry; line++)
4085 line->dirty = line->cleareol = 1;
4086 return TRUE;
4087 }
4089 if (tree_lineno > view->lineno) {
4090 view->lineno = tree_lineno;
4091 tree_lineno = 0;
4092 }
4094 return TRUE;
4095 }
4097 static bool
4098 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4099 {
4100 struct tree_entry *entry = line->data;
4102 if (line->type == LINE_TREE_HEAD) {
4103 if (draw_text(view, line->type, "Directory path /", TRUE))
4104 return TRUE;
4105 } else {
4106 if (draw_mode(view, entry->mode))
4107 return TRUE;
4109 if (opt_author && draw_author(view, entry->author))
4110 return TRUE;
4112 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4113 return TRUE;
4114 }
4115 if (draw_text(view, line->type, entry->name, TRUE))
4116 return TRUE;
4117 return TRUE;
4118 }
4120 static void
4121 open_blob_editor()
4122 {
4123 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4124 int fd = mkstemp(file);
4126 if (fd == -1)
4127 report("Failed to create temporary file");
4128 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4129 report("Failed to save blob data to file");
4130 else
4131 open_editor(FALSE, file);
4132 if (fd != -1)
4133 unlink(file);
4134 }
4136 static enum request
4137 tree_request(struct view *view, enum request request, struct line *line)
4138 {
4139 enum open_flags flags;
4141 switch (request) {
4142 case REQ_VIEW_BLAME:
4143 if (line->type != LINE_TREE_FILE) {
4144 report("Blame only supported for files");
4145 return REQ_NONE;
4146 }
4148 string_copy(opt_ref, view->vid);
4149 return request;
4151 case REQ_EDIT:
4152 if (line->type != LINE_TREE_FILE) {
4153 report("Edit only supported for files");
4154 } else if (!is_head_commit(view->vid)) {
4155 open_blob_editor();
4156 } else {
4157 open_editor(TRUE, opt_file);
4158 }
4159 return REQ_NONE;
4161 case REQ_TOGGLE_SORT_FIELD:
4162 case REQ_TOGGLE_SORT_ORDER:
4163 sort_view(view, request, &tree_sort_state, tree_compare);
4164 return REQ_NONE;
4166 case REQ_PARENT:
4167 if (!*opt_path) {
4168 /* quit view if at top of tree */
4169 return REQ_VIEW_CLOSE;
4170 }
4171 /* fake 'cd ..' */
4172 line = &view->line[1];
4173 break;
4175 case REQ_ENTER:
4176 break;
4178 default:
4179 return request;
4180 }
4182 /* Cleanup the stack if the tree view is at a different tree. */
4183 while (!*opt_path && tree_stack)
4184 pop_tree_stack_entry();
4186 switch (line->type) {
4187 case LINE_TREE_DIR:
4188 /* Depending on whether it is a subdirectory or parent link
4189 * mangle the path buffer. */
4190 if (line == &view->line[1] && *opt_path) {
4191 pop_tree_stack_entry();
4193 } else {
4194 const char *basename = tree_path(line);
4196 push_tree_stack_entry(basename, view->lineno);
4197 }
4199 /* Trees and subtrees share the same ID, so they are not not
4200 * unique like blobs. */
4201 flags = OPEN_RELOAD;
4202 request = REQ_VIEW_TREE;
4203 break;
4205 case LINE_TREE_FILE:
4206 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4207 request = REQ_VIEW_BLOB;
4208 break;
4210 default:
4211 return REQ_NONE;
4212 }
4214 open_view(view, request, flags);
4215 if (request == REQ_VIEW_TREE)
4216 view->lineno = tree_lineno;
4218 return REQ_NONE;
4219 }
4221 static bool
4222 tree_grep(struct view *view, struct line *line)
4223 {
4224 struct tree_entry *entry = line->data;
4225 const char *text[] = {
4226 entry->name,
4227 opt_author ? entry->author : "",
4228 opt_date ? mkdate(&entry->time) : "",
4229 NULL
4230 };
4232 return grep_text(view, text);
4233 }
4235 static void
4236 tree_select(struct view *view, struct line *line)
4237 {
4238 struct tree_entry *entry = line->data;
4240 if (line->type == LINE_TREE_FILE) {
4241 string_copy_rev(ref_blob, entry->id);
4242 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4244 } else if (line->type != LINE_TREE_DIR) {
4245 return;
4246 }
4248 string_copy_rev(view->ref, entry->id);
4249 }
4251 static const char *tree_argv[SIZEOF_ARG] = {
4252 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4253 };
4255 static struct view_ops tree_ops = {
4256 "file",
4257 tree_argv,
4258 NULL,
4259 tree_read,
4260 tree_draw,
4261 tree_request,
4262 tree_grep,
4263 tree_select,
4264 };
4266 static bool
4267 blob_read(struct view *view, char *line)
4268 {
4269 if (!line)
4270 return TRUE;
4271 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4272 }
4274 static enum request
4275 blob_request(struct view *view, enum request request, struct line *line)
4276 {
4277 switch (request) {
4278 case REQ_EDIT:
4279 open_blob_editor();
4280 return REQ_NONE;
4281 default:
4282 return pager_request(view, request, line);
4283 }
4284 }
4286 static const char *blob_argv[SIZEOF_ARG] = {
4287 "git", "cat-file", "blob", "%(blob)", NULL
4288 };
4290 static struct view_ops blob_ops = {
4291 "line",
4292 blob_argv,
4293 NULL,
4294 blob_read,
4295 pager_draw,
4296 blob_request,
4297 pager_grep,
4298 pager_select,
4299 };
4301 /*
4302 * Blame backend
4303 *
4304 * Loading the blame view is a two phase job:
4305 *
4306 * 1. File content is read either using opt_file from the
4307 * filesystem or using git-cat-file.
4308 * 2. Then blame information is incrementally added by
4309 * reading output from git-blame.
4310 */
4312 static const char *blame_head_argv[] = {
4313 "git", "blame", "--incremental", "--", "%(file)", NULL
4314 };
4316 static const char *blame_ref_argv[] = {
4317 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4318 };
4320 static const char *blame_cat_file_argv[] = {
4321 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4322 };
4324 struct blame_commit {
4325 char id[SIZEOF_REV]; /* SHA1 ID. */
4326 char title[128]; /* First line of the commit message. */
4327 const char *author; /* Author of the commit. */
4328 time_t time; /* Date from the author ident. */
4329 char filename[128]; /* Name of file. */
4330 bool has_previous; /* Was a "previous" line detected. */
4331 };
4333 struct blame {
4334 struct blame_commit *commit;
4335 unsigned long lineno;
4336 char text[1];
4337 };
4339 static bool
4340 blame_open(struct view *view)
4341 {
4342 if (*opt_ref || !io_open(&view->io, opt_file)) {
4343 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4344 return FALSE;
4345 }
4347 setup_update(view, opt_file);
4348 string_format(view->ref, "%s ...", opt_file);
4350 return TRUE;
4351 }
4353 static struct blame_commit *
4354 get_blame_commit(struct view *view, const char *id)
4355 {
4356 size_t i;
4358 for (i = 0; i < view->lines; i++) {
4359 struct blame *blame = view->line[i].data;
4361 if (!blame->commit)
4362 continue;
4364 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4365 return blame->commit;
4366 }
4368 {
4369 struct blame_commit *commit = calloc(1, sizeof(*commit));
4371 if (commit)
4372 string_ncopy(commit->id, id, SIZEOF_REV);
4373 return commit;
4374 }
4375 }
4377 static bool
4378 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4379 {
4380 const char *pos = *posref;
4382 *posref = NULL;
4383 pos = strchr(pos + 1, ' ');
4384 if (!pos || !isdigit(pos[1]))
4385 return FALSE;
4386 *number = atoi(pos + 1);
4387 if (*number < min || *number > max)
4388 return FALSE;
4390 *posref = pos;
4391 return TRUE;
4392 }
4394 static struct blame_commit *
4395 parse_blame_commit(struct view *view, const char *text, int *blamed)
4396 {
4397 struct blame_commit *commit;
4398 struct blame *blame;
4399 const char *pos = text + SIZEOF_REV - 2;
4400 size_t orig_lineno = 0;
4401 size_t lineno;
4402 size_t group;
4404 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4405 return NULL;
4407 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4408 !parse_number(&pos, &lineno, 1, view->lines) ||
4409 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4410 return NULL;
4412 commit = get_blame_commit(view, text);
4413 if (!commit)
4414 return NULL;
4416 *blamed += group;
4417 while (group--) {
4418 struct line *line = &view->line[lineno + group - 1];
4420 blame = line->data;
4421 blame->commit = commit;
4422 blame->lineno = orig_lineno + group - 1;
4423 line->dirty = 1;
4424 }
4426 return commit;
4427 }
4429 static bool
4430 blame_read_file(struct view *view, const char *line, bool *read_file)
4431 {
4432 if (!line) {
4433 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4434 struct io io = {};
4436 if (view->lines == 0 && !view->parent)
4437 die("No blame exist for %s", view->vid);
4439 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4440 report("Failed to load blame data");
4441 return TRUE;
4442 }
4444 done_io(view->pipe);
4445 view->io = io;
4446 *read_file = FALSE;
4447 return FALSE;
4449 } else {
4450 size_t linelen = strlen(line);
4451 struct blame *blame = malloc(sizeof(*blame) + linelen);
4453 if (!blame)
4454 return FALSE;
4456 blame->commit = NULL;
4457 strncpy(blame->text, line, linelen);
4458 blame->text[linelen] = 0;
4459 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4460 }
4461 }
4463 static bool
4464 match_blame_header(const char *name, char **line)
4465 {
4466 size_t namelen = strlen(name);
4467 bool matched = !strncmp(name, *line, namelen);
4469 if (matched)
4470 *line += namelen;
4472 return matched;
4473 }
4475 static bool
4476 blame_read(struct view *view, char *line)
4477 {
4478 static struct blame_commit *commit = NULL;
4479 static int blamed = 0;
4480 static bool read_file = TRUE;
4482 if (read_file)
4483 return blame_read_file(view, line, &read_file);
4485 if (!line) {
4486 /* Reset all! */
4487 commit = NULL;
4488 blamed = 0;
4489 read_file = TRUE;
4490 string_format(view->ref, "%s", view->vid);
4491 if (view_is_displayed(view)) {
4492 update_view_title(view);
4493 redraw_view_from(view, 0);
4494 }
4495 return TRUE;
4496 }
4498 if (!commit) {
4499 commit = parse_blame_commit(view, line, &blamed);
4500 string_format(view->ref, "%s %2d%%", view->vid,
4501 view->lines ? blamed * 100 / view->lines : 0);
4503 } else if (match_blame_header("author ", &line)) {
4504 commit->author = get_author(line);
4506 } else if (match_blame_header("author-time ", &line)) {
4507 commit->time = (time_t) atol(line);
4509 } else if (match_blame_header("author-tz ", &line)) {
4510 parse_timezone(&commit->time, line);
4512 } else if (match_blame_header("summary ", &line)) {
4513 string_ncopy(commit->title, line, strlen(line));
4515 } else if (match_blame_header("previous ", &line)) {
4516 commit->has_previous = TRUE;
4518 } else if (match_blame_header("filename ", &line)) {
4519 string_ncopy(commit->filename, line, strlen(line));
4520 commit = NULL;
4521 }
4523 return TRUE;
4524 }
4526 static bool
4527 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4528 {
4529 struct blame *blame = line->data;
4530 time_t *time = NULL;
4531 const char *id = NULL, *author = NULL;
4532 char text[SIZEOF_STR];
4534 if (blame->commit && *blame->commit->filename) {
4535 id = blame->commit->id;
4536 author = blame->commit->author;
4537 time = &blame->commit->time;
4538 }
4540 if (opt_date && draw_date(view, time))
4541 return TRUE;
4543 if (opt_author && draw_author(view, author))
4544 return TRUE;
4546 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4547 return TRUE;
4549 if (draw_lineno(view, lineno))
4550 return TRUE;
4552 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4553 draw_text(view, LINE_DEFAULT, text, TRUE);
4554 return TRUE;
4555 }
4557 static bool
4558 check_blame_commit(struct blame *blame, bool check_null_id)
4559 {
4560 if (!blame->commit)
4561 report("Commit data not loaded yet");
4562 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4563 report("No commit exist for the selected line");
4564 else
4565 return TRUE;
4566 return FALSE;
4567 }
4569 static void
4570 setup_blame_parent_line(struct view *view, struct blame *blame)
4571 {
4572 const char *diff_tree_argv[] = {
4573 "git", "diff-tree", "-U0", blame->commit->id,
4574 "--", blame->commit->filename, NULL
4575 };
4576 struct io io = {};
4577 int parent_lineno = -1;
4578 int blamed_lineno = -1;
4579 char *line;
4581 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4582 return;
4584 while ((line = io_get(&io, '\n', TRUE))) {
4585 if (*line == '@') {
4586 char *pos = strchr(line, '+');
4588 parent_lineno = atoi(line + 4);
4589 if (pos)
4590 blamed_lineno = atoi(pos + 1);
4592 } else if (*line == '+' && parent_lineno != -1) {
4593 if (blame->lineno == blamed_lineno - 1 &&
4594 !strcmp(blame->text, line + 1)) {
4595 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4596 break;
4597 }
4598 blamed_lineno++;
4599 }
4600 }
4602 done_io(&io);
4603 }
4605 static enum request
4606 blame_request(struct view *view, enum request request, struct line *line)
4607 {
4608 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4609 struct blame *blame = line->data;
4611 switch (request) {
4612 case REQ_VIEW_BLAME:
4613 if (check_blame_commit(blame, TRUE)) {
4614 string_copy(opt_ref, blame->commit->id);
4615 string_copy(opt_file, blame->commit->filename);
4616 if (blame->lineno)
4617 view->lineno = blame->lineno;
4618 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4619 }
4620 break;
4622 case REQ_PARENT:
4623 if (check_blame_commit(blame, TRUE) &&
4624 select_commit_parent(blame->commit->id, opt_ref,
4625 blame->commit->filename)) {
4626 string_copy(opt_file, blame->commit->filename);
4627 setup_blame_parent_line(view, blame);
4628 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4629 }
4630 break;
4632 case REQ_ENTER:
4633 if (!check_blame_commit(blame, FALSE))
4634 break;
4636 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4637 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4638 break;
4640 if (!strcmp(blame->commit->id, NULL_ID)) {
4641 struct view *diff = VIEW(REQ_VIEW_DIFF);
4642 const char *diff_index_argv[] = {
4643 "git", "diff-index", "--root", "--patch-with-stat",
4644 "-C", "-M", "HEAD", "--", view->vid, NULL
4645 };
4647 if (!blame->commit->has_previous) {
4648 diff_index_argv[1] = "diff";
4649 diff_index_argv[2] = "--no-color";
4650 diff_index_argv[6] = "--";
4651 diff_index_argv[7] = "/dev/null";
4652 }
4654 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4655 report("Failed to allocate diff command");
4656 break;
4657 }
4658 flags |= OPEN_PREPARED;
4659 }
4661 open_view(view, REQ_VIEW_DIFF, flags);
4662 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4663 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4664 break;
4666 default:
4667 return request;
4668 }
4670 return REQ_NONE;
4671 }
4673 static bool
4674 blame_grep(struct view *view, struct line *line)
4675 {
4676 struct blame *blame = line->data;
4677 struct blame_commit *commit = blame->commit;
4678 const char *text[] = {
4679 blame->text,
4680 commit ? commit->title : "",
4681 commit ? commit->id : "",
4682 commit && opt_author ? commit->author : "",
4683 commit && opt_date ? mkdate(&commit->time) : "",
4684 NULL
4685 };
4687 return grep_text(view, text);
4688 }
4690 static void
4691 blame_select(struct view *view, struct line *line)
4692 {
4693 struct blame *blame = line->data;
4694 struct blame_commit *commit = blame->commit;
4696 if (!commit)
4697 return;
4699 if (!strcmp(commit->id, NULL_ID))
4700 string_ncopy(ref_commit, "HEAD", 4);
4701 else
4702 string_copy_rev(ref_commit, commit->id);
4703 }
4705 static struct view_ops blame_ops = {
4706 "line",
4707 NULL,
4708 blame_open,
4709 blame_read,
4710 blame_draw,
4711 blame_request,
4712 blame_grep,
4713 blame_select,
4714 };
4716 /*
4717 * Branch backend
4718 */
4720 struct branch {
4721 const char *author; /* Author of the last commit. */
4722 time_t time; /* Date of the last activity. */
4723 struct ref *ref; /* Name and commit ID information. */
4724 };
4726 static const enum sort_field branch_sort_fields[] = {
4727 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4728 };
4729 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4731 static int
4732 branch_compare(const void *l1, const void *l2)
4733 {
4734 const struct branch *branch1 = ((const struct line *) l1)->data;
4735 const struct branch *branch2 = ((const struct line *) l2)->data;
4737 switch (get_sort_field(branch_sort_state)) {
4738 case ORDERBY_DATE:
4739 return sort_order(branch_sort_state, branch1->time - branch2->time);
4741 case ORDERBY_AUTHOR:
4742 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4744 case ORDERBY_NAME:
4745 default:
4746 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4747 }
4748 }
4750 static bool
4751 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4752 {
4753 struct branch *branch = line->data;
4754 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4756 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4757 return TRUE;
4759 if (opt_author && draw_author(view, branch->author))
4760 return TRUE;
4762 draw_text(view, type, branch->ref->name, TRUE);
4763 return TRUE;
4764 }
4766 static enum request
4767 branch_request(struct view *view, enum request request, struct line *line)
4768 {
4769 switch (request) {
4770 case REQ_REFRESH:
4771 load_refs();
4772 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4773 return REQ_NONE;
4775 case REQ_TOGGLE_SORT_FIELD:
4776 case REQ_TOGGLE_SORT_ORDER:
4777 sort_view(view, request, &branch_sort_state, branch_compare);
4778 return REQ_NONE;
4780 case REQ_ENTER:
4781 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4782 return REQ_NONE;
4784 default:
4785 return request;
4786 }
4787 }
4789 static bool
4790 branch_read(struct view *view, char *line)
4791 {
4792 static char id[SIZEOF_REV];
4793 struct branch *reference;
4794 size_t i;
4796 if (!line)
4797 return TRUE;
4799 switch (get_line_type(line)) {
4800 case LINE_COMMIT:
4801 string_copy_rev(id, line + STRING_SIZE("commit "));
4802 return TRUE;
4804 case LINE_AUTHOR:
4805 for (i = 0, reference = NULL; i < view->lines; i++) {
4806 struct branch *branch = view->line[i].data;
4808 if (strcmp(branch->ref->id, id))
4809 continue;
4811 view->line[i].dirty = TRUE;
4812 if (reference) {
4813 branch->author = reference->author;
4814 branch->time = reference->time;
4815 continue;
4816 }
4818 parse_author_line(line + STRING_SIZE("author "),
4819 &branch->author, &branch->time);
4820 reference = branch;
4821 }
4822 return TRUE;
4824 default:
4825 return TRUE;
4826 }
4828 }
4830 static bool
4831 branch_open_visitor(void *data, struct ref *ref)
4832 {
4833 struct view *view = data;
4834 struct branch *branch;
4836 if (ref->tag || ref->ltag || ref->remote)
4837 return TRUE;
4839 branch = calloc(1, sizeof(*branch));
4840 if (!branch)
4841 return FALSE;
4843 branch->ref = ref;
4844 return !!add_line_data(view, branch, LINE_DEFAULT);
4845 }
4847 static bool
4848 branch_open(struct view *view)
4849 {
4850 const char *branch_log[] = {
4851 "git", "log", "--no-color", "--pretty=raw",
4852 "--simplify-by-decoration", "--all", NULL
4853 };
4855 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4856 report("Failed to load branch data");
4857 return TRUE;
4858 }
4860 setup_update(view, view->id);
4861 foreach_ref(branch_open_visitor, view);
4863 return TRUE;
4864 }
4866 static bool
4867 branch_grep(struct view *view, struct line *line)
4868 {
4869 struct branch *branch = line->data;
4870 const char *text[] = {
4871 branch->ref->name,
4872 branch->author,
4873 NULL
4874 };
4876 return grep_text(view, text);
4877 }
4879 static void
4880 branch_select(struct view *view, struct line *line)
4881 {
4882 struct branch *branch = line->data;
4884 string_copy_rev(view->ref, branch->ref->id);
4885 string_copy_rev(ref_commit, branch->ref->id);
4886 string_copy_rev(ref_head, branch->ref->id);
4887 }
4889 static struct view_ops branch_ops = {
4890 "branch",
4891 NULL,
4892 branch_open,
4893 branch_read,
4894 branch_draw,
4895 branch_request,
4896 branch_grep,
4897 branch_select,
4898 };
4900 /*
4901 * Status backend
4902 */
4904 struct status {
4905 char status;
4906 struct {
4907 mode_t mode;
4908 char rev[SIZEOF_REV];
4909 char name[SIZEOF_STR];
4910 } old;
4911 struct {
4912 mode_t mode;
4913 char rev[SIZEOF_REV];
4914 char name[SIZEOF_STR];
4915 } new;
4916 };
4918 static char status_onbranch[SIZEOF_STR];
4919 static struct status stage_status;
4920 static enum line_type stage_line_type;
4921 static size_t stage_chunks;
4922 static int *stage_chunk;
4924 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4926 /* This should work even for the "On branch" line. */
4927 static inline bool
4928 status_has_none(struct view *view, struct line *line)
4929 {
4930 return line < view->line + view->lines && !line[1].data;
4931 }
4933 /* Get fields from the diff line:
4934 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4935 */
4936 static inline bool
4937 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4938 {
4939 const char *old_mode = buf + 1;
4940 const char *new_mode = buf + 8;
4941 const char *old_rev = buf + 15;
4942 const char *new_rev = buf + 56;
4943 const char *status = buf + 97;
4945 if (bufsize < 98 ||
4946 old_mode[-1] != ':' ||
4947 new_mode[-1] != ' ' ||
4948 old_rev[-1] != ' ' ||
4949 new_rev[-1] != ' ' ||
4950 status[-1] != ' ')
4951 return FALSE;
4953 file->status = *status;
4955 string_copy_rev(file->old.rev, old_rev);
4956 string_copy_rev(file->new.rev, new_rev);
4958 file->old.mode = strtoul(old_mode, NULL, 8);
4959 file->new.mode = strtoul(new_mode, NULL, 8);
4961 file->old.name[0] = file->new.name[0] = 0;
4963 return TRUE;
4964 }
4966 static bool
4967 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4968 {
4969 struct status *unmerged = NULL;
4970 char *buf;
4971 struct io io = {};
4973 if (!run_io(&io, argv, NULL, IO_RD))
4974 return FALSE;
4976 add_line_data(view, NULL, type);
4978 while ((buf = io_get(&io, 0, TRUE))) {
4979 struct status *file = unmerged;
4981 if (!file) {
4982 file = calloc(1, sizeof(*file));
4983 if (!file || !add_line_data(view, file, type))
4984 goto error_out;
4985 }
4987 /* Parse diff info part. */
4988 if (status) {
4989 file->status = status;
4990 if (status == 'A')
4991 string_copy(file->old.rev, NULL_ID);
4993 } else if (!file->status || file == unmerged) {
4994 if (!status_get_diff(file, buf, strlen(buf)))
4995 goto error_out;
4997 buf = io_get(&io, 0, TRUE);
4998 if (!buf)
4999 break;
5001 /* Collapse all modified entries that follow an
5002 * associated unmerged entry. */
5003 if (unmerged == file) {
5004 unmerged->status = 'U';
5005 unmerged = NULL;
5006 } else if (file->status == 'U') {
5007 unmerged = file;
5008 }
5009 }
5011 /* Grab the old name for rename/copy. */
5012 if (!*file->old.name &&
5013 (file->status == 'R' || file->status == 'C')) {
5014 string_ncopy(file->old.name, buf, strlen(buf));
5016 buf = io_get(&io, 0, TRUE);
5017 if (!buf)
5018 break;
5019 }
5021 /* git-ls-files just delivers a NUL separated list of
5022 * file names similar to the second half of the
5023 * git-diff-* output. */
5024 string_ncopy(file->new.name, buf, strlen(buf));
5025 if (!*file->old.name)
5026 string_copy(file->old.name, file->new.name);
5027 file = NULL;
5028 }
5030 if (io_error(&io)) {
5031 error_out:
5032 done_io(&io);
5033 return FALSE;
5034 }
5036 if (!view->line[view->lines - 1].data)
5037 add_line_data(view, NULL, LINE_STAT_NONE);
5039 done_io(&io);
5040 return TRUE;
5041 }
5043 /* Don't show unmerged entries in the staged section. */
5044 static const char *status_diff_index_argv[] = {
5045 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5046 "--cached", "-M", "HEAD", NULL
5047 };
5049 static const char *status_diff_files_argv[] = {
5050 "git", "diff-files", "-z", NULL
5051 };
5053 static const char *status_list_other_argv[] = {
5054 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5055 };
5057 static const char *status_list_no_head_argv[] = {
5058 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5059 };
5061 static const char *update_index_argv[] = {
5062 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5063 };
5065 /* Restore the previous line number to stay in the context or select a
5066 * line with something that can be updated. */
5067 static void
5068 status_restore(struct view *view)
5069 {
5070 if (view->p_lineno >= view->lines)
5071 view->p_lineno = view->lines - 1;
5072 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5073 view->p_lineno++;
5074 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5075 view->p_lineno--;
5077 /* If the above fails, always skip the "On branch" line. */
5078 if (view->p_lineno < view->lines)
5079 view->lineno = view->p_lineno;
5080 else
5081 view->lineno = 1;
5083 if (view->lineno < view->offset)
5084 view->offset = view->lineno;
5085 else if (view->offset + view->height <= view->lineno)
5086 view->offset = view->lineno - view->height + 1;
5088 view->p_restore = FALSE;
5089 }
5091 static void
5092 status_update_onbranch(void)
5093 {
5094 static const char *paths[][2] = {
5095 { "rebase-apply/rebasing", "Rebasing" },
5096 { "rebase-apply/applying", "Applying mailbox" },
5097 { "rebase-apply/", "Rebasing mailbox" },
5098 { "rebase-merge/interactive", "Interactive rebase" },
5099 { "rebase-merge/", "Rebase merge" },
5100 { "MERGE_HEAD", "Merging" },
5101 { "BISECT_LOG", "Bisecting" },
5102 { "HEAD", "On branch" },
5103 };
5104 char buf[SIZEOF_STR];
5105 struct stat stat;
5106 int i;
5108 if (is_initial_commit()) {
5109 string_copy(status_onbranch, "Initial commit");
5110 return;
5111 }
5113 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5114 char *head = opt_head;
5116 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5117 lstat(buf, &stat) < 0)
5118 continue;
5120 if (!*opt_head) {
5121 struct io io = {};
5123 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5124 io_open(&io, buf) &&
5125 io_read_buf(&io, buf, sizeof(buf))) {
5126 head = buf;
5127 if (!prefixcmp(head, "refs/heads/"))
5128 head += STRING_SIZE("refs/heads/");
5129 }
5130 }
5132 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5133 string_copy(status_onbranch, opt_head);
5134 return;
5135 }
5137 string_copy(status_onbranch, "Not currently on any branch");
5138 }
5140 /* First parse staged info using git-diff-index(1), then parse unstaged
5141 * info using git-diff-files(1), and finally untracked files using
5142 * git-ls-files(1). */
5143 static bool
5144 status_open(struct view *view)
5145 {
5146 reset_view(view);
5148 add_line_data(view, NULL, LINE_STAT_HEAD);
5149 status_update_onbranch();
5151 run_io_bg(update_index_argv);
5153 if (is_initial_commit()) {
5154 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5155 return FALSE;
5156 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5157 return FALSE;
5158 }
5160 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5161 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5162 return FALSE;
5164 /* Restore the exact position or use the specialized restore
5165 * mode? */
5166 if (!view->p_restore)
5167 status_restore(view);
5168 return TRUE;
5169 }
5171 static bool
5172 status_draw(struct view *view, struct line *line, unsigned int lineno)
5173 {
5174 struct status *status = line->data;
5175 enum line_type type;
5176 const char *text;
5178 if (!status) {
5179 switch (line->type) {
5180 case LINE_STAT_STAGED:
5181 type = LINE_STAT_SECTION;
5182 text = "Changes to be committed:";
5183 break;
5185 case LINE_STAT_UNSTAGED:
5186 type = LINE_STAT_SECTION;
5187 text = "Changed but not updated:";
5188 break;
5190 case LINE_STAT_UNTRACKED:
5191 type = LINE_STAT_SECTION;
5192 text = "Untracked files:";
5193 break;
5195 case LINE_STAT_NONE:
5196 type = LINE_DEFAULT;
5197 text = " (no files)";
5198 break;
5200 case LINE_STAT_HEAD:
5201 type = LINE_STAT_HEAD;
5202 text = status_onbranch;
5203 break;
5205 default:
5206 return FALSE;
5207 }
5208 } else {
5209 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5211 buf[0] = status->status;
5212 if (draw_text(view, line->type, buf, TRUE))
5213 return TRUE;
5214 type = LINE_DEFAULT;
5215 text = status->new.name;
5216 }
5218 draw_text(view, type, text, TRUE);
5219 return TRUE;
5220 }
5222 static enum request
5223 status_load_error(struct view *view, struct view *stage, const char *path)
5224 {
5225 if (displayed_views() == 2 || display[current_view] != view)
5226 maximize_view(view);
5227 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5228 return REQ_NONE;
5229 }
5231 static enum request
5232 status_enter(struct view *view, struct line *line)
5233 {
5234 struct status *status = line->data;
5235 const char *oldpath = status ? status->old.name : NULL;
5236 /* Diffs for unmerged entries are empty when passing the new
5237 * path, so leave it empty. */
5238 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5239 const char *info;
5240 enum open_flags split;
5241 struct view *stage = VIEW(REQ_VIEW_STAGE);
5243 if (line->type == LINE_STAT_NONE ||
5244 (!status && line[1].type == LINE_STAT_NONE)) {
5245 report("No file to diff");
5246 return REQ_NONE;
5247 }
5249 switch (line->type) {
5250 case LINE_STAT_STAGED:
5251 if (is_initial_commit()) {
5252 const char *no_head_diff_argv[] = {
5253 "git", "diff", "--no-color", "--patch-with-stat",
5254 "--", "/dev/null", newpath, NULL
5255 };
5257 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5258 return status_load_error(view, stage, newpath);
5259 } else {
5260 const char *index_show_argv[] = {
5261 "git", "diff-index", "--root", "--patch-with-stat",
5262 "-C", "-M", "--cached", "HEAD", "--",
5263 oldpath, newpath, NULL
5264 };
5266 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5267 return status_load_error(view, stage, newpath);
5268 }
5270 if (status)
5271 info = "Staged changes to %s";
5272 else
5273 info = "Staged changes";
5274 break;
5276 case LINE_STAT_UNSTAGED:
5277 {
5278 const char *files_show_argv[] = {
5279 "git", "diff-files", "--root", "--patch-with-stat",
5280 "-C", "-M", "--", oldpath, newpath, NULL
5281 };
5283 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5284 return status_load_error(view, stage, newpath);
5285 if (status)
5286 info = "Unstaged changes to %s";
5287 else
5288 info = "Unstaged changes";
5289 break;
5290 }
5291 case LINE_STAT_UNTRACKED:
5292 if (!newpath) {
5293 report("No file to show");
5294 return REQ_NONE;
5295 }
5297 if (!suffixcmp(status->new.name, -1, "/")) {
5298 report("Cannot display a directory");
5299 return REQ_NONE;
5300 }
5302 if (!prepare_update_file(stage, newpath))
5303 return status_load_error(view, stage, newpath);
5304 info = "Untracked file %s";
5305 break;
5307 case LINE_STAT_HEAD:
5308 return REQ_NONE;
5310 default:
5311 die("line type %d not handled in switch", line->type);
5312 }
5314 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5315 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5316 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5317 if (status) {
5318 stage_status = *status;
5319 } else {
5320 memset(&stage_status, 0, sizeof(stage_status));
5321 }
5323 stage_line_type = line->type;
5324 stage_chunks = 0;
5325 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5326 }
5328 return REQ_NONE;
5329 }
5331 static bool
5332 status_exists(struct status *status, enum line_type type)
5333 {
5334 struct view *view = VIEW(REQ_VIEW_STATUS);
5335 unsigned long lineno;
5337 for (lineno = 0; lineno < view->lines; lineno++) {
5338 struct line *line = &view->line[lineno];
5339 struct status *pos = line->data;
5341 if (line->type != type)
5342 continue;
5343 if (!pos && (!status || !status->status) && line[1].data) {
5344 select_view_line(view, lineno);
5345 return TRUE;
5346 }
5347 if (pos && !strcmp(status->new.name, pos->new.name)) {
5348 select_view_line(view, lineno);
5349 return TRUE;
5350 }
5351 }
5353 return FALSE;
5354 }
5357 static bool
5358 status_update_prepare(struct io *io, enum line_type type)
5359 {
5360 const char *staged_argv[] = {
5361 "git", "update-index", "-z", "--index-info", NULL
5362 };
5363 const char *others_argv[] = {
5364 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5365 };
5367 switch (type) {
5368 case LINE_STAT_STAGED:
5369 return run_io(io, staged_argv, opt_cdup, IO_WR);
5371 case LINE_STAT_UNSTAGED:
5372 return run_io(io, others_argv, opt_cdup, IO_WR);
5374 case LINE_STAT_UNTRACKED:
5375 return run_io(io, others_argv, NULL, IO_WR);
5377 default:
5378 die("line type %d not handled in switch", type);
5379 return FALSE;
5380 }
5381 }
5383 static bool
5384 status_update_write(struct io *io, struct status *status, enum line_type type)
5385 {
5386 char buf[SIZEOF_STR];
5387 size_t bufsize = 0;
5389 switch (type) {
5390 case LINE_STAT_STAGED:
5391 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5392 status->old.mode,
5393 status->old.rev,
5394 status->old.name, 0))
5395 return FALSE;
5396 break;
5398 case LINE_STAT_UNSTAGED:
5399 case LINE_STAT_UNTRACKED:
5400 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5401 return FALSE;
5402 break;
5404 default:
5405 die("line type %d not handled in switch", type);
5406 }
5408 return io_write(io, buf, bufsize);
5409 }
5411 static bool
5412 status_update_file(struct status *status, enum line_type type)
5413 {
5414 struct io io = {};
5415 bool result;
5417 if (!status_update_prepare(&io, type))
5418 return FALSE;
5420 result = status_update_write(&io, status, type);
5421 return done_io(&io) && result;
5422 }
5424 static bool
5425 status_update_files(struct view *view, struct line *line)
5426 {
5427 char buf[sizeof(view->ref)];
5428 struct io io = {};
5429 bool result = TRUE;
5430 struct line *pos = view->line + view->lines;
5431 int files = 0;
5432 int file, done;
5433 int cursor_y, cursor_x;
5435 if (!status_update_prepare(&io, line->type))
5436 return FALSE;
5438 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5439 files++;
5441 string_copy(buf, view->ref);
5442 getsyx(cursor_y, cursor_x);
5443 for (file = 0, done = 5; result && file < files; line++, file++) {
5444 int almost_done = file * 100 / files;
5446 if (almost_done > done) {
5447 done = almost_done;
5448 string_format(view->ref, "updating file %u of %u (%d%% done)",
5449 file, files, done);
5450 update_view_title(view);
5451 setsyx(cursor_y, cursor_x);
5452 doupdate();
5453 }
5454 result = status_update_write(&io, line->data, line->type);
5455 }
5456 string_copy(view->ref, buf);
5458 return done_io(&io) && result;
5459 }
5461 static bool
5462 status_update(struct view *view)
5463 {
5464 struct line *line = &view->line[view->lineno];
5466 assert(view->lines);
5468 if (!line->data) {
5469 /* This should work even for the "On branch" line. */
5470 if (line < view->line + view->lines && !line[1].data) {
5471 report("Nothing to update");
5472 return FALSE;
5473 }
5475 if (!status_update_files(view, line + 1)) {
5476 report("Failed to update file status");
5477 return FALSE;
5478 }
5480 } else if (!status_update_file(line->data, line->type)) {
5481 report("Failed to update file status");
5482 return FALSE;
5483 }
5485 return TRUE;
5486 }
5488 static bool
5489 status_revert(struct status *status, enum line_type type, bool has_none)
5490 {
5491 if (!status || type != LINE_STAT_UNSTAGED) {
5492 if (type == LINE_STAT_STAGED) {
5493 report("Cannot revert changes to staged files");
5494 } else if (type == LINE_STAT_UNTRACKED) {
5495 report("Cannot revert changes to untracked files");
5496 } else if (has_none) {
5497 report("Nothing to revert");
5498 } else {
5499 report("Cannot revert changes to multiple files");
5500 }
5501 return FALSE;
5503 } else {
5504 char mode[10] = "100644";
5505 const char *reset_argv[] = {
5506 "git", "update-index", "--cacheinfo", mode,
5507 status->old.rev, status->old.name, NULL
5508 };
5509 const char *checkout_argv[] = {
5510 "git", "checkout", "--", status->old.name, NULL
5511 };
5513 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5514 return FALSE;
5515 string_format(mode, "%o", status->old.mode);
5516 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5517 run_io_fg(checkout_argv, opt_cdup);
5518 }
5519 }
5521 static enum request
5522 status_request(struct view *view, enum request request, struct line *line)
5523 {
5524 struct status *status = line->data;
5526 switch (request) {
5527 case REQ_STATUS_UPDATE:
5528 if (!status_update(view))
5529 return REQ_NONE;
5530 break;
5532 case REQ_STATUS_REVERT:
5533 if (!status_revert(status, line->type, status_has_none(view, line)))
5534 return REQ_NONE;
5535 break;
5537 case REQ_STATUS_MERGE:
5538 if (!status || status->status != 'U') {
5539 report("Merging only possible for files with unmerged status ('U').");
5540 return REQ_NONE;
5541 }
5542 open_mergetool(status->new.name);
5543 break;
5545 case REQ_EDIT:
5546 if (!status)
5547 return request;
5548 if (status->status == 'D') {
5549 report("File has been deleted.");
5550 return REQ_NONE;
5551 }
5553 open_editor(status->status != '?', status->new.name);
5554 break;
5556 case REQ_VIEW_BLAME:
5557 if (status) {
5558 string_copy(opt_file, status->new.name);
5559 opt_ref[0] = 0;
5560 }
5561 return request;
5563 case REQ_ENTER:
5564 /* After returning the status view has been split to
5565 * show the stage view. No further reloading is
5566 * necessary. */
5567 return status_enter(view, line);
5569 case REQ_REFRESH:
5570 /* Simply reload the view. */
5571 break;
5573 default:
5574 return request;
5575 }
5577 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5579 return REQ_NONE;
5580 }
5582 static void
5583 status_select(struct view *view, struct line *line)
5584 {
5585 struct status *status = line->data;
5586 char file[SIZEOF_STR] = "all files";
5587 const char *text;
5588 const char *key;
5590 if (status && !string_format(file, "'%s'", status->new.name))
5591 return;
5593 if (!status && line[1].type == LINE_STAT_NONE)
5594 line++;
5596 switch (line->type) {
5597 case LINE_STAT_STAGED:
5598 text = "Press %s to unstage %s for commit";
5599 break;
5601 case LINE_STAT_UNSTAGED:
5602 text = "Press %s to stage %s for commit";
5603 break;
5605 case LINE_STAT_UNTRACKED:
5606 text = "Press %s to stage %s for addition";
5607 break;
5609 case LINE_STAT_HEAD:
5610 case LINE_STAT_NONE:
5611 text = "Nothing to update";
5612 break;
5614 default:
5615 die("line type %d not handled in switch", line->type);
5616 }
5618 if (status && status->status == 'U') {
5619 text = "Press %s to resolve conflict in %s";
5620 key = get_key(REQ_STATUS_MERGE);
5622 } else {
5623 key = get_key(REQ_STATUS_UPDATE);
5624 }
5626 string_format(view->ref, text, key, file);
5627 }
5629 static bool
5630 status_grep(struct view *view, struct line *line)
5631 {
5632 struct status *status = line->data;
5634 if (status) {
5635 const char buf[2] = { status->status, 0 };
5636 const char *text[] = { status->new.name, buf, NULL };
5638 return grep_text(view, text);
5639 }
5641 return FALSE;
5642 }
5644 static struct view_ops status_ops = {
5645 "file",
5646 NULL,
5647 status_open,
5648 NULL,
5649 status_draw,
5650 status_request,
5651 status_grep,
5652 status_select,
5653 };
5656 static bool
5657 stage_diff_write(struct io *io, struct line *line, struct line *end)
5658 {
5659 while (line < end) {
5660 if (!io_write(io, line->data, strlen(line->data)) ||
5661 !io_write(io, "\n", 1))
5662 return FALSE;
5663 line++;
5664 if (line->type == LINE_DIFF_CHUNK ||
5665 line->type == LINE_DIFF_HEADER)
5666 break;
5667 }
5669 return TRUE;
5670 }
5672 static struct line *
5673 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5674 {
5675 for (; view->line < line; line--)
5676 if (line->type == type)
5677 return line;
5679 return NULL;
5680 }
5682 static bool
5683 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5684 {
5685 const char *apply_argv[SIZEOF_ARG] = {
5686 "git", "apply", "--whitespace=nowarn", NULL
5687 };
5688 struct line *diff_hdr;
5689 struct io io = {};
5690 int argc = 3;
5692 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5693 if (!diff_hdr)
5694 return FALSE;
5696 if (!revert)
5697 apply_argv[argc++] = "--cached";
5698 if (revert || stage_line_type == LINE_STAT_STAGED)
5699 apply_argv[argc++] = "-R";
5700 apply_argv[argc++] = "-";
5701 apply_argv[argc++] = NULL;
5702 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5703 return FALSE;
5705 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5706 !stage_diff_write(&io, chunk, view->line + view->lines))
5707 chunk = NULL;
5709 done_io(&io);
5710 run_io_bg(update_index_argv);
5712 return chunk ? TRUE : FALSE;
5713 }
5715 static bool
5716 stage_update(struct view *view, struct line *line)
5717 {
5718 struct line *chunk = NULL;
5720 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5721 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5723 if (chunk) {
5724 if (!stage_apply_chunk(view, chunk, FALSE)) {
5725 report("Failed to apply chunk");
5726 return FALSE;
5727 }
5729 } else if (!stage_status.status) {
5730 view = VIEW(REQ_VIEW_STATUS);
5732 for (line = view->line; line < view->line + view->lines; line++)
5733 if (line->type == stage_line_type)
5734 break;
5736 if (!status_update_files(view, line + 1)) {
5737 report("Failed to update files");
5738 return FALSE;
5739 }
5741 } else if (!status_update_file(&stage_status, stage_line_type)) {
5742 report("Failed to update file");
5743 return FALSE;
5744 }
5746 return TRUE;
5747 }
5749 static bool
5750 stage_revert(struct view *view, struct line *line)
5751 {
5752 struct line *chunk = NULL;
5754 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5755 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5757 if (chunk) {
5758 if (!prompt_yesno("Are you sure you want to revert changes?"))
5759 return FALSE;
5761 if (!stage_apply_chunk(view, chunk, TRUE)) {
5762 report("Failed to revert chunk");
5763 return FALSE;
5764 }
5765 return TRUE;
5767 } else {
5768 return status_revert(stage_status.status ? &stage_status : NULL,
5769 stage_line_type, FALSE);
5770 }
5771 }
5774 static void
5775 stage_next(struct view *view, struct line *line)
5776 {
5777 int i;
5779 if (!stage_chunks) {
5780 for (line = view->line; line < view->line + view->lines; line++) {
5781 if (line->type != LINE_DIFF_CHUNK)
5782 continue;
5784 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5785 report("Allocation failure");
5786 return;
5787 }
5789 stage_chunk[stage_chunks++] = line - view->line;
5790 }
5791 }
5793 for (i = 0; i < stage_chunks; i++) {
5794 if (stage_chunk[i] > view->lineno) {
5795 do_scroll_view(view, stage_chunk[i] - view->lineno);
5796 report("Chunk %d of %d", i + 1, stage_chunks);
5797 return;
5798 }
5799 }
5801 report("No next chunk found");
5802 }
5804 static enum request
5805 stage_request(struct view *view, enum request request, struct line *line)
5806 {
5807 switch (request) {
5808 case REQ_STATUS_UPDATE:
5809 if (!stage_update(view, line))
5810 return REQ_NONE;
5811 break;
5813 case REQ_STATUS_REVERT:
5814 if (!stage_revert(view, line))
5815 return REQ_NONE;
5816 break;
5818 case REQ_STAGE_NEXT:
5819 if (stage_line_type == LINE_STAT_UNTRACKED) {
5820 report("File is untracked; press %s to add",
5821 get_key(REQ_STATUS_UPDATE));
5822 return REQ_NONE;
5823 }
5824 stage_next(view, line);
5825 return REQ_NONE;
5827 case REQ_EDIT:
5828 if (!stage_status.new.name[0])
5829 return request;
5830 if (stage_status.status == 'D') {
5831 report("File has been deleted.");
5832 return REQ_NONE;
5833 }
5835 open_editor(stage_status.status != '?', stage_status.new.name);
5836 break;
5838 case REQ_REFRESH:
5839 /* Reload everything ... */
5840 break;
5842 case REQ_VIEW_BLAME:
5843 if (stage_status.new.name[0]) {
5844 string_copy(opt_file, stage_status.new.name);
5845 opt_ref[0] = 0;
5846 }
5847 return request;
5849 case REQ_ENTER:
5850 return pager_request(view, request, line);
5852 default:
5853 return request;
5854 }
5856 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5857 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5859 /* Check whether the staged entry still exists, and close the
5860 * stage view if it doesn't. */
5861 if (!status_exists(&stage_status, stage_line_type)) {
5862 status_restore(VIEW(REQ_VIEW_STATUS));
5863 return REQ_VIEW_CLOSE;
5864 }
5866 if (stage_line_type == LINE_STAT_UNTRACKED) {
5867 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5868 report("Cannot display a directory");
5869 return REQ_NONE;
5870 }
5872 if (!prepare_update_file(view, stage_status.new.name)) {
5873 report("Failed to open file: %s", strerror(errno));
5874 return REQ_NONE;
5875 }
5876 }
5877 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5879 return REQ_NONE;
5880 }
5882 static struct view_ops stage_ops = {
5883 "line",
5884 NULL,
5885 NULL,
5886 pager_read,
5887 pager_draw,
5888 stage_request,
5889 pager_grep,
5890 pager_select,
5891 };
5894 /*
5895 * Revision graph
5896 */
5898 struct commit {
5899 char id[SIZEOF_REV]; /* SHA1 ID. */
5900 char title[128]; /* First line of the commit message. */
5901 const char *author; /* Author of the commit. */
5902 time_t time; /* Date from the author ident. */
5903 struct ref **refs; /* Repository references. */
5904 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5905 size_t graph_size; /* The width of the graph array. */
5906 bool has_parents; /* Rewritten --parents seen. */
5907 };
5909 /* Size of rev graph with no "padding" columns */
5910 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5912 struct rev_graph {
5913 struct rev_graph *prev, *next, *parents;
5914 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5915 size_t size;
5916 struct commit *commit;
5917 size_t pos;
5918 unsigned int boundary:1;
5919 };
5921 /* Parents of the commit being visualized. */
5922 static struct rev_graph graph_parents[4];
5924 /* The current stack of revisions on the graph. */
5925 static struct rev_graph graph_stacks[4] = {
5926 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5927 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5928 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5929 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5930 };
5932 static inline bool
5933 graph_parent_is_merge(struct rev_graph *graph)
5934 {
5935 return graph->parents->size > 1;
5936 }
5938 static inline void
5939 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5940 {
5941 struct commit *commit = graph->commit;
5943 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5944 commit->graph[commit->graph_size++] = symbol;
5945 }
5947 static void
5948 clear_rev_graph(struct rev_graph *graph)
5949 {
5950 graph->boundary = 0;
5951 graph->size = graph->pos = 0;
5952 graph->commit = NULL;
5953 memset(graph->parents, 0, sizeof(*graph->parents));
5954 }
5956 static void
5957 done_rev_graph(struct rev_graph *graph)
5958 {
5959 if (graph_parent_is_merge(graph) &&
5960 graph->pos < graph->size - 1 &&
5961 graph->next->size == graph->size + graph->parents->size - 1) {
5962 size_t i = graph->pos + graph->parents->size - 1;
5964 graph->commit->graph_size = i * 2;
5965 while (i < graph->next->size - 1) {
5966 append_to_rev_graph(graph, ' ');
5967 append_to_rev_graph(graph, '\\');
5968 i++;
5969 }
5970 }
5972 clear_rev_graph(graph);
5973 }
5975 static void
5976 push_rev_graph(struct rev_graph *graph, const char *parent)
5977 {
5978 int i;
5980 /* "Collapse" duplicate parents lines.
5981 *
5982 * FIXME: This needs to also update update the drawn graph but
5983 * for now it just serves as a method for pruning graph lines. */
5984 for (i = 0; i < graph->size; i++)
5985 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5986 return;
5988 if (graph->size < SIZEOF_REVITEMS) {
5989 string_copy_rev(graph->rev[graph->size++], parent);
5990 }
5991 }
5993 static chtype
5994 get_rev_graph_symbol(struct rev_graph *graph)
5995 {
5996 chtype symbol;
5998 if (graph->boundary)
5999 symbol = REVGRAPH_BOUND;
6000 else if (graph->parents->size == 0)
6001 symbol = REVGRAPH_INIT;
6002 else if (graph_parent_is_merge(graph))
6003 symbol = REVGRAPH_MERGE;
6004 else if (graph->pos >= graph->size)
6005 symbol = REVGRAPH_BRANCH;
6006 else
6007 symbol = REVGRAPH_COMMIT;
6009 return symbol;
6010 }
6012 static void
6013 draw_rev_graph(struct rev_graph *graph)
6014 {
6015 struct rev_filler {
6016 chtype separator, line;
6017 };
6018 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6019 static struct rev_filler fillers[] = {
6020 { ' ', '|' },
6021 { '`', '.' },
6022 { '\'', ' ' },
6023 { '/', ' ' },
6024 };
6025 chtype symbol = get_rev_graph_symbol(graph);
6026 struct rev_filler *filler;
6027 size_t i;
6029 if (opt_line_graphics)
6030 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6032 filler = &fillers[DEFAULT];
6034 for (i = 0; i < graph->pos; i++) {
6035 append_to_rev_graph(graph, filler->line);
6036 if (graph_parent_is_merge(graph->prev) &&
6037 graph->prev->pos == i)
6038 filler = &fillers[RSHARP];
6040 append_to_rev_graph(graph, filler->separator);
6041 }
6043 /* Place the symbol for this revision. */
6044 append_to_rev_graph(graph, symbol);
6046 if (graph->prev->size > graph->size)
6047 filler = &fillers[RDIAG];
6048 else
6049 filler = &fillers[DEFAULT];
6051 i++;
6053 for (; i < graph->size; i++) {
6054 append_to_rev_graph(graph, filler->separator);
6055 append_to_rev_graph(graph, filler->line);
6056 if (graph_parent_is_merge(graph->prev) &&
6057 i < graph->prev->pos + graph->parents->size)
6058 filler = &fillers[RSHARP];
6059 if (graph->prev->size > graph->size)
6060 filler = &fillers[LDIAG];
6061 }
6063 if (graph->prev->size > graph->size) {
6064 append_to_rev_graph(graph, filler->separator);
6065 if (filler->line != ' ')
6066 append_to_rev_graph(graph, filler->line);
6067 }
6068 }
6070 /* Prepare the next rev graph */
6071 static void
6072 prepare_rev_graph(struct rev_graph *graph)
6073 {
6074 size_t i;
6076 /* First, traverse all lines of revisions up to the active one. */
6077 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6078 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6079 break;
6081 push_rev_graph(graph->next, graph->rev[graph->pos]);
6082 }
6084 /* Interleave the new revision parent(s). */
6085 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6086 push_rev_graph(graph->next, graph->parents->rev[i]);
6088 /* Lastly, put any remaining revisions. */
6089 for (i = graph->pos + 1; i < graph->size; i++)
6090 push_rev_graph(graph->next, graph->rev[i]);
6091 }
6093 static void
6094 update_rev_graph(struct view *view, struct rev_graph *graph)
6095 {
6096 /* If this is the finalizing update ... */
6097 if (graph->commit)
6098 prepare_rev_graph(graph);
6100 /* Graph visualization needs a one rev look-ahead,
6101 * so the first update doesn't visualize anything. */
6102 if (!graph->prev->commit)
6103 return;
6105 if (view->lines > 2)
6106 view->line[view->lines - 3].dirty = 1;
6107 if (view->lines > 1)
6108 view->line[view->lines - 2].dirty = 1;
6109 draw_rev_graph(graph->prev);
6110 done_rev_graph(graph->prev->prev);
6111 }
6114 /*
6115 * Main view backend
6116 */
6118 static const char *main_argv[SIZEOF_ARG] = {
6119 "git", "log", "--no-color", "--pretty=raw", "--parents",
6120 "--topo-order", "%(head)", NULL
6121 };
6123 static bool
6124 main_draw(struct view *view, struct line *line, unsigned int lineno)
6125 {
6126 struct commit *commit = line->data;
6128 if (!commit->author)
6129 return FALSE;
6131 if (opt_date && draw_date(view, &commit->time))
6132 return TRUE;
6134 if (opt_author && draw_author(view, commit->author))
6135 return TRUE;
6137 if (opt_rev_graph && commit->graph_size &&
6138 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6139 return TRUE;
6141 if (opt_show_refs && commit->refs) {
6142 size_t i = 0;
6144 do {
6145 struct ref *ref = commit->refs[i];
6146 enum line_type type;
6148 if (ref->head)
6149 type = LINE_MAIN_HEAD;
6150 else if (ref->ltag)
6151 type = LINE_MAIN_LOCAL_TAG;
6152 else if (ref->tag)
6153 type = LINE_MAIN_TAG;
6154 else if (ref->tracked)
6155 type = LINE_MAIN_TRACKED;
6156 else if (ref->remote)
6157 type = LINE_MAIN_REMOTE;
6158 else
6159 type = LINE_MAIN_REF;
6161 if (draw_text(view, type, "[", TRUE) ||
6162 draw_text(view, type, ref->name, TRUE) ||
6163 draw_text(view, type, "]", TRUE))
6164 return TRUE;
6166 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6167 return TRUE;
6168 } while (commit->refs[i++]->next);
6169 }
6171 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6172 return TRUE;
6173 }
6175 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6176 static bool
6177 main_read(struct view *view, char *line)
6178 {
6179 static struct rev_graph *graph = graph_stacks;
6180 enum line_type type;
6181 struct commit *commit;
6183 if (!line) {
6184 int i;
6186 if (!view->lines && !view->parent)
6187 die("No revisions match the given arguments.");
6188 if (view->lines > 0) {
6189 commit = view->line[view->lines - 1].data;
6190 view->line[view->lines - 1].dirty = 1;
6191 if (!commit->author) {
6192 view->lines--;
6193 free(commit);
6194 graph->commit = NULL;
6195 }
6196 }
6197 update_rev_graph(view, graph);
6199 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6200 clear_rev_graph(&graph_stacks[i]);
6201 return TRUE;
6202 }
6204 type = get_line_type(line);
6205 if (type == LINE_COMMIT) {
6206 commit = calloc(1, sizeof(struct commit));
6207 if (!commit)
6208 return FALSE;
6210 line += STRING_SIZE("commit ");
6211 if (*line == '-') {
6212 graph->boundary = 1;
6213 line++;
6214 }
6216 string_copy_rev(commit->id, line);
6217 commit->refs = get_refs(commit->id);
6218 graph->commit = commit;
6219 add_line_data(view, commit, LINE_MAIN_COMMIT);
6221 while ((line = strchr(line, ' '))) {
6222 line++;
6223 push_rev_graph(graph->parents, line);
6224 commit->has_parents = TRUE;
6225 }
6226 return TRUE;
6227 }
6229 if (!view->lines)
6230 return TRUE;
6231 commit = view->line[view->lines - 1].data;
6233 switch (type) {
6234 case LINE_PARENT:
6235 if (commit->has_parents)
6236 break;
6237 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6238 break;
6240 case LINE_AUTHOR:
6241 parse_author_line(line + STRING_SIZE("author "),
6242 &commit->author, &commit->time);
6243 update_rev_graph(view, graph);
6244 graph = graph->next;
6245 break;
6247 default:
6248 /* Fill in the commit title if it has not already been set. */
6249 if (commit->title[0])
6250 break;
6252 /* Require titles to start with a non-space character at the
6253 * offset used by git log. */
6254 if (strncmp(line, " ", 4))
6255 break;
6256 line += 4;
6257 /* Well, if the title starts with a whitespace character,
6258 * try to be forgiving. Otherwise we end up with no title. */
6259 while (isspace(*line))
6260 line++;
6261 if (*line == '\0')
6262 break;
6263 /* FIXME: More graceful handling of titles; append "..." to
6264 * shortened titles, etc. */
6266 string_expand(commit->title, sizeof(commit->title), line, 1);
6267 view->line[view->lines - 1].dirty = 1;
6268 }
6270 return TRUE;
6271 }
6273 static enum request
6274 main_request(struct view *view, enum request request, struct line *line)
6275 {
6276 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6278 switch (request) {
6279 case REQ_ENTER:
6280 open_view(view, REQ_VIEW_DIFF, flags);
6281 break;
6282 case REQ_REFRESH:
6283 load_refs();
6284 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6285 break;
6286 default:
6287 return request;
6288 }
6290 return REQ_NONE;
6291 }
6293 static bool
6294 grep_refs(struct ref **refs, regex_t *regex)
6295 {
6296 regmatch_t pmatch;
6297 size_t i = 0;
6299 if (!opt_show_refs || !refs)
6300 return FALSE;
6301 do {
6302 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6303 return TRUE;
6304 } while (refs[i++]->next);
6306 return FALSE;
6307 }
6309 static bool
6310 main_grep(struct view *view, struct line *line)
6311 {
6312 struct commit *commit = line->data;
6313 const char *text[] = {
6314 commit->title,
6315 opt_author ? commit->author : "",
6316 opt_date ? mkdate(&commit->time) : "",
6317 NULL
6318 };
6320 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6321 }
6323 static void
6324 main_select(struct view *view, struct line *line)
6325 {
6326 struct commit *commit = line->data;
6328 string_copy_rev(view->ref, commit->id);
6329 string_copy_rev(ref_commit, view->ref);
6330 }
6332 static struct view_ops main_ops = {
6333 "commit",
6334 main_argv,
6335 NULL,
6336 main_read,
6337 main_draw,
6338 main_request,
6339 main_grep,
6340 main_select,
6341 };
6344 /*
6345 * Unicode / UTF-8 handling
6346 *
6347 * NOTE: Much of the following code for dealing with Unicode is derived from
6348 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6349 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6350 */
6352 static inline int
6353 unicode_width(unsigned long c)
6354 {
6355 if (c >= 0x1100 &&
6356 (c <= 0x115f /* Hangul Jamo */
6357 || c == 0x2329
6358 || c == 0x232a
6359 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6360 /* CJK ... Yi */
6361 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6362 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6363 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6364 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6365 || (c >= 0xffe0 && c <= 0xffe6)
6366 || (c >= 0x20000 && c <= 0x2fffd)
6367 || (c >= 0x30000 && c <= 0x3fffd)))
6368 return 2;
6370 if (c == '\t')
6371 return opt_tab_size;
6373 return 1;
6374 }
6376 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6377 * Illegal bytes are set one. */
6378 static const unsigned char utf8_bytes[256] = {
6379 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,
6380 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,
6381 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,
6382 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,
6383 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,
6384 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,
6385 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,
6386 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,
6387 };
6389 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6390 static inline unsigned long
6391 utf8_to_unicode(const char *string, size_t length)
6392 {
6393 unsigned long unicode;
6395 switch (length) {
6396 case 1:
6397 unicode = string[0];
6398 break;
6399 case 2:
6400 unicode = (string[0] & 0x1f) << 6;
6401 unicode += (string[1] & 0x3f);
6402 break;
6403 case 3:
6404 unicode = (string[0] & 0x0f) << 12;
6405 unicode += ((string[1] & 0x3f) << 6);
6406 unicode += (string[2] & 0x3f);
6407 break;
6408 case 4:
6409 unicode = (string[0] & 0x0f) << 18;
6410 unicode += ((string[1] & 0x3f) << 12);
6411 unicode += ((string[2] & 0x3f) << 6);
6412 unicode += (string[3] & 0x3f);
6413 break;
6414 case 5:
6415 unicode = (string[0] & 0x0f) << 24;
6416 unicode += ((string[1] & 0x3f) << 18);
6417 unicode += ((string[2] & 0x3f) << 12);
6418 unicode += ((string[3] & 0x3f) << 6);
6419 unicode += (string[4] & 0x3f);
6420 break;
6421 case 6:
6422 unicode = (string[0] & 0x01) << 30;
6423 unicode += ((string[1] & 0x3f) << 24);
6424 unicode += ((string[2] & 0x3f) << 18);
6425 unicode += ((string[3] & 0x3f) << 12);
6426 unicode += ((string[4] & 0x3f) << 6);
6427 unicode += (string[5] & 0x3f);
6428 break;
6429 default:
6430 die("Invalid Unicode length");
6431 }
6433 /* Invalid characters could return the special 0xfffd value but NUL
6434 * should be just as good. */
6435 return unicode > 0xffff ? 0 : unicode;
6436 }
6438 /* Calculates how much of string can be shown within the given maximum width
6439 * and sets trimmed parameter to non-zero value if all of string could not be
6440 * shown. If the reserve flag is TRUE, it will reserve at least one
6441 * trailing character, which can be useful when drawing a delimiter.
6442 *
6443 * Returns the number of bytes to output from string to satisfy max_width. */
6444 static size_t
6445 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6446 {
6447 const char *string = *start;
6448 const char *end = strchr(string, '\0');
6449 unsigned char last_bytes = 0;
6450 size_t last_ucwidth = 0;
6452 *width = 0;
6453 *trimmed = 0;
6455 while (string < end) {
6456 int c = *(unsigned char *) string;
6457 unsigned char bytes = utf8_bytes[c];
6458 size_t ucwidth;
6459 unsigned long unicode;
6461 if (string + bytes > end)
6462 break;
6464 /* Change representation to figure out whether
6465 * it is a single- or double-width character. */
6467 unicode = utf8_to_unicode(string, bytes);
6468 /* FIXME: Graceful handling of invalid Unicode character. */
6469 if (!unicode)
6470 break;
6472 ucwidth = unicode_width(unicode);
6473 if (skip > 0) {
6474 skip -= ucwidth <= skip ? ucwidth : skip;
6475 *start += bytes;
6476 }
6477 *width += ucwidth;
6478 if (*width > max_width) {
6479 *trimmed = 1;
6480 *width -= ucwidth;
6481 if (reserve && *width == max_width) {
6482 string -= last_bytes;
6483 *width -= last_ucwidth;
6484 }
6485 break;
6486 }
6488 string += bytes;
6489 last_bytes = ucwidth ? bytes : 0;
6490 last_ucwidth = ucwidth;
6491 }
6493 return string - *start;
6494 }
6497 /*
6498 * Status management
6499 */
6501 /* Whether or not the curses interface has been initialized. */
6502 static bool cursed = FALSE;
6504 /* Terminal hacks and workarounds. */
6505 static bool use_scroll_redrawwin;
6506 static bool use_scroll_status_wclear;
6508 /* The status window is used for polling keystrokes. */
6509 static WINDOW *status_win;
6511 /* Reading from the prompt? */
6512 static bool input_mode = FALSE;
6514 static bool status_empty = FALSE;
6516 /* Update status and title window. */
6517 static void
6518 report(const char *msg, ...)
6519 {
6520 struct view *view = display[current_view];
6522 if (input_mode)
6523 return;
6525 if (!view) {
6526 char buf[SIZEOF_STR];
6527 va_list args;
6529 va_start(args, msg);
6530 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6531 buf[sizeof(buf) - 1] = 0;
6532 buf[sizeof(buf) - 2] = '.';
6533 buf[sizeof(buf) - 3] = '.';
6534 buf[sizeof(buf) - 4] = '.';
6535 }
6536 va_end(args);
6537 die("%s", buf);
6538 }
6540 if (!status_empty || *msg) {
6541 va_list args;
6543 va_start(args, msg);
6545 wmove(status_win, 0, 0);
6546 if (view->has_scrolled && use_scroll_status_wclear)
6547 wclear(status_win);
6548 if (*msg) {
6549 vwprintw(status_win, msg, args);
6550 status_empty = FALSE;
6551 } else {
6552 status_empty = TRUE;
6553 }
6554 wclrtoeol(status_win);
6555 wnoutrefresh(status_win);
6557 va_end(args);
6558 }
6560 update_view_title(view);
6561 }
6563 /* Controls when nodelay should be in effect when polling user input. */
6564 static void
6565 set_nonblocking_input(bool loading)
6566 {
6567 static unsigned int loading_views;
6569 if ((loading == FALSE && loading_views-- == 1) ||
6570 (loading == TRUE && loading_views++ == 0))
6571 nodelay(status_win, loading);
6572 }
6574 static void
6575 init_display(void)
6576 {
6577 const char *term;
6578 int x, y;
6580 /* Initialize the curses library */
6581 if (isatty(STDIN_FILENO)) {
6582 cursed = !!initscr();
6583 opt_tty = stdin;
6584 } else {
6585 /* Leave stdin and stdout alone when acting as a pager. */
6586 opt_tty = fopen("/dev/tty", "r+");
6587 if (!opt_tty)
6588 die("Failed to open /dev/tty");
6589 cursed = !!newterm(NULL, opt_tty, opt_tty);
6590 }
6592 if (!cursed)
6593 die("Failed to initialize curses");
6595 nonl(); /* Disable conversion and detect newlines from input. */
6596 cbreak(); /* Take input chars one at a time, no wait for \n */
6597 noecho(); /* Don't echo input */
6598 leaveok(stdscr, FALSE);
6600 if (has_colors())
6601 init_colors();
6603 getmaxyx(stdscr, y, x);
6604 status_win = newwin(1, 0, y - 1, 0);
6605 if (!status_win)
6606 die("Failed to create status window");
6608 /* Enable keyboard mapping */
6609 keypad(status_win, TRUE);
6610 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6612 TABSIZE = opt_tab_size;
6613 if (opt_line_graphics) {
6614 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6615 }
6617 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6618 if (term && !strcmp(term, "gnome-terminal")) {
6619 /* In the gnome-terminal-emulator, the message from
6620 * scrolling up one line when impossible followed by
6621 * scrolling down one line causes corruption of the
6622 * status line. This is fixed by calling wclear. */
6623 use_scroll_status_wclear = TRUE;
6624 use_scroll_redrawwin = FALSE;
6626 } else if (term && !strcmp(term, "xrvt-xpm")) {
6627 /* No problems with full optimizations in xrvt-(unicode)
6628 * and aterm. */
6629 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6631 } else {
6632 /* When scrolling in (u)xterm the last line in the
6633 * scrolling direction will update slowly. */
6634 use_scroll_redrawwin = TRUE;
6635 use_scroll_status_wclear = FALSE;
6636 }
6637 }
6639 static int
6640 get_input(int prompt_position)
6641 {
6642 struct view *view;
6643 int i, key, cursor_y, cursor_x;
6645 if (prompt_position)
6646 input_mode = TRUE;
6648 while (TRUE) {
6649 foreach_view (view, i) {
6650 update_view(view);
6651 if (view_is_displayed(view) && view->has_scrolled &&
6652 use_scroll_redrawwin)
6653 redrawwin(view->win);
6654 view->has_scrolled = FALSE;
6655 }
6657 /* Update the cursor position. */
6658 if (prompt_position) {
6659 getbegyx(status_win, cursor_y, cursor_x);
6660 cursor_x = prompt_position;
6661 } else {
6662 view = display[current_view];
6663 getbegyx(view->win, cursor_y, cursor_x);
6664 cursor_x = view->width - 1;
6665 cursor_y += view->lineno - view->offset;
6666 }
6667 setsyx(cursor_y, cursor_x);
6669 /* Refresh, accept single keystroke of input */
6670 doupdate();
6671 key = wgetch(status_win);
6673 /* wgetch() with nodelay() enabled returns ERR when
6674 * there's no input. */
6675 if (key == ERR) {
6677 } else if (key == KEY_RESIZE) {
6678 int height, width;
6680 getmaxyx(stdscr, height, width);
6682 wresize(status_win, 1, width);
6683 mvwin(status_win, height - 1, 0);
6684 wnoutrefresh(status_win);
6685 resize_display();
6686 redraw_display(TRUE);
6688 } else {
6689 input_mode = FALSE;
6690 return key;
6691 }
6692 }
6693 }
6695 static char *
6696 prompt_input(const char *prompt, input_handler handler, void *data)
6697 {
6698 enum input_status status = INPUT_OK;
6699 static char buf[SIZEOF_STR];
6700 size_t pos = 0;
6702 buf[pos] = 0;
6704 while (status == INPUT_OK || status == INPUT_SKIP) {
6705 int key;
6707 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6708 wclrtoeol(status_win);
6710 key = get_input(pos + 1);
6711 switch (key) {
6712 case KEY_RETURN:
6713 case KEY_ENTER:
6714 case '\n':
6715 status = pos ? INPUT_STOP : INPUT_CANCEL;
6716 break;
6718 case KEY_BACKSPACE:
6719 if (pos > 0)
6720 buf[--pos] = 0;
6721 else
6722 status = INPUT_CANCEL;
6723 break;
6725 case KEY_ESC:
6726 status = INPUT_CANCEL;
6727 break;
6729 default:
6730 if (pos >= sizeof(buf)) {
6731 report("Input string too long");
6732 return NULL;
6733 }
6735 status = handler(data, buf, key);
6736 if (status == INPUT_OK)
6737 buf[pos++] = (char) key;
6738 }
6739 }
6741 /* Clear the status window */
6742 status_empty = FALSE;
6743 report("");
6745 if (status == INPUT_CANCEL)
6746 return NULL;
6748 buf[pos++] = 0;
6750 return buf;
6751 }
6753 static enum input_status
6754 prompt_yesno_handler(void *data, char *buf, int c)
6755 {
6756 if (c == 'y' || c == 'Y')
6757 return INPUT_STOP;
6758 if (c == 'n' || c == 'N')
6759 return INPUT_CANCEL;
6760 return INPUT_SKIP;
6761 }
6763 static bool
6764 prompt_yesno(const char *prompt)
6765 {
6766 char prompt2[SIZEOF_STR];
6768 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6769 return FALSE;
6771 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6772 }
6774 static enum input_status
6775 read_prompt_handler(void *data, char *buf, int c)
6776 {
6777 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6778 }
6780 static char *
6781 read_prompt(const char *prompt)
6782 {
6783 return prompt_input(prompt, read_prompt_handler, NULL);
6784 }
6786 /*
6787 * Repository properties
6788 */
6790 static struct ref *refs = NULL;
6791 static size_t refs_size = 0;
6793 /* Id <-> ref store */
6794 static struct ref ***id_refs = NULL;
6795 static size_t id_refs_size = 0;
6797 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6798 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6799 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6801 static int
6802 compare_refs(const void *ref1_, const void *ref2_)
6803 {
6804 const struct ref *ref1 = *(const struct ref **)ref1_;
6805 const struct ref *ref2 = *(const struct ref **)ref2_;
6807 if (ref1->tag != ref2->tag)
6808 return ref2->tag - ref1->tag;
6809 if (ref1->ltag != ref2->ltag)
6810 return ref2->ltag - ref2->ltag;
6811 if (ref1->head != ref2->head)
6812 return ref2->head - ref1->head;
6813 if (ref1->tracked != ref2->tracked)
6814 return ref2->tracked - ref1->tracked;
6815 if (ref1->remote != ref2->remote)
6816 return ref2->remote - ref1->remote;
6817 return strcmp(ref1->name, ref2->name);
6818 }
6820 static void
6821 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6822 {
6823 size_t i;
6825 for (i = 0; i < refs_size; i++)
6826 if (!visitor(data, &refs[i]))
6827 break;
6828 }
6830 static struct ref **
6831 get_refs(const char *id)
6832 {
6833 struct ref **ref_list = NULL;
6834 size_t ref_list_size = 0;
6835 size_t i;
6837 for (i = 0; i < id_refs_size; i++)
6838 if (!strcmp(id, id_refs[i][0]->id))
6839 return id_refs[i];
6841 if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
6842 return NULL;
6844 for (i = 0; i < refs_size; i++) {
6845 if (strcmp(id, refs[i].id))
6846 continue;
6848 if (!realloc_refs_list(&ref_list, ref_list_size, 1))
6849 break;
6851 ref_list[ref_list_size] = &refs[i];
6852 /* XXX: The properties of the commit chains ensures that we can
6853 * safely modify the shared ref. The repo references will
6854 * always be similar for the same id. */
6855 ref_list[ref_list_size]->next = 1;
6856 ref_list_size++;
6857 }
6859 if (ref_list) {
6860 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6861 ref_list[ref_list_size - 1]->next = 0;
6862 id_refs[id_refs_size++] = ref_list;
6863 }
6865 return ref_list;
6866 }
6868 static int
6869 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6870 {
6871 struct ref *ref;
6872 bool tag = FALSE;
6873 bool ltag = FALSE;
6874 bool remote = FALSE;
6875 bool tracked = FALSE;
6876 bool check_replace = FALSE;
6877 bool head = FALSE;
6879 if (!prefixcmp(name, "refs/tags/")) {
6880 if (!suffixcmp(name, namelen, "^{}")) {
6881 namelen -= 3;
6882 name[namelen] = 0;
6883 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6884 check_replace = TRUE;
6885 } else {
6886 ltag = TRUE;
6887 }
6889 tag = TRUE;
6890 namelen -= STRING_SIZE("refs/tags/");
6891 name += STRING_SIZE("refs/tags/");
6893 } else if (!prefixcmp(name, "refs/remotes/")) {
6894 remote = TRUE;
6895 namelen -= STRING_SIZE("refs/remotes/");
6896 name += STRING_SIZE("refs/remotes/");
6897 tracked = !strcmp(opt_remote, name);
6899 } else if (!prefixcmp(name, "refs/heads/")) {
6900 namelen -= STRING_SIZE("refs/heads/");
6901 name += STRING_SIZE("refs/heads/");
6902 head = !strncmp(opt_head, name, namelen);
6904 } else if (!strcmp(name, "HEAD")) {
6905 string_ncopy(opt_head_rev, id, idlen);
6906 return OK;
6907 }
6909 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6910 /* it's an annotated tag, replace the previous SHA1 with the
6911 * resolved commit id; relies on the fact git-ls-remote lists
6912 * the commit id of an annotated tag right before the commit id
6913 * it points to. */
6914 refs[refs_size - 1].ltag = ltag;
6915 string_copy_rev(refs[refs_size - 1].id, id);
6917 return OK;
6918 }
6920 if (!realloc_refs(&refs, refs_size, 1))
6921 return ERR;
6923 ref = &refs[refs_size++];
6924 ref->name = malloc(namelen + 1);
6925 if (!ref->name)
6926 return ERR;
6928 strncpy(ref->name, name, namelen);
6929 ref->name[namelen] = 0;
6930 ref->head = head;
6931 ref->tag = tag;
6932 ref->ltag = ltag;
6933 ref->remote = remote;
6934 ref->tracked = tracked;
6935 string_copy_rev(ref->id, id);
6937 return OK;
6938 }
6940 static int
6941 load_refs(void)
6942 {
6943 const char *head_argv[] = {
6944 "git", "symbolic-ref", "HEAD", NULL
6945 };
6946 static const char *ls_remote_argv[SIZEOF_ARG] = {
6947 "git", "ls-remote", opt_git_dir, NULL
6948 };
6949 static bool init = FALSE;
6951 if (!init) {
6952 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6953 init = TRUE;
6954 }
6956 if (!*opt_git_dir)
6957 return OK;
6959 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6960 !prefixcmp(opt_head, "refs/heads/")) {
6961 char *offset = opt_head + STRING_SIZE("refs/heads/");
6963 memmove(opt_head, offset, strlen(offset) + 1);
6964 }
6966 while (refs_size > 0)
6967 free(refs[--refs_size].name);
6968 while (id_refs_size > 0)
6969 free(id_refs[--id_refs_size]);
6971 return run_io_load(ls_remote_argv, "\t", read_ref);
6972 }
6974 static void
6975 set_remote_branch(const char *name, const char *value, size_t valuelen)
6976 {
6977 if (!strcmp(name, ".remote")) {
6978 string_ncopy(opt_remote, value, valuelen);
6980 } else if (*opt_remote && !strcmp(name, ".merge")) {
6981 size_t from = strlen(opt_remote);
6983 if (!prefixcmp(value, "refs/heads/"))
6984 value += STRING_SIZE("refs/heads/");
6986 if (!string_format_from(opt_remote, &from, "/%s", value))
6987 opt_remote[0] = 0;
6988 }
6989 }
6991 static void
6992 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6993 {
6994 const char *argv[SIZEOF_ARG] = { name, "=" };
6995 int argc = 1 + (cmd == option_set_command);
6996 int error = ERR;
6998 if (!argv_from_string(argv, &argc, value))
6999 config_msg = "Too many option arguments";
7000 else
7001 error = cmd(argc, argv);
7003 if (error == ERR)
7004 warn("Option 'tig.%s': %s", name, config_msg);
7005 }
7007 static bool
7008 set_environment_variable(const char *name, const char *value)
7009 {
7010 size_t len = strlen(name) + 1 + strlen(value) + 1;
7011 char *env = malloc(len);
7013 if (env &&
7014 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7015 putenv(env) == 0)
7016 return TRUE;
7017 free(env);
7018 return FALSE;
7019 }
7021 static void
7022 set_work_tree(const char *value)
7023 {
7024 char cwd[SIZEOF_STR];
7026 if (!getcwd(cwd, sizeof(cwd)))
7027 die("Failed to get cwd path: %s", strerror(errno));
7028 if (chdir(opt_git_dir) < 0)
7029 die("Failed to chdir(%s): %s", strerror(errno));
7030 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7031 die("Failed to get git path: %s", strerror(errno));
7032 if (chdir(cwd) < 0)
7033 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7034 if (chdir(value) < 0)
7035 die("Failed to chdir(%s): %s", value, strerror(errno));
7036 if (!getcwd(cwd, sizeof(cwd)))
7037 die("Failed to get cwd path: %s", strerror(errno));
7038 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7039 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7040 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7041 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7042 opt_is_inside_work_tree = TRUE;
7043 }
7045 static int
7046 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7047 {
7048 if (!strcmp(name, "i18n.commitencoding"))
7049 string_ncopy(opt_encoding, value, valuelen);
7051 else if (!strcmp(name, "core.editor"))
7052 string_ncopy(opt_editor, value, valuelen);
7054 else if (!strcmp(name, "core.worktree"))
7055 set_work_tree(value);
7057 else if (!prefixcmp(name, "tig.color."))
7058 set_repo_config_option(name + 10, value, option_color_command);
7060 else if (!prefixcmp(name, "tig.bind."))
7061 set_repo_config_option(name + 9, value, option_bind_command);
7063 else if (!prefixcmp(name, "tig."))
7064 set_repo_config_option(name + 4, value, option_set_command);
7066 else if (*opt_head && !prefixcmp(name, "branch.") &&
7067 !strncmp(name + 7, opt_head, strlen(opt_head)))
7068 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7070 return OK;
7071 }
7073 static int
7074 load_git_config(void)
7075 {
7076 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7078 return run_io_load(config_list_argv, "=", read_repo_config_option);
7079 }
7081 static int
7082 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7083 {
7084 if (!opt_git_dir[0]) {
7085 string_ncopy(opt_git_dir, name, namelen);
7087 } else if (opt_is_inside_work_tree == -1) {
7088 /* This can be 3 different values depending on the
7089 * version of git being used. If git-rev-parse does not
7090 * understand --is-inside-work-tree it will simply echo
7091 * the option else either "true" or "false" is printed.
7092 * Default to true for the unknown case. */
7093 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7095 } else if (*name == '.') {
7096 string_ncopy(opt_cdup, name, namelen);
7098 } else {
7099 string_ncopy(opt_prefix, name, namelen);
7100 }
7102 return OK;
7103 }
7105 static int
7106 load_repo_info(void)
7107 {
7108 const char *rev_parse_argv[] = {
7109 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7110 "--show-cdup", "--show-prefix", NULL
7111 };
7113 return run_io_load(rev_parse_argv, "=", read_repo_info);
7114 }
7117 /*
7118 * Main
7119 */
7121 static const char usage[] =
7122 "tig " TIG_VERSION " (" __DATE__ ")\n"
7123 "\n"
7124 "Usage: tig [options] [revs] [--] [paths]\n"
7125 " or: tig show [options] [revs] [--] [paths]\n"
7126 " or: tig blame [rev] path\n"
7127 " or: tig status\n"
7128 " or: tig < [git command output]\n"
7129 "\n"
7130 "Options:\n"
7131 " -v, --version Show version and exit\n"
7132 " -h, --help Show help message and exit";
7134 static void __NORETURN
7135 quit(int sig)
7136 {
7137 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7138 if (cursed)
7139 endwin();
7140 exit(0);
7141 }
7143 static void __NORETURN
7144 die(const char *err, ...)
7145 {
7146 va_list args;
7148 endwin();
7150 va_start(args, err);
7151 fputs("tig: ", stderr);
7152 vfprintf(stderr, err, args);
7153 fputs("\n", stderr);
7154 va_end(args);
7156 exit(1);
7157 }
7159 static void
7160 warn(const char *msg, ...)
7161 {
7162 va_list args;
7164 va_start(args, msg);
7165 fputs("tig warning: ", stderr);
7166 vfprintf(stderr, msg, args);
7167 fputs("\n", stderr);
7168 va_end(args);
7169 }
7171 static enum request
7172 parse_options(int argc, const char *argv[])
7173 {
7174 enum request request = REQ_VIEW_MAIN;
7175 const char *subcommand;
7176 bool seen_dashdash = FALSE;
7177 /* XXX: This is vulnerable to the user overriding options
7178 * required for the main view parser. */
7179 const char *custom_argv[SIZEOF_ARG] = {
7180 "git", "log", "--no-color", "--pretty=raw", "--parents",
7181 "--topo-order", NULL
7182 };
7183 int i, j = 6;
7185 if (!isatty(STDIN_FILENO)) {
7186 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7187 return REQ_VIEW_PAGER;
7188 }
7190 if (argc <= 1)
7191 return REQ_NONE;
7193 subcommand = argv[1];
7194 if (!strcmp(subcommand, "status")) {
7195 if (argc > 2)
7196 warn("ignoring arguments after `%s'", subcommand);
7197 return REQ_VIEW_STATUS;
7199 } else if (!strcmp(subcommand, "blame")) {
7200 if (argc <= 2 || argc > 4)
7201 die("invalid number of options to blame\n\n%s", usage);
7203 i = 2;
7204 if (argc == 4) {
7205 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7206 i++;
7207 }
7209 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7210 return REQ_VIEW_BLAME;
7212 } else if (!strcmp(subcommand, "show")) {
7213 request = REQ_VIEW_DIFF;
7215 } else {
7216 subcommand = NULL;
7217 }
7219 if (subcommand) {
7220 custom_argv[1] = subcommand;
7221 j = 2;
7222 }
7224 for (i = 1 + !!subcommand; i < argc; i++) {
7225 const char *opt = argv[i];
7227 if (seen_dashdash || !strcmp(opt, "--")) {
7228 seen_dashdash = TRUE;
7230 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7231 printf("tig version %s\n", TIG_VERSION);
7232 quit(0);
7234 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7235 printf("%s\n", usage);
7236 quit(0);
7237 }
7239 custom_argv[j++] = opt;
7240 if (j >= ARRAY_SIZE(custom_argv))
7241 die("command too long");
7242 }
7244 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7245 die("Failed to format arguments");
7247 return request;
7248 }
7250 int
7251 main(int argc, const char *argv[])
7252 {
7253 enum request request = parse_options(argc, argv);
7254 struct view *view;
7255 size_t i;
7257 signal(SIGINT, quit);
7258 signal(SIGPIPE, SIG_IGN);
7260 if (setlocale(LC_ALL, "")) {
7261 char *codeset = nl_langinfo(CODESET);
7263 string_ncopy(opt_codeset, codeset, strlen(codeset));
7264 }
7266 if (load_repo_info() == ERR)
7267 die("Failed to load repo info.");
7269 if (load_options() == ERR)
7270 die("Failed to load user config.");
7272 if (load_git_config() == ERR)
7273 die("Failed to load repo config.");
7275 /* Require a git repository unless when running in pager mode. */
7276 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7277 die("Not a git repository");
7279 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7280 opt_utf8 = FALSE;
7282 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7283 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7284 if (opt_iconv == ICONV_NONE)
7285 die("Failed to initialize character set conversion");
7286 }
7288 if (load_refs() == ERR)
7289 die("Failed to load refs.");
7291 foreach_view (view, i)
7292 argv_from_env(view->ops->argv, view->cmd_env);
7294 init_display();
7296 if (request != REQ_NONE)
7297 open_view(NULL, request, OPEN_PREPARED);
7298 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7300 while (view_driver(display[current_view], request)) {
7301 int key = get_input(0);
7303 view = display[current_view];
7304 request = get_keybinding(view->keymap, key);
7306 /* Some low-level request handling. This keeps access to
7307 * status_win restricted. */
7308 switch (request) {
7309 case REQ_PROMPT:
7310 {
7311 char *cmd = read_prompt(":");
7313 if (cmd && isdigit(*cmd)) {
7314 int lineno = view->lineno + 1;
7316 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7317 select_view_line(view, lineno - 1);
7318 report("");
7319 } else {
7320 report("Unable to parse '%s' as a line number", cmd);
7321 }
7323 } else if (cmd) {
7324 struct view *next = VIEW(REQ_VIEW_PAGER);
7325 const char *argv[SIZEOF_ARG] = { "git" };
7326 int argc = 1;
7328 /* When running random commands, initially show the
7329 * command in the title. However, it maybe later be
7330 * overwritten if a commit line is selected. */
7331 string_ncopy(next->ref, cmd, strlen(cmd));
7333 if (!argv_from_string(argv, &argc, cmd)) {
7334 report("Too many arguments");
7335 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7336 report("Failed to format command");
7337 } else {
7338 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7339 }
7340 }
7342 request = REQ_NONE;
7343 break;
7344 }
7345 case REQ_SEARCH:
7346 case REQ_SEARCH_BACK:
7347 {
7348 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7349 char *search = read_prompt(prompt);
7351 if (search)
7352 string_ncopy(opt_search, search, strlen(search));
7353 else if (*opt_search)
7354 request = request == REQ_SEARCH ?
7355 REQ_FIND_NEXT :
7356 REQ_FIND_PREV;
7357 else
7358 request = REQ_NONE;
7359 break;
7360 }
7361 default:
7362 break;
7363 }
7364 }
7366 quit(0);
7368 return 0;
7369 }