7a996dca08ca43e21b13428ebfdb3f4d0109cd42
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS 20
107 #define ID_COLS 8
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
112 #define TAB_SIZE 8
114 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
116 #define NULL_ID "0000000000000000000000000000000000000000"
118 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ASCII-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB '\t'
126 #define KEY_RETURN '\r'
127 #define KEY_ESC 27
130 struct ref {
131 char id[SIZEOF_REV]; /* Commit SHA1 ID */
132 unsigned int head:1; /* Is it the current HEAD? */
133 unsigned int tag:1; /* Is it a tag? */
134 unsigned int ltag:1; /* If so, is the tag local? */
135 unsigned int remote:1; /* Is it a remote ref? */
136 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137 char name[1]; /* Ref name; tag or head names are shortened. */
138 };
140 struct ref_list {
141 char id[SIZEOF_REV]; /* Commit SHA1 ID */
142 size_t size; /* Number of refs. */
143 struct ref **refs; /* References for this ID. */
144 };
146 static struct ref_list *get_ref_list(const char *id);
147 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
148 static int load_refs(void);
150 enum format_flags {
151 FORMAT_ALL, /* Perform replacement in all arguments. */
152 FORMAT_DASH, /* Perform replacement up until "--". */
153 FORMAT_NONE /* No replacement should be performed. */
154 };
156 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
158 enum input_status {
159 INPUT_OK,
160 INPUT_SKIP,
161 INPUT_STOP,
162 INPUT_CANCEL
163 };
165 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
167 static char *prompt_input(const char *prompt, input_handler handler, void *data);
168 static bool prompt_yesno(const char *prompt);
170 /*
171 * Allocation helpers ... Entering macro hell to never be seen again.
172 */
174 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
175 static type * \
176 name(type **mem, size_t size, size_t increase) \
177 { \
178 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
179 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
180 type *tmp = *mem; \
181 \
182 if (mem == NULL || num_chunks != num_chunks_new) { \
183 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
184 if (tmp) \
185 *mem = tmp; \
186 } \
187 \
188 return tmp; \
189 }
191 /*
192 * String helpers
193 */
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
197 {
198 if (srclen > dstlen - 1)
199 srclen = dstlen - 1;
201 strncpy(dst, src, srclen);
202 dst[srclen] = 0;
203 }
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211 string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static void
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
221 {
222 size_t size, pos;
224 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225 if (src[pos] == '\t') {
226 size_t expanded = tabsize - (size % tabsize);
228 if (expanded + size >= dstlen - 1)
229 expanded = dstlen - size - 1;
230 memcpy(dst + size, " ", expanded);
231 size += expanded;
232 } else {
233 dst[size++] = src[pos];
234 }
235 }
237 dst[size] = 0;
238 }
240 static char *
241 chomp_string(char *name)
242 {
243 int namelen;
245 while (isspace(*name))
246 name++;
248 namelen = strlen(name) - 1;
249 while (namelen > 0 && isspace(name[namelen]))
250 name[namelen--] = 0;
252 return name;
253 }
255 static bool
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
257 {
258 va_list args;
259 size_t pos = bufpos ? *bufpos : 0;
261 va_start(args, fmt);
262 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263 va_end(args);
265 if (bufpos)
266 *bufpos = pos;
268 return pos >= bufsize ? FALSE : TRUE;
269 }
271 #define string_format(buf, fmt, args...) \
272 string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275 string_nformat(buf, sizeof(buf), from, fmt, args)
277 static int
278 string_enum_compare(const char *str1, const char *str2, int len)
279 {
280 size_t i;
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284 /* Diff-Header == DIFF_HEADER */
285 for (i = 0; i < len; i++) {
286 if (toupper(str1[i]) == toupper(str2[i]))
287 continue;
289 if (string_enum_sep(str1[i]) &&
290 string_enum_sep(str2[i]))
291 continue;
293 return str1[i] - str2[i];
294 }
296 return 0;
297 }
299 struct enum_map {
300 const char *name;
301 int namelen;
302 int value;
303 };
305 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
307 static bool
308 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
309 {
310 size_t namelen = strlen(name);
311 int i;
313 for (i = 0; i < map_size; i++)
314 if (namelen == map[i].namelen &&
315 !string_enum_compare(name, map[i].name, namelen)) {
316 *value = map[i].value;
317 return TRUE;
318 }
320 return FALSE;
321 }
323 #define map_enum(attr, map, name) \
324 map_enum_do(map, ARRAY_SIZE(map), attr, name)
326 #define prefixcmp(str1, str2) \
327 strncmp(str1, str2, STRING_SIZE(str2))
329 static inline int
330 suffixcmp(const char *str, int slen, const char *suffix)
331 {
332 size_t len = slen >= 0 ? slen : strlen(str);
333 size_t suffixlen = strlen(suffix);
335 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
336 }
339 static const char *
340 mkdate(const time_t *time)
341 {
342 static char buf[DATE_COLS + 1];
343 struct tm tm;
345 gmtime_r(time, &tm);
346 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
347 }
350 static bool
351 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
352 {
353 int valuelen;
355 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
356 bool advance = cmd[valuelen] != 0;
358 cmd[valuelen] = 0;
359 argv[(*argc)++] = chomp_string(cmd);
360 cmd = chomp_string(cmd + valuelen + advance);
361 }
363 if (*argc < SIZEOF_ARG)
364 argv[*argc] = NULL;
365 return *argc < SIZEOF_ARG;
366 }
368 static void
369 argv_from_env(const char **argv, const char *name)
370 {
371 char *env = argv ? getenv(name) : NULL;
372 int argc = 0;
374 if (env && *env)
375 env = strdup(env);
376 if (env && !argv_from_string(argv, &argc, env))
377 die("Too many arguments in the `%s` environment variable", name);
378 }
381 /*
382 * Executing external commands.
383 */
385 enum io_type {
386 IO_FD, /* File descriptor based IO. */
387 IO_BG, /* Execute command in the background. */
388 IO_FG, /* Execute command with same std{in,out,err}. */
389 IO_RD, /* Read only fork+exec IO. */
390 IO_WR, /* Write only fork+exec IO. */
391 IO_AP, /* Append fork+exec output to file. */
392 };
394 struct io {
395 enum io_type type; /* The requested type of pipe. */
396 const char *dir; /* Directory from which to execute. */
397 pid_t pid; /* Pipe for reading or writing. */
398 int pipe; /* Pipe end for reading or writing. */
399 int error; /* Error status. */
400 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
401 char *buf; /* Read buffer. */
402 size_t bufalloc; /* Allocated buffer size. */
403 size_t bufsize; /* Buffer content size. */
404 char *bufpos; /* Current buffer position. */
405 unsigned int eof:1; /* Has end of file been reached. */
406 };
408 static void
409 reset_io(struct io *io)
410 {
411 io->pipe = -1;
412 io->pid = 0;
413 io->buf = io->bufpos = NULL;
414 io->bufalloc = io->bufsize = 0;
415 io->error = 0;
416 io->eof = 0;
417 }
419 static void
420 init_io(struct io *io, const char *dir, enum io_type type)
421 {
422 reset_io(io);
423 io->type = type;
424 io->dir = dir;
425 }
427 static bool
428 init_io_rd(struct io *io, const char *argv[], const char *dir,
429 enum format_flags flags)
430 {
431 init_io(io, dir, IO_RD);
432 return format_argv(io->argv, argv, flags);
433 }
435 static bool
436 io_open(struct io *io, const char *name)
437 {
438 init_io(io, NULL, IO_FD);
439 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
440 if (io->pipe == -1)
441 io->error = errno;
442 return io->pipe != -1;
443 }
445 static bool
446 kill_io(struct io *io)
447 {
448 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
449 }
451 static bool
452 done_io(struct io *io)
453 {
454 pid_t pid = io->pid;
456 if (io->pipe != -1)
457 close(io->pipe);
458 free(io->buf);
459 reset_io(io);
461 while (pid > 0) {
462 int status;
463 pid_t waiting = waitpid(pid, &status, 0);
465 if (waiting < 0) {
466 if (errno == EINTR)
467 continue;
468 report("waitpid failed (%s)", strerror(errno));
469 return FALSE;
470 }
472 return waiting == pid &&
473 !WIFSIGNALED(status) &&
474 WIFEXITED(status) &&
475 !WEXITSTATUS(status);
476 }
478 return TRUE;
479 }
481 static bool
482 start_io(struct io *io)
483 {
484 int pipefds[2] = { -1, -1 };
486 if (io->type == IO_FD)
487 return TRUE;
489 if ((io->type == IO_RD || io->type == IO_WR) &&
490 pipe(pipefds) < 0)
491 return FALSE;
492 else if (io->type == IO_AP)
493 pipefds[1] = io->pipe;
495 if ((io->pid = fork())) {
496 if (pipefds[!(io->type == IO_WR)] != -1)
497 close(pipefds[!(io->type == IO_WR)]);
498 if (io->pid != -1) {
499 io->pipe = pipefds[!!(io->type == IO_WR)];
500 return TRUE;
501 }
503 } else {
504 if (io->type != IO_FG) {
505 int devnull = open("/dev/null", O_RDWR);
506 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
507 int writefd = (io->type == IO_RD || io->type == IO_AP)
508 ? pipefds[1] : devnull;
510 dup2(readfd, STDIN_FILENO);
511 dup2(writefd, STDOUT_FILENO);
512 dup2(devnull, STDERR_FILENO);
514 close(devnull);
515 if (pipefds[0] != -1)
516 close(pipefds[0]);
517 if (pipefds[1] != -1)
518 close(pipefds[1]);
519 }
521 if (io->dir && *io->dir && chdir(io->dir) == -1)
522 die("Failed to change directory: %s", strerror(errno));
524 execvp(io->argv[0], (char *const*) io->argv);
525 die("Failed to execute program: %s", strerror(errno));
526 }
528 if (pipefds[!!(io->type == IO_WR)] != -1)
529 close(pipefds[!!(io->type == IO_WR)]);
530 return FALSE;
531 }
533 static bool
534 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
535 {
536 init_io(io, dir, type);
537 if (!format_argv(io->argv, argv, FORMAT_NONE))
538 return FALSE;
539 return start_io(io);
540 }
542 static int
543 run_io_do(struct io *io)
544 {
545 return start_io(io) && done_io(io);
546 }
548 static int
549 run_io_bg(const char **argv)
550 {
551 struct io io = {};
553 init_io(&io, NULL, IO_BG);
554 if (!format_argv(io.argv, argv, FORMAT_NONE))
555 return FALSE;
556 return run_io_do(&io);
557 }
559 static bool
560 run_io_fg(const char **argv, const char *dir)
561 {
562 struct io io = {};
564 init_io(&io, dir, IO_FG);
565 if (!format_argv(io.argv, argv, FORMAT_NONE))
566 return FALSE;
567 return run_io_do(&io);
568 }
570 static bool
571 run_io_append(const char **argv, enum format_flags flags, int fd)
572 {
573 struct io io = {};
575 init_io(&io, NULL, IO_AP);
576 io.pipe = fd;
577 if (format_argv(io.argv, argv, flags))
578 return run_io_do(&io);
579 close(fd);
580 return FALSE;
581 }
583 static bool
584 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
585 {
586 return init_io_rd(io, argv, NULL, flags) && start_io(io);
587 }
589 static bool
590 io_eof(struct io *io)
591 {
592 return io->eof;
593 }
595 static int
596 io_error(struct io *io)
597 {
598 return io->error;
599 }
601 static char *
602 io_strerror(struct io *io)
603 {
604 return strerror(io->error);
605 }
607 static bool
608 io_can_read(struct io *io)
609 {
610 struct timeval tv = { 0, 500 };
611 fd_set fds;
613 FD_ZERO(&fds);
614 FD_SET(io->pipe, &fds);
616 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
617 }
619 static ssize_t
620 io_read(struct io *io, void *buf, size_t bufsize)
621 {
622 do {
623 ssize_t readsize = read(io->pipe, buf, bufsize);
625 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
626 continue;
627 else if (readsize == -1)
628 io->error = errno;
629 else if (readsize == 0)
630 io->eof = 1;
631 return readsize;
632 } while (1);
633 }
635 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
637 static char *
638 io_get(struct io *io, int c, bool can_read)
639 {
640 char *eol;
641 ssize_t readsize;
643 while (TRUE) {
644 if (io->bufsize > 0) {
645 eol = memchr(io->bufpos, c, io->bufsize);
646 if (eol) {
647 char *line = io->bufpos;
649 *eol = 0;
650 io->bufpos = eol + 1;
651 io->bufsize -= io->bufpos - line;
652 return line;
653 }
654 }
656 if (io_eof(io)) {
657 if (io->bufsize) {
658 io->bufpos[io->bufsize] = 0;
659 io->bufsize = 0;
660 return io->bufpos;
661 }
662 return NULL;
663 }
665 if (!can_read)
666 return NULL;
668 if (io->bufsize > 0 && io->bufpos > io->buf)
669 memmove(io->buf, io->bufpos, io->bufsize);
671 if (io->bufalloc == io->bufsize) {
672 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
673 return NULL;
674 io->bufalloc += BUFSIZ;
675 }
677 io->bufpos = io->buf;
678 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
679 if (io_error(io))
680 return NULL;
681 io->bufsize += readsize;
682 }
683 }
685 static bool
686 io_write(struct io *io, const void *buf, size_t bufsize)
687 {
688 size_t written = 0;
690 while (!io_error(io) && written < bufsize) {
691 ssize_t size;
693 size = write(io->pipe, buf + written, bufsize - written);
694 if (size < 0 && (errno == EAGAIN || errno == EINTR))
695 continue;
696 else if (size == -1)
697 io->error = errno;
698 else
699 written += size;
700 }
702 return written == bufsize;
703 }
705 static bool
706 io_read_buf(struct io *io, char buf[], size_t bufsize)
707 {
708 char *result = io_get(io, '\n', TRUE);
710 if (result) {
711 result = chomp_string(result);
712 string_ncopy_do(buf, bufsize, result, strlen(result));
713 }
715 return done_io(io) && result;
716 }
718 static bool
719 run_io_buf(const char **argv, char buf[], size_t bufsize)
720 {
721 struct io io = {};
723 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
724 }
726 static int
727 io_load(struct io *io, const char *separators,
728 int (*read_property)(char *, size_t, char *, size_t))
729 {
730 char *name;
731 int state = OK;
733 if (!start_io(io))
734 return ERR;
736 while (state == OK && (name = io_get(io, '\n', TRUE))) {
737 char *value;
738 size_t namelen;
739 size_t valuelen;
741 name = chomp_string(name);
742 namelen = strcspn(name, separators);
744 if (name[namelen]) {
745 name[namelen] = 0;
746 value = chomp_string(name + namelen + 1);
747 valuelen = strlen(value);
749 } else {
750 value = "";
751 valuelen = 0;
752 }
754 state = read_property(name, namelen, value, valuelen);
755 }
757 if (state != ERR && io_error(io))
758 state = ERR;
759 done_io(io);
761 return state;
762 }
764 static int
765 run_io_load(const char **argv, const char *separators,
766 int (*read_property)(char *, size_t, char *, size_t))
767 {
768 struct io io = {};
770 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
771 ? io_load(&io, separators, read_property) : ERR;
772 }
775 /*
776 * User requests
777 */
779 #define REQ_INFO \
780 /* XXX: Keep the view request first and in sync with views[]. */ \
781 REQ_GROUP("View switching") \
782 REQ_(VIEW_MAIN, "Show main view"), \
783 REQ_(VIEW_DIFF, "Show diff view"), \
784 REQ_(VIEW_LOG, "Show log view"), \
785 REQ_(VIEW_TREE, "Show tree view"), \
786 REQ_(VIEW_BLOB, "Show blob view"), \
787 REQ_(VIEW_BLAME, "Show blame view"), \
788 REQ_(VIEW_BRANCH, "Show branch view"), \
789 REQ_(VIEW_HELP, "Show help page"), \
790 REQ_(VIEW_PAGER, "Show pager view"), \
791 REQ_(VIEW_STATUS, "Show status view"), \
792 REQ_(VIEW_STAGE, "Show stage view"), \
793 \
794 REQ_GROUP("View manipulation") \
795 REQ_(ENTER, "Enter current line and scroll"), \
796 REQ_(NEXT, "Move to next"), \
797 REQ_(PREVIOUS, "Move to previous"), \
798 REQ_(PARENT, "Move to parent"), \
799 REQ_(VIEW_NEXT, "Move focus to next view"), \
800 REQ_(REFRESH, "Reload and refresh"), \
801 REQ_(MAXIMIZE, "Maximize the current view"), \
802 REQ_(VIEW_CLOSE, "Close the current view"), \
803 REQ_(QUIT, "Close all views and quit"), \
804 \
805 REQ_GROUP("View specific requests") \
806 REQ_(STATUS_UPDATE, "Update file status"), \
807 REQ_(STATUS_REVERT, "Revert file changes"), \
808 REQ_(STATUS_MERGE, "Merge file using external tool"), \
809 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
810 \
811 REQ_GROUP("Cursor navigation") \
812 REQ_(MOVE_UP, "Move cursor one line up"), \
813 REQ_(MOVE_DOWN, "Move cursor one line down"), \
814 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
815 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
816 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
817 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
818 \
819 REQ_GROUP("Scrolling") \
820 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
821 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
822 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
823 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
824 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
825 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
826 \
827 REQ_GROUP("Searching") \
828 REQ_(SEARCH, "Search the view"), \
829 REQ_(SEARCH_BACK, "Search backwards in the view"), \
830 REQ_(FIND_NEXT, "Find next search match"), \
831 REQ_(FIND_PREV, "Find previous search match"), \
832 \
833 REQ_GROUP("Option manipulation") \
834 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
835 REQ_(TOGGLE_DATE, "Toggle date display"), \
836 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
837 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
838 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
839 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
840 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
841 \
842 REQ_GROUP("Misc") \
843 REQ_(PROMPT, "Bring up the prompt"), \
844 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
845 REQ_(SHOW_VERSION, "Show version information"), \
846 REQ_(STOP_LOADING, "Stop all loading views"), \
847 REQ_(EDIT, "Open in editor"), \
848 REQ_(NONE, "Do nothing")
851 /* User action requests. */
852 enum request {
853 #define REQ_GROUP(help)
854 #define REQ_(req, help) REQ_##req
856 /* Offset all requests to avoid conflicts with ncurses getch values. */
857 REQ_OFFSET = KEY_MAX + 1,
858 REQ_INFO
860 #undef REQ_GROUP
861 #undef REQ_
862 };
864 struct request_info {
865 enum request request;
866 const char *name;
867 int namelen;
868 const char *help;
869 };
871 static const struct request_info req_info[] = {
872 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
873 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
874 REQ_INFO
875 #undef REQ_GROUP
876 #undef REQ_
877 };
879 static enum request
880 get_request(const char *name)
881 {
882 int namelen = strlen(name);
883 int i;
885 for (i = 0; i < ARRAY_SIZE(req_info); i++)
886 if (req_info[i].namelen == namelen &&
887 !string_enum_compare(req_info[i].name, name, namelen))
888 return req_info[i].request;
890 return REQ_NONE;
891 }
894 /*
895 * Options
896 */
898 /* Option and state variables. */
899 static bool opt_date = TRUE;
900 static bool opt_author = TRUE;
901 static bool opt_line_number = FALSE;
902 static bool opt_line_graphics = TRUE;
903 static bool opt_rev_graph = FALSE;
904 static bool opt_show_refs = TRUE;
905 static int opt_num_interval = NUMBER_INTERVAL;
906 static double opt_hscroll = 0.50;
907 static int opt_tab_size = TAB_SIZE;
908 static int opt_author_cols = AUTHOR_COLS-1;
909 static char opt_path[SIZEOF_STR] = "";
910 static char opt_file[SIZEOF_STR] = "";
911 static char opt_ref[SIZEOF_REF] = "";
912 static char opt_head[SIZEOF_REF] = "";
913 static char opt_head_rev[SIZEOF_REV] = "";
914 static char opt_remote[SIZEOF_REF] = "";
915 static char opt_encoding[20] = "UTF-8";
916 static bool opt_utf8 = TRUE;
917 static char opt_codeset[20] = "UTF-8";
918 static iconv_t opt_iconv = ICONV_NONE;
919 static char opt_search[SIZEOF_STR] = "";
920 static char opt_cdup[SIZEOF_STR] = "";
921 static char opt_prefix[SIZEOF_STR] = "";
922 static char opt_git_dir[SIZEOF_STR] = "";
923 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
924 static char opt_editor[SIZEOF_STR] = "";
925 static FILE *opt_tty = NULL;
927 #define is_initial_commit() (!*opt_head_rev)
928 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
931 /*
932 * Line-oriented content detection.
933 */
935 #define LINE_INFO \
936 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
937 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
938 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
939 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
940 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
941 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
943 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
944 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
945 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
946 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
947 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
948 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
950 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
951 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
952 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
953 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
954 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
955 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
956 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
957 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
958 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
959 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
960 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
961 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
962 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
963 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
964 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
965 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
966 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
967 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
968 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
969 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
970 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
971 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
972 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
973 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
974 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
975 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
976 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
977 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
978 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
979 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
980 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
981 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
982 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
983 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
984 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
985 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
986 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
987 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
988 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
989 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
990 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
992 enum line_type {
993 #define LINE(type, line, fg, bg, attr) \
994 LINE_##type
995 LINE_INFO,
996 LINE_NONE
997 #undef LINE
998 };
1000 struct line_info {
1001 const char *name; /* Option name. */
1002 int namelen; /* Size of option name. */
1003 const char *line; /* The start of line to match. */
1004 int linelen; /* Size of string to match. */
1005 int fg, bg, attr; /* Color and text attributes for the lines. */
1006 };
1008 static struct line_info line_info[] = {
1009 #define LINE(type, line, fg, bg, attr) \
1010 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1011 LINE_INFO
1012 #undef LINE
1013 };
1015 static enum line_type
1016 get_line_type(const char *line)
1017 {
1018 int linelen = strlen(line);
1019 enum line_type type;
1021 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1022 /* Case insensitive search matches Signed-off-by lines better. */
1023 if (linelen >= line_info[type].linelen &&
1024 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1025 return type;
1027 return LINE_DEFAULT;
1028 }
1030 static inline int
1031 get_line_attr(enum line_type type)
1032 {
1033 assert(type < ARRAY_SIZE(line_info));
1034 return COLOR_PAIR(type) | line_info[type].attr;
1035 }
1037 static struct line_info *
1038 get_line_info(const char *name)
1039 {
1040 size_t namelen = strlen(name);
1041 enum line_type type;
1043 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1044 if (namelen == line_info[type].namelen &&
1045 !string_enum_compare(line_info[type].name, name, namelen))
1046 return &line_info[type];
1048 return NULL;
1049 }
1051 static void
1052 init_colors(void)
1053 {
1054 int default_bg = line_info[LINE_DEFAULT].bg;
1055 int default_fg = line_info[LINE_DEFAULT].fg;
1056 enum line_type type;
1058 start_color();
1060 if (assume_default_colors(default_fg, default_bg) == ERR) {
1061 default_bg = COLOR_BLACK;
1062 default_fg = COLOR_WHITE;
1063 }
1065 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1066 struct line_info *info = &line_info[type];
1067 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1068 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1070 init_pair(type, fg, bg);
1071 }
1072 }
1074 struct line {
1075 enum line_type type;
1077 /* State flags */
1078 unsigned int selected:1;
1079 unsigned int dirty:1;
1080 unsigned int cleareol:1;
1082 void *data; /* User data */
1083 };
1086 /*
1087 * Keys
1088 */
1090 struct keybinding {
1091 int alias;
1092 enum request request;
1093 };
1095 static const struct keybinding default_keybindings[] = {
1096 /* View switching */
1097 { 'm', REQ_VIEW_MAIN },
1098 { 'd', REQ_VIEW_DIFF },
1099 { 'l', REQ_VIEW_LOG },
1100 { 't', REQ_VIEW_TREE },
1101 { 'f', REQ_VIEW_BLOB },
1102 { 'B', REQ_VIEW_BLAME },
1103 { 'H', REQ_VIEW_BRANCH },
1104 { 'p', REQ_VIEW_PAGER },
1105 { 'h', REQ_VIEW_HELP },
1106 { 'S', REQ_VIEW_STATUS },
1107 { 'c', REQ_VIEW_STAGE },
1109 /* View manipulation */
1110 { 'q', REQ_VIEW_CLOSE },
1111 { KEY_TAB, REQ_VIEW_NEXT },
1112 { KEY_RETURN, REQ_ENTER },
1113 { KEY_UP, REQ_PREVIOUS },
1114 { KEY_DOWN, REQ_NEXT },
1115 { 'R', REQ_REFRESH },
1116 { KEY_F(5), REQ_REFRESH },
1117 { 'O', REQ_MAXIMIZE },
1119 /* Cursor navigation */
1120 { 'k', REQ_MOVE_UP },
1121 { 'j', REQ_MOVE_DOWN },
1122 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1123 { KEY_END, REQ_MOVE_LAST_LINE },
1124 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1125 { ' ', REQ_MOVE_PAGE_DOWN },
1126 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1127 { 'b', REQ_MOVE_PAGE_UP },
1128 { '-', REQ_MOVE_PAGE_UP },
1130 /* Scrolling */
1131 { KEY_LEFT, REQ_SCROLL_LEFT },
1132 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1133 { KEY_IC, REQ_SCROLL_LINE_UP },
1134 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1135 { 'w', REQ_SCROLL_PAGE_UP },
1136 { 's', REQ_SCROLL_PAGE_DOWN },
1138 /* Searching */
1139 { '/', REQ_SEARCH },
1140 { '?', REQ_SEARCH_BACK },
1141 { 'n', REQ_FIND_NEXT },
1142 { 'N', REQ_FIND_PREV },
1144 /* Misc */
1145 { 'Q', REQ_QUIT },
1146 { 'z', REQ_STOP_LOADING },
1147 { 'v', REQ_SHOW_VERSION },
1148 { 'r', REQ_SCREEN_REDRAW },
1149 { '.', REQ_TOGGLE_LINENO },
1150 { 'D', REQ_TOGGLE_DATE },
1151 { 'A', REQ_TOGGLE_AUTHOR },
1152 { 'g', REQ_TOGGLE_REV_GRAPH },
1153 { 'F', REQ_TOGGLE_REFS },
1154 { 'I', REQ_TOGGLE_SORT_ORDER },
1155 { 'i', REQ_TOGGLE_SORT_FIELD },
1156 { ':', REQ_PROMPT },
1157 { 'u', REQ_STATUS_UPDATE },
1158 { '!', REQ_STATUS_REVERT },
1159 { 'M', REQ_STATUS_MERGE },
1160 { '@', REQ_STAGE_NEXT },
1161 { ',', REQ_PARENT },
1162 { 'e', REQ_EDIT },
1163 };
1165 #define KEYMAP_INFO \
1166 KEYMAP_(GENERIC), \
1167 KEYMAP_(MAIN), \
1168 KEYMAP_(DIFF), \
1169 KEYMAP_(LOG), \
1170 KEYMAP_(TREE), \
1171 KEYMAP_(BLOB), \
1172 KEYMAP_(BLAME), \
1173 KEYMAP_(BRANCH), \
1174 KEYMAP_(PAGER), \
1175 KEYMAP_(HELP), \
1176 KEYMAP_(STATUS), \
1177 KEYMAP_(STAGE)
1179 enum keymap {
1180 #define KEYMAP_(name) KEYMAP_##name
1181 KEYMAP_INFO
1182 #undef KEYMAP_
1183 };
1185 static const struct enum_map keymap_table[] = {
1186 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1187 KEYMAP_INFO
1188 #undef KEYMAP_
1189 };
1191 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1193 struct keybinding_table {
1194 struct keybinding *data;
1195 size_t size;
1196 };
1198 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1200 static void
1201 add_keybinding(enum keymap keymap, enum request request, int key)
1202 {
1203 struct keybinding_table *table = &keybindings[keymap];
1205 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1206 if (!table->data)
1207 die("Failed to allocate keybinding");
1208 table->data[table->size].alias = key;
1209 table->data[table->size++].request = request;
1210 }
1212 /* Looks for a key binding first in the given map, then in the generic map, and
1213 * lastly in the default keybindings. */
1214 static enum request
1215 get_keybinding(enum keymap keymap, int key)
1216 {
1217 size_t i;
1219 for (i = 0; i < keybindings[keymap].size; i++)
1220 if (keybindings[keymap].data[i].alias == key)
1221 return keybindings[keymap].data[i].request;
1223 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1224 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1225 return keybindings[KEYMAP_GENERIC].data[i].request;
1227 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1228 if (default_keybindings[i].alias == key)
1229 return default_keybindings[i].request;
1231 return (enum request) key;
1232 }
1235 struct key {
1236 const char *name;
1237 int value;
1238 };
1240 static const struct key key_table[] = {
1241 { "Enter", KEY_RETURN },
1242 { "Space", ' ' },
1243 { "Backspace", KEY_BACKSPACE },
1244 { "Tab", KEY_TAB },
1245 { "Escape", KEY_ESC },
1246 { "Left", KEY_LEFT },
1247 { "Right", KEY_RIGHT },
1248 { "Up", KEY_UP },
1249 { "Down", KEY_DOWN },
1250 { "Insert", KEY_IC },
1251 { "Delete", KEY_DC },
1252 { "Hash", '#' },
1253 { "Home", KEY_HOME },
1254 { "End", KEY_END },
1255 { "PageUp", KEY_PPAGE },
1256 { "PageDown", KEY_NPAGE },
1257 { "F1", KEY_F(1) },
1258 { "F2", KEY_F(2) },
1259 { "F3", KEY_F(3) },
1260 { "F4", KEY_F(4) },
1261 { "F5", KEY_F(5) },
1262 { "F6", KEY_F(6) },
1263 { "F7", KEY_F(7) },
1264 { "F8", KEY_F(8) },
1265 { "F9", KEY_F(9) },
1266 { "F10", KEY_F(10) },
1267 { "F11", KEY_F(11) },
1268 { "F12", KEY_F(12) },
1269 };
1271 static int
1272 get_key_value(const char *name)
1273 {
1274 int i;
1276 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1277 if (!strcasecmp(key_table[i].name, name))
1278 return key_table[i].value;
1280 if (strlen(name) == 1 && isprint(*name))
1281 return (int) *name;
1283 return ERR;
1284 }
1286 static const char *
1287 get_key_name(int key_value)
1288 {
1289 static char key_char[] = "'X'";
1290 const char *seq = NULL;
1291 int key;
1293 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1294 if (key_table[key].value == key_value)
1295 seq = key_table[key].name;
1297 if (seq == NULL &&
1298 key_value < 127 &&
1299 isprint(key_value)) {
1300 key_char[1] = (char) key_value;
1301 seq = key_char;
1302 }
1304 return seq ? seq : "(no key)";
1305 }
1307 static const char *
1308 get_key(enum request request)
1309 {
1310 static char buf[BUFSIZ];
1311 size_t pos = 0;
1312 char *sep = "";
1313 int i;
1315 buf[pos] = 0;
1317 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1318 const struct keybinding *keybinding = &default_keybindings[i];
1320 if (keybinding->request != request)
1321 continue;
1323 if (!string_format_from(buf, &pos, "%s%s", sep,
1324 get_key_name(keybinding->alias)))
1325 return "Too many keybindings!";
1326 sep = ", ";
1327 }
1329 return buf;
1330 }
1332 struct run_request {
1333 enum keymap keymap;
1334 int key;
1335 const char *argv[SIZEOF_ARG];
1336 };
1338 static struct run_request *run_request;
1339 static size_t run_requests;
1341 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1343 static enum request
1344 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1345 {
1346 struct run_request *req;
1348 if (argc >= ARRAY_SIZE(req->argv) - 1)
1349 return REQ_NONE;
1351 if (!realloc_run_requests(&run_request, run_requests, 1))
1352 return REQ_NONE;
1354 req = &run_request[run_requests];
1355 req->keymap = keymap;
1356 req->key = key;
1357 req->argv[0] = NULL;
1359 if (!format_argv(req->argv, argv, FORMAT_NONE))
1360 return REQ_NONE;
1362 return REQ_NONE + ++run_requests;
1363 }
1365 static struct run_request *
1366 get_run_request(enum request request)
1367 {
1368 if (request <= REQ_NONE)
1369 return NULL;
1370 return &run_request[request - REQ_NONE - 1];
1371 }
1373 static void
1374 add_builtin_run_requests(void)
1375 {
1376 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1377 const char *commit[] = { "git", "commit", NULL };
1378 const char *gc[] = { "git", "gc", NULL };
1379 struct {
1380 enum keymap keymap;
1381 int key;
1382 int argc;
1383 const char **argv;
1384 } reqs[] = {
1385 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1386 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1387 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1388 };
1389 int i;
1391 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1392 enum request req;
1394 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1395 if (req != REQ_NONE)
1396 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1397 }
1398 }
1400 /*
1401 * User config file handling.
1402 */
1404 static int config_lineno;
1405 static bool config_errors;
1406 static const char *config_msg;
1408 static const struct enum_map color_map[] = {
1409 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1410 COLOR_MAP(DEFAULT),
1411 COLOR_MAP(BLACK),
1412 COLOR_MAP(BLUE),
1413 COLOR_MAP(CYAN),
1414 COLOR_MAP(GREEN),
1415 COLOR_MAP(MAGENTA),
1416 COLOR_MAP(RED),
1417 COLOR_MAP(WHITE),
1418 COLOR_MAP(YELLOW),
1419 };
1421 static const struct enum_map attr_map[] = {
1422 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1423 ATTR_MAP(NORMAL),
1424 ATTR_MAP(BLINK),
1425 ATTR_MAP(BOLD),
1426 ATTR_MAP(DIM),
1427 ATTR_MAP(REVERSE),
1428 ATTR_MAP(STANDOUT),
1429 ATTR_MAP(UNDERLINE),
1430 };
1432 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1434 static int parse_step(double *opt, const char *arg)
1435 {
1436 *opt = atoi(arg);
1437 if (!strchr(arg, '%'))
1438 return OK;
1440 /* "Shift down" so 100% and 1 does not conflict. */
1441 *opt = (*opt - 1) / 100;
1442 if (*opt >= 1.0) {
1443 *opt = 0.99;
1444 config_msg = "Step value larger than 100%";
1445 return ERR;
1446 }
1447 if (*opt < 0.0) {
1448 *opt = 1;
1449 config_msg = "Invalid step value";
1450 return ERR;
1451 }
1452 return OK;
1453 }
1455 static int
1456 parse_int(int *opt, const char *arg, int min, int max)
1457 {
1458 int value = atoi(arg);
1460 if (min <= value && value <= max) {
1461 *opt = value;
1462 return OK;
1463 }
1465 config_msg = "Integer value out of bound";
1466 return ERR;
1467 }
1469 static bool
1470 set_color(int *color, const char *name)
1471 {
1472 if (map_enum(color, color_map, name))
1473 return TRUE;
1474 if (!prefixcmp(name, "color"))
1475 return parse_int(color, name + 5, 0, 255) == OK;
1476 return FALSE;
1477 }
1479 /* Wants: object fgcolor bgcolor [attribute] */
1480 static int
1481 option_color_command(int argc, const char *argv[])
1482 {
1483 struct line_info *info;
1485 if (argc != 3 && argc != 4) {
1486 config_msg = "Wrong number of arguments given to color command";
1487 return ERR;
1488 }
1490 info = get_line_info(argv[0]);
1491 if (!info) {
1492 static const struct enum_map obsolete[] = {
1493 ENUM_MAP("main-delim", LINE_DELIMITER),
1494 ENUM_MAP("main-date", LINE_DATE),
1495 ENUM_MAP("main-author", LINE_AUTHOR),
1496 };
1497 int index;
1499 if (!map_enum(&index, obsolete, argv[0])) {
1500 config_msg = "Unknown color name";
1501 return ERR;
1502 }
1503 info = &line_info[index];
1504 }
1506 if (!set_color(&info->fg, argv[1]) ||
1507 !set_color(&info->bg, argv[2])) {
1508 config_msg = "Unknown color";
1509 return ERR;
1510 }
1512 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1513 config_msg = "Unknown attribute";
1514 return ERR;
1515 }
1517 return OK;
1518 }
1520 static int parse_bool(bool *opt, const char *arg)
1521 {
1522 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1523 ? TRUE : FALSE;
1524 return OK;
1525 }
1527 static int
1528 parse_string(char *opt, const char *arg, size_t optsize)
1529 {
1530 int arglen = strlen(arg);
1532 switch (arg[0]) {
1533 case '\"':
1534 case '\'':
1535 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1536 config_msg = "Unmatched quotation";
1537 return ERR;
1538 }
1539 arg += 1; arglen -= 2;
1540 default:
1541 string_ncopy_do(opt, optsize, arg, arglen);
1542 return OK;
1543 }
1544 }
1546 /* Wants: name = value */
1547 static int
1548 option_set_command(int argc, const char *argv[])
1549 {
1550 if (argc != 3) {
1551 config_msg = "Wrong number of arguments given to set command";
1552 return ERR;
1553 }
1555 if (strcmp(argv[1], "=")) {
1556 config_msg = "No value assigned";
1557 return ERR;
1558 }
1560 if (!strcmp(argv[0], "show-author"))
1561 return parse_bool(&opt_author, argv[2]);
1563 if (!strcmp(argv[0], "show-date"))
1564 return parse_bool(&opt_date, argv[2]);
1566 if (!strcmp(argv[0], "show-rev-graph"))
1567 return parse_bool(&opt_rev_graph, argv[2]);
1569 if (!strcmp(argv[0], "show-refs"))
1570 return parse_bool(&opt_show_refs, argv[2]);
1572 if (!strcmp(argv[0], "show-line-numbers"))
1573 return parse_bool(&opt_line_number, argv[2]);
1575 if (!strcmp(argv[0], "line-graphics"))
1576 return parse_bool(&opt_line_graphics, argv[2]);
1578 if (!strcmp(argv[0], "line-number-interval"))
1579 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1581 if (!strcmp(argv[0], "author-width"))
1582 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1584 if (!strcmp(argv[0], "horizontal-scroll"))
1585 return parse_step(&opt_hscroll, argv[2]);
1587 if (!strcmp(argv[0], "tab-size"))
1588 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1590 if (!strcmp(argv[0], "commit-encoding"))
1591 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1593 config_msg = "Unknown variable name";
1594 return ERR;
1595 }
1597 /* Wants: mode request key */
1598 static int
1599 option_bind_command(int argc, const char *argv[])
1600 {
1601 enum request request;
1602 int keymap;
1603 int key;
1605 if (argc < 3) {
1606 config_msg = "Wrong number of arguments given to bind command";
1607 return ERR;
1608 }
1610 if (set_keymap(&keymap, argv[0]) == ERR) {
1611 config_msg = "Unknown key map";
1612 return ERR;
1613 }
1615 key = get_key_value(argv[1]);
1616 if (key == ERR) {
1617 config_msg = "Unknown key";
1618 return ERR;
1619 }
1621 request = get_request(argv[2]);
1622 if (request == REQ_NONE) {
1623 static const struct enum_map obsolete[] = {
1624 ENUM_MAP("cherry-pick", REQ_NONE),
1625 ENUM_MAP("screen-resize", REQ_NONE),
1626 ENUM_MAP("tree-parent", REQ_PARENT),
1627 };
1628 int alias;
1630 if (map_enum(&alias, obsolete, argv[2])) {
1631 if (alias != REQ_NONE)
1632 add_keybinding(keymap, alias, key);
1633 config_msg = "Obsolete request name";
1634 return ERR;
1635 }
1636 }
1637 if (request == REQ_NONE && *argv[2]++ == '!')
1638 request = add_run_request(keymap, key, argc - 2, argv + 2);
1639 if (request == REQ_NONE) {
1640 config_msg = "Unknown request name";
1641 return ERR;
1642 }
1644 add_keybinding(keymap, request, key);
1646 return OK;
1647 }
1649 static int
1650 set_option(const char *opt, char *value)
1651 {
1652 const char *argv[SIZEOF_ARG];
1653 int argc = 0;
1655 if (!argv_from_string(argv, &argc, value)) {
1656 config_msg = "Too many option arguments";
1657 return ERR;
1658 }
1660 if (!strcmp(opt, "color"))
1661 return option_color_command(argc, argv);
1663 if (!strcmp(opt, "set"))
1664 return option_set_command(argc, argv);
1666 if (!strcmp(opt, "bind"))
1667 return option_bind_command(argc, argv);
1669 config_msg = "Unknown option command";
1670 return ERR;
1671 }
1673 static int
1674 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1675 {
1676 int status = OK;
1678 config_lineno++;
1679 config_msg = "Internal error";
1681 /* Check for comment markers, since read_properties() will
1682 * only ensure opt and value are split at first " \t". */
1683 optlen = strcspn(opt, "#");
1684 if (optlen == 0)
1685 return OK;
1687 if (opt[optlen] != 0) {
1688 config_msg = "No option value";
1689 status = ERR;
1691 } else {
1692 /* Look for comment endings in the value. */
1693 size_t len = strcspn(value, "#");
1695 if (len < valuelen) {
1696 valuelen = len;
1697 value[valuelen] = 0;
1698 }
1700 status = set_option(opt, value);
1701 }
1703 if (status == ERR) {
1704 warn("Error on line %d, near '%.*s': %s",
1705 config_lineno, (int) optlen, opt, config_msg);
1706 config_errors = TRUE;
1707 }
1709 /* Always keep going if errors are encountered. */
1710 return OK;
1711 }
1713 static void
1714 load_option_file(const char *path)
1715 {
1716 struct io io = {};
1718 /* It's OK that the file doesn't exist. */
1719 if (!io_open(&io, path))
1720 return;
1722 config_lineno = 0;
1723 config_errors = FALSE;
1725 if (io_load(&io, " \t", read_option) == ERR ||
1726 config_errors == TRUE)
1727 warn("Errors while loading %s.", path);
1728 }
1730 static int
1731 load_options(void)
1732 {
1733 const char *home = getenv("HOME");
1734 const char *tigrc_user = getenv("TIGRC_USER");
1735 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1736 char buf[SIZEOF_STR];
1738 add_builtin_run_requests();
1740 if (!tigrc_system)
1741 tigrc_system = SYSCONFDIR "/tigrc";
1742 load_option_file(tigrc_system);
1744 if (!tigrc_user) {
1745 if (!home || !string_format(buf, "%s/.tigrc", home))
1746 return ERR;
1747 tigrc_user = buf;
1748 }
1749 load_option_file(tigrc_user);
1751 return OK;
1752 }
1755 /*
1756 * The viewer
1757 */
1759 struct view;
1760 struct view_ops;
1762 /* The display array of active views and the index of the current view. */
1763 static struct view *display[2];
1764 static unsigned int current_view;
1766 #define foreach_displayed_view(view, i) \
1767 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1769 #define displayed_views() (display[1] != NULL ? 2 : 1)
1771 /* Current head and commit ID */
1772 static char ref_blob[SIZEOF_REF] = "";
1773 static char ref_commit[SIZEOF_REF] = "HEAD";
1774 static char ref_head[SIZEOF_REF] = "HEAD";
1776 struct view {
1777 const char *name; /* View name */
1778 const char *cmd_env; /* Command line set via environment */
1779 const char *id; /* Points to either of ref_{head,commit,blob} */
1781 struct view_ops *ops; /* View operations */
1783 enum keymap keymap; /* What keymap does this view have */
1784 bool git_dir; /* Whether the view requires a git directory. */
1786 char ref[SIZEOF_REF]; /* Hovered commit reference */
1787 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1789 int height, width; /* The width and height of the main window */
1790 WINDOW *win; /* The main window */
1791 WINDOW *title; /* The title window living below the main window */
1793 /* Navigation */
1794 unsigned long offset; /* Offset of the window top */
1795 unsigned long yoffset; /* Offset from the window side. */
1796 unsigned long lineno; /* Current line number */
1797 unsigned long p_offset; /* Previous offset of the window top */
1798 unsigned long p_yoffset;/* Previous offset from the window side */
1799 unsigned long p_lineno; /* Previous current line number */
1800 bool p_restore; /* Should the previous position be restored. */
1802 /* Searching */
1803 char grep[SIZEOF_STR]; /* Search string */
1804 regex_t *regex; /* Pre-compiled regexp */
1806 /* If non-NULL, points to the view that opened this view. If this view
1807 * is closed tig will switch back to the parent view. */
1808 struct view *parent;
1810 /* Buffering */
1811 size_t lines; /* Total number of lines */
1812 struct line *line; /* Line index */
1813 unsigned int digits; /* Number of digits in the lines member. */
1815 /* Drawing */
1816 struct line *curline; /* Line currently being drawn. */
1817 enum line_type curtype; /* Attribute currently used for drawing. */
1818 unsigned long col; /* Column when drawing. */
1819 bool has_scrolled; /* View was scrolled. */
1821 /* Loading */
1822 struct io io;
1823 struct io *pipe;
1824 time_t start_time;
1825 time_t update_secs;
1826 };
1828 struct view_ops {
1829 /* What type of content being displayed. Used in the title bar. */
1830 const char *type;
1831 /* Default command arguments. */
1832 const char **argv;
1833 /* Open and reads in all view content. */
1834 bool (*open)(struct view *view);
1835 /* Read one line; updates view->line. */
1836 bool (*read)(struct view *view, char *data);
1837 /* Draw one line; @lineno must be < view->height. */
1838 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1839 /* Depending on view handle a special requests. */
1840 enum request (*request)(struct view *view, enum request request, struct line *line);
1841 /* Search for regexp in a line. */
1842 bool (*grep)(struct view *view, struct line *line);
1843 /* Select line */
1844 void (*select)(struct view *view, struct line *line);
1845 };
1847 static struct view_ops blame_ops;
1848 static struct view_ops blob_ops;
1849 static struct view_ops diff_ops;
1850 static struct view_ops help_ops;
1851 static struct view_ops log_ops;
1852 static struct view_ops main_ops;
1853 static struct view_ops pager_ops;
1854 static struct view_ops stage_ops;
1855 static struct view_ops status_ops;
1856 static struct view_ops tree_ops;
1857 static struct view_ops branch_ops;
1859 #define VIEW_STR(name, env, ref, ops, map, git) \
1860 { name, #env, ref, ops, map, git }
1862 #define VIEW_(id, name, ops, git, ref) \
1863 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1866 static struct view views[] = {
1867 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1868 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1869 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1870 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1871 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1872 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1873 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
1874 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1875 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1876 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1877 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1878 };
1880 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1881 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1883 #define foreach_view(view, i) \
1884 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1886 #define view_is_displayed(view) \
1887 (view == display[0] || view == display[1])
1890 enum line_graphic {
1891 LINE_GRAPHIC_VLINE
1892 };
1894 static chtype line_graphics[] = {
1895 /* LINE_GRAPHIC_VLINE: */ '|'
1896 };
1898 static inline void
1899 set_view_attr(struct view *view, enum line_type type)
1900 {
1901 if (!view->curline->selected && view->curtype != type) {
1902 wattrset(view->win, get_line_attr(type));
1903 wchgat(view->win, -1, 0, type, NULL);
1904 view->curtype = type;
1905 }
1906 }
1908 static int
1909 draw_chars(struct view *view, enum line_type type, const char *string,
1910 int max_len, bool use_tilde)
1911 {
1912 int len = 0;
1913 int col = 0;
1914 int trimmed = FALSE;
1915 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1917 if (max_len <= 0)
1918 return 0;
1920 if (opt_utf8) {
1921 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1922 } else {
1923 col = len = strlen(string);
1924 if (len > max_len) {
1925 if (use_tilde) {
1926 max_len -= 1;
1927 }
1928 col = len = max_len;
1929 trimmed = TRUE;
1930 }
1931 }
1933 set_view_attr(view, type);
1934 if (len > 0)
1935 waddnstr(view->win, string, len);
1936 if (trimmed && use_tilde) {
1937 set_view_attr(view, LINE_DELIMITER);
1938 waddch(view->win, '~');
1939 col++;
1940 }
1942 return col;
1943 }
1945 static int
1946 draw_space(struct view *view, enum line_type type, int max, int spaces)
1947 {
1948 static char space[] = " ";
1949 int col = 0;
1951 spaces = MIN(max, spaces);
1953 while (spaces > 0) {
1954 int len = MIN(spaces, sizeof(space) - 1);
1956 col += draw_chars(view, type, space, len, FALSE);
1957 spaces -= len;
1958 }
1960 return col;
1961 }
1963 static bool
1964 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1965 {
1966 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1967 return view->width + view->yoffset <= view->col;
1968 }
1970 static bool
1971 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1972 {
1973 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1974 int max = view->width + view->yoffset - view->col;
1975 int i;
1977 if (max < size)
1978 size = max;
1980 set_view_attr(view, type);
1981 /* Using waddch() instead of waddnstr() ensures that
1982 * they'll be rendered correctly for the cursor line. */
1983 for (i = skip; i < size; i++)
1984 waddch(view->win, graphic[i]);
1986 view->col += size;
1987 if (size < max && skip <= size)
1988 waddch(view->win, ' ');
1989 view->col++;
1991 return view->width + view->yoffset <= view->col;
1992 }
1994 static bool
1995 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1996 {
1997 int max = MIN(view->width + view->yoffset - view->col, len);
1998 int col;
2000 if (text)
2001 col = draw_chars(view, type, text, max - 1, trim);
2002 else
2003 col = draw_space(view, type, max - 1, max - 1);
2005 view->col += col;
2006 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2007 return view->width + view->yoffset <= view->col;
2008 }
2010 static bool
2011 draw_date(struct view *view, time_t *time)
2012 {
2013 const char *date = mkdate(time);
2015 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2016 }
2018 static bool
2019 draw_author(struct view *view, const char *author)
2020 {
2021 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2023 if (!trim) {
2024 static char initials[10];
2025 size_t pos;
2027 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2029 memset(initials, 0, sizeof(initials));
2030 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2031 while (is_initial_sep(*author))
2032 author++;
2033 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2034 while (*author && !is_initial_sep(author[1]))
2035 author++;
2036 }
2038 author = initials;
2039 }
2041 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2042 }
2044 static bool
2045 draw_mode(struct view *view, mode_t mode)
2046 {
2047 const char *str;
2049 if (S_ISDIR(mode))
2050 str = "drwxr-xr-x";
2051 else if (S_ISLNK(mode))
2052 str = "lrwxrwxrwx";
2053 else if (S_ISGITLINK(mode))
2054 str = "m---------";
2055 else if (S_ISREG(mode) && mode & S_IXUSR)
2056 str = "-rwxr-xr-x";
2057 else if (S_ISREG(mode))
2058 str = "-rw-r--r--";
2059 else
2060 str = "----------";
2062 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2063 }
2065 static bool
2066 draw_lineno(struct view *view, unsigned int lineno)
2067 {
2068 char number[10];
2069 int digits3 = view->digits < 3 ? 3 : view->digits;
2070 int max = MIN(view->width + view->yoffset - view->col, digits3);
2071 char *text = NULL;
2073 lineno += view->offset + 1;
2074 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2075 static char fmt[] = "%1ld";
2077 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2078 if (string_format(number, fmt, lineno))
2079 text = number;
2080 }
2081 if (text)
2082 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2083 else
2084 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2085 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2086 }
2088 static bool
2089 draw_view_line(struct view *view, unsigned int lineno)
2090 {
2091 struct line *line;
2092 bool selected = (view->offset + lineno == view->lineno);
2094 assert(view_is_displayed(view));
2096 if (view->offset + lineno >= view->lines)
2097 return FALSE;
2099 line = &view->line[view->offset + lineno];
2101 wmove(view->win, lineno, 0);
2102 if (line->cleareol)
2103 wclrtoeol(view->win);
2104 view->col = 0;
2105 view->curline = line;
2106 view->curtype = LINE_NONE;
2107 line->selected = FALSE;
2108 line->dirty = line->cleareol = 0;
2110 if (selected) {
2111 set_view_attr(view, LINE_CURSOR);
2112 line->selected = TRUE;
2113 view->ops->select(view, line);
2114 }
2116 return view->ops->draw(view, line, lineno);
2117 }
2119 static void
2120 redraw_view_dirty(struct view *view)
2121 {
2122 bool dirty = FALSE;
2123 int lineno;
2125 for (lineno = 0; lineno < view->height; lineno++) {
2126 if (view->offset + lineno >= view->lines)
2127 break;
2128 if (!view->line[view->offset + lineno].dirty)
2129 continue;
2130 dirty = TRUE;
2131 if (!draw_view_line(view, lineno))
2132 break;
2133 }
2135 if (!dirty)
2136 return;
2137 wnoutrefresh(view->win);
2138 }
2140 static void
2141 redraw_view_from(struct view *view, int lineno)
2142 {
2143 assert(0 <= lineno && lineno < view->height);
2145 for (; lineno < view->height; lineno++) {
2146 if (!draw_view_line(view, lineno))
2147 break;
2148 }
2150 wnoutrefresh(view->win);
2151 }
2153 static void
2154 redraw_view(struct view *view)
2155 {
2156 werase(view->win);
2157 redraw_view_from(view, 0);
2158 }
2161 static void
2162 update_view_title(struct view *view)
2163 {
2164 char buf[SIZEOF_STR];
2165 char state[SIZEOF_STR];
2166 size_t bufpos = 0, statelen = 0;
2168 assert(view_is_displayed(view));
2170 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2171 unsigned int view_lines = view->offset + view->height;
2172 unsigned int lines = view->lines
2173 ? MIN(view_lines, view->lines) * 100 / view->lines
2174 : 0;
2176 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2177 view->ops->type,
2178 view->lineno + 1,
2179 view->lines,
2180 lines);
2182 }
2184 if (view->pipe) {
2185 time_t secs = time(NULL) - view->start_time;
2187 /* Three git seconds are a long time ... */
2188 if (secs > 2)
2189 string_format_from(state, &statelen, " loading %lds", secs);
2190 }
2192 string_format_from(buf, &bufpos, "[%s]", view->name);
2193 if (*view->ref && bufpos < view->width) {
2194 size_t refsize = strlen(view->ref);
2195 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2197 if (minsize < view->width)
2198 refsize = view->width - minsize + 7;
2199 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2200 }
2202 if (statelen && bufpos < view->width) {
2203 string_format_from(buf, &bufpos, "%s", state);
2204 }
2206 if (view == display[current_view])
2207 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2208 else
2209 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2211 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2212 wclrtoeol(view->title);
2213 wnoutrefresh(view->title);
2214 }
2216 static void
2217 resize_display(void)
2218 {
2219 int offset, i;
2220 struct view *base = display[0];
2221 struct view *view = display[1] ? display[1] : display[0];
2223 /* Setup window dimensions */
2225 getmaxyx(stdscr, base->height, base->width);
2227 /* Make room for the status window. */
2228 base->height -= 1;
2230 if (view != base) {
2231 /* Horizontal split. */
2232 view->width = base->width;
2233 view->height = SCALE_SPLIT_VIEW(base->height);
2234 base->height -= view->height;
2236 /* Make room for the title bar. */
2237 view->height -= 1;
2238 }
2240 /* Make room for the title bar. */
2241 base->height -= 1;
2243 offset = 0;
2245 foreach_displayed_view (view, i) {
2246 if (!view->win) {
2247 view->win = newwin(view->height, 0, offset, 0);
2248 if (!view->win)
2249 die("Failed to create %s view", view->name);
2251 scrollok(view->win, FALSE);
2253 view->title = newwin(1, 0, offset + view->height, 0);
2254 if (!view->title)
2255 die("Failed to create title window");
2257 } else {
2258 wresize(view->win, view->height, view->width);
2259 mvwin(view->win, offset, 0);
2260 mvwin(view->title, offset + view->height, 0);
2261 }
2263 offset += view->height + 1;
2264 }
2265 }
2267 static void
2268 redraw_display(bool clear)
2269 {
2270 struct view *view;
2271 int i;
2273 foreach_displayed_view (view, i) {
2274 if (clear)
2275 wclear(view->win);
2276 redraw_view(view);
2277 update_view_title(view);
2278 }
2279 }
2281 static void
2282 toggle_view_option(bool *option, const char *help)
2283 {
2284 *option = !*option;
2285 redraw_display(FALSE);
2286 report("%sabling %s", *option ? "En" : "Dis", help);
2287 }
2289 static void
2290 maximize_view(struct view *view)
2291 {
2292 memset(display, 0, sizeof(display));
2293 current_view = 0;
2294 display[current_view] = view;
2295 resize_display();
2296 redraw_display(FALSE);
2297 report("");
2298 }
2301 /*
2302 * Navigation
2303 */
2305 static bool
2306 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2307 {
2308 if (lineno >= view->lines)
2309 lineno = view->lines > 0 ? view->lines - 1 : 0;
2311 if (offset > lineno || offset + view->height <= lineno) {
2312 unsigned long half = view->height / 2;
2314 if (lineno > half)
2315 offset = lineno - half;
2316 else
2317 offset = 0;
2318 }
2320 if (offset != view->offset || lineno != view->lineno) {
2321 view->offset = offset;
2322 view->lineno = lineno;
2323 return TRUE;
2324 }
2326 return FALSE;
2327 }
2329 static int
2330 apply_step(double step, int value)
2331 {
2332 if (step >= 1)
2333 return (int) step;
2334 value *= step + 0.01;
2335 return value ? value : 1;
2336 }
2338 /* Scrolling backend */
2339 static void
2340 do_scroll_view(struct view *view, int lines)
2341 {
2342 bool redraw_current_line = FALSE;
2344 /* The rendering expects the new offset. */
2345 view->offset += lines;
2347 assert(0 <= view->offset && view->offset < view->lines);
2348 assert(lines);
2350 /* Move current line into the view. */
2351 if (view->lineno < view->offset) {
2352 view->lineno = view->offset;
2353 redraw_current_line = TRUE;
2354 } else if (view->lineno >= view->offset + view->height) {
2355 view->lineno = view->offset + view->height - 1;
2356 redraw_current_line = TRUE;
2357 }
2359 assert(view->offset <= view->lineno && view->lineno < view->lines);
2361 /* Redraw the whole screen if scrolling is pointless. */
2362 if (view->height < ABS(lines)) {
2363 redraw_view(view);
2365 } else {
2366 int line = lines > 0 ? view->height - lines : 0;
2367 int end = line + ABS(lines);
2369 scrollok(view->win, TRUE);
2370 wscrl(view->win, lines);
2371 scrollok(view->win, FALSE);
2373 while (line < end && draw_view_line(view, line))
2374 line++;
2376 if (redraw_current_line)
2377 draw_view_line(view, view->lineno - view->offset);
2378 wnoutrefresh(view->win);
2379 }
2381 view->has_scrolled = TRUE;
2382 report("");
2383 }
2385 /* Scroll frontend */
2386 static void
2387 scroll_view(struct view *view, enum request request)
2388 {
2389 int lines = 1;
2391 assert(view_is_displayed(view));
2393 switch (request) {
2394 case REQ_SCROLL_LEFT:
2395 if (view->yoffset == 0) {
2396 report("Cannot scroll beyond the first column");
2397 return;
2398 }
2399 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2400 view->yoffset = 0;
2401 else
2402 view->yoffset -= apply_step(opt_hscroll, view->width);
2403 redraw_view_from(view, 0);
2404 report("");
2405 return;
2406 case REQ_SCROLL_RIGHT:
2407 view->yoffset += apply_step(opt_hscroll, view->width);
2408 redraw_view(view);
2409 report("");
2410 return;
2411 case REQ_SCROLL_PAGE_DOWN:
2412 lines = view->height;
2413 case REQ_SCROLL_LINE_DOWN:
2414 if (view->offset + lines > view->lines)
2415 lines = view->lines - view->offset;
2417 if (lines == 0 || view->offset + view->height >= view->lines) {
2418 report("Cannot scroll beyond the last line");
2419 return;
2420 }
2421 break;
2423 case REQ_SCROLL_PAGE_UP:
2424 lines = view->height;
2425 case REQ_SCROLL_LINE_UP:
2426 if (lines > view->offset)
2427 lines = view->offset;
2429 if (lines == 0) {
2430 report("Cannot scroll beyond the first line");
2431 return;
2432 }
2434 lines = -lines;
2435 break;
2437 default:
2438 die("request %d not handled in switch", request);
2439 }
2441 do_scroll_view(view, lines);
2442 }
2444 /* Cursor moving */
2445 static void
2446 move_view(struct view *view, enum request request)
2447 {
2448 int scroll_steps = 0;
2449 int steps;
2451 switch (request) {
2452 case REQ_MOVE_FIRST_LINE:
2453 steps = -view->lineno;
2454 break;
2456 case REQ_MOVE_LAST_LINE:
2457 steps = view->lines - view->lineno - 1;
2458 break;
2460 case REQ_MOVE_PAGE_UP:
2461 steps = view->height > view->lineno
2462 ? -view->lineno : -view->height;
2463 break;
2465 case REQ_MOVE_PAGE_DOWN:
2466 steps = view->lineno + view->height >= view->lines
2467 ? view->lines - view->lineno - 1 : view->height;
2468 break;
2470 case REQ_MOVE_UP:
2471 steps = -1;
2472 break;
2474 case REQ_MOVE_DOWN:
2475 steps = 1;
2476 break;
2478 default:
2479 die("request %d not handled in switch", request);
2480 }
2482 if (steps <= 0 && view->lineno == 0) {
2483 report("Cannot move beyond the first line");
2484 return;
2486 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2487 report("Cannot move beyond the last line");
2488 return;
2489 }
2491 /* Move the current line */
2492 view->lineno += steps;
2493 assert(0 <= view->lineno && view->lineno < view->lines);
2495 /* Check whether the view needs to be scrolled */
2496 if (view->lineno < view->offset ||
2497 view->lineno >= view->offset + view->height) {
2498 scroll_steps = steps;
2499 if (steps < 0 && -steps > view->offset) {
2500 scroll_steps = -view->offset;
2502 } else if (steps > 0) {
2503 if (view->lineno == view->lines - 1 &&
2504 view->lines > view->height) {
2505 scroll_steps = view->lines - view->offset - 1;
2506 if (scroll_steps >= view->height)
2507 scroll_steps -= view->height - 1;
2508 }
2509 }
2510 }
2512 if (!view_is_displayed(view)) {
2513 view->offset += scroll_steps;
2514 assert(0 <= view->offset && view->offset < view->lines);
2515 view->ops->select(view, &view->line[view->lineno]);
2516 return;
2517 }
2519 /* Repaint the old "current" line if we be scrolling */
2520 if (ABS(steps) < view->height)
2521 draw_view_line(view, view->lineno - steps - view->offset);
2523 if (scroll_steps) {
2524 do_scroll_view(view, scroll_steps);
2525 return;
2526 }
2528 /* Draw the current line */
2529 draw_view_line(view, view->lineno - view->offset);
2531 wnoutrefresh(view->win);
2532 report("");
2533 }
2536 /*
2537 * Searching
2538 */
2540 static void search_view(struct view *view, enum request request);
2542 static bool
2543 grep_text(struct view *view, const char *text[])
2544 {
2545 regmatch_t pmatch;
2546 size_t i;
2548 for (i = 0; text[i]; i++)
2549 if (*text[i] &&
2550 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2551 return TRUE;
2552 return FALSE;
2553 }
2555 static void
2556 select_view_line(struct view *view, unsigned long lineno)
2557 {
2558 unsigned long old_lineno = view->lineno;
2559 unsigned long old_offset = view->offset;
2561 if (goto_view_line(view, view->offset, lineno)) {
2562 if (view_is_displayed(view)) {
2563 if (old_offset != view->offset) {
2564 redraw_view(view);
2565 } else {
2566 draw_view_line(view, old_lineno - view->offset);
2567 draw_view_line(view, view->lineno - view->offset);
2568 wnoutrefresh(view->win);
2569 }
2570 } else {
2571 view->ops->select(view, &view->line[view->lineno]);
2572 }
2573 }
2574 }
2576 static void
2577 find_next(struct view *view, enum request request)
2578 {
2579 unsigned long lineno = view->lineno;
2580 int direction;
2582 if (!*view->grep) {
2583 if (!*opt_search)
2584 report("No previous search");
2585 else
2586 search_view(view, request);
2587 return;
2588 }
2590 switch (request) {
2591 case REQ_SEARCH:
2592 case REQ_FIND_NEXT:
2593 direction = 1;
2594 break;
2596 case REQ_SEARCH_BACK:
2597 case REQ_FIND_PREV:
2598 direction = -1;
2599 break;
2601 default:
2602 return;
2603 }
2605 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2606 lineno += direction;
2608 /* Note, lineno is unsigned long so will wrap around in which case it
2609 * will become bigger than view->lines. */
2610 for (; lineno < view->lines; lineno += direction) {
2611 if (view->ops->grep(view, &view->line[lineno])) {
2612 select_view_line(view, lineno);
2613 report("Line %ld matches '%s'", lineno + 1, view->grep);
2614 return;
2615 }
2616 }
2618 report("No match found for '%s'", view->grep);
2619 }
2621 static void
2622 search_view(struct view *view, enum request request)
2623 {
2624 int regex_err;
2626 if (view->regex) {
2627 regfree(view->regex);
2628 *view->grep = 0;
2629 } else {
2630 view->regex = calloc(1, sizeof(*view->regex));
2631 if (!view->regex)
2632 return;
2633 }
2635 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2636 if (regex_err != 0) {
2637 char buf[SIZEOF_STR] = "unknown error";
2639 regerror(regex_err, view->regex, buf, sizeof(buf));
2640 report("Search failed: %s", buf);
2641 return;
2642 }
2644 string_copy(view->grep, opt_search);
2646 find_next(view, request);
2647 }
2649 /*
2650 * Incremental updating
2651 */
2653 static void
2654 reset_view(struct view *view)
2655 {
2656 int i;
2658 for (i = 0; i < view->lines; i++)
2659 free(view->line[i].data);
2660 free(view->line);
2662 view->p_offset = view->offset;
2663 view->p_yoffset = view->yoffset;
2664 view->p_lineno = view->lineno;
2666 view->line = NULL;
2667 view->offset = 0;
2668 view->yoffset = 0;
2669 view->lines = 0;
2670 view->lineno = 0;
2671 view->vid[0] = 0;
2672 view->update_secs = 0;
2673 }
2675 static void
2676 free_argv(const char *argv[])
2677 {
2678 int argc;
2680 for (argc = 0; argv[argc]; argc++)
2681 free((void *) argv[argc]);
2682 }
2684 static bool
2685 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2686 {
2687 char buf[SIZEOF_STR];
2688 int argc;
2689 bool noreplace = flags == FORMAT_NONE;
2691 free_argv(dst_argv);
2693 for (argc = 0; src_argv[argc]; argc++) {
2694 const char *arg = src_argv[argc];
2695 size_t bufpos = 0;
2697 while (arg) {
2698 char *next = strstr(arg, "%(");
2699 int len = next - arg;
2700 const char *value;
2702 if (!next || noreplace) {
2703 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2704 noreplace = TRUE;
2705 len = strlen(arg);
2706 value = "";
2708 } else if (!prefixcmp(next, "%(directory)")) {
2709 value = opt_path;
2711 } else if (!prefixcmp(next, "%(file)")) {
2712 value = opt_file;
2714 } else if (!prefixcmp(next, "%(ref)")) {
2715 value = *opt_ref ? opt_ref : "HEAD";
2717 } else if (!prefixcmp(next, "%(head)")) {
2718 value = ref_head;
2720 } else if (!prefixcmp(next, "%(commit)")) {
2721 value = ref_commit;
2723 } else if (!prefixcmp(next, "%(blob)")) {
2724 value = ref_blob;
2726 } else {
2727 report("Unknown replacement: `%s`", next);
2728 return FALSE;
2729 }
2731 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2732 return FALSE;
2734 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2735 }
2737 dst_argv[argc] = strdup(buf);
2738 if (!dst_argv[argc])
2739 break;
2740 }
2742 dst_argv[argc] = NULL;
2744 return src_argv[argc] == NULL;
2745 }
2747 static bool
2748 restore_view_position(struct view *view)
2749 {
2750 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2751 return FALSE;
2753 /* Changing the view position cancels the restoring. */
2754 /* FIXME: Changing back to the first line is not detected. */
2755 if (view->offset != 0 || view->lineno != 0) {
2756 view->p_restore = FALSE;
2757 return FALSE;
2758 }
2760 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2761 view_is_displayed(view))
2762 werase(view->win);
2764 view->yoffset = view->p_yoffset;
2765 view->p_restore = FALSE;
2767 return TRUE;
2768 }
2770 static void
2771 end_update(struct view *view, bool force)
2772 {
2773 if (!view->pipe)
2774 return;
2775 while (!view->ops->read(view, NULL))
2776 if (!force)
2777 return;
2778 set_nonblocking_input(FALSE);
2779 if (force)
2780 kill_io(view->pipe);
2781 done_io(view->pipe);
2782 view->pipe = NULL;
2783 }
2785 static void
2786 setup_update(struct view *view, const char *vid)
2787 {
2788 set_nonblocking_input(TRUE);
2789 reset_view(view);
2790 string_copy_rev(view->vid, vid);
2791 view->pipe = &view->io;
2792 view->start_time = time(NULL);
2793 }
2795 static bool
2796 prepare_update(struct view *view, const char *argv[], const char *dir,
2797 enum format_flags flags)
2798 {
2799 if (view->pipe)
2800 end_update(view, TRUE);
2801 return init_io_rd(&view->io, argv, dir, flags);
2802 }
2804 static bool
2805 prepare_update_file(struct view *view, const char *name)
2806 {
2807 if (view->pipe)
2808 end_update(view, TRUE);
2809 return io_open(&view->io, name);
2810 }
2812 static bool
2813 begin_update(struct view *view, bool refresh)
2814 {
2815 if (view->pipe)
2816 end_update(view, TRUE);
2818 if (refresh) {
2819 if (!start_io(&view->io))
2820 return FALSE;
2822 } else {
2823 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2824 opt_path[0] = 0;
2826 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2827 return FALSE;
2829 /* Put the current ref_* value to the view title ref
2830 * member. This is needed by the blob view. Most other
2831 * views sets it automatically after loading because the
2832 * first line is a commit line. */
2833 string_copy_rev(view->ref, view->id);
2834 }
2836 setup_update(view, view->id);
2838 return TRUE;
2839 }
2841 static bool
2842 update_view(struct view *view)
2843 {
2844 char out_buffer[BUFSIZ * 2];
2845 char *line;
2846 /* Clear the view and redraw everything since the tree sorting
2847 * might have rearranged things. */
2848 bool redraw = view->lines == 0;
2849 bool can_read = TRUE;
2851 if (!view->pipe)
2852 return TRUE;
2854 if (!io_can_read(view->pipe)) {
2855 if (view->lines == 0 && view_is_displayed(view)) {
2856 time_t secs = time(NULL) - view->start_time;
2858 if (secs > 1 && secs > view->update_secs) {
2859 if (view->update_secs == 0)
2860 redraw_view(view);
2861 update_view_title(view);
2862 view->update_secs = secs;
2863 }
2864 }
2865 return TRUE;
2866 }
2868 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2869 if (opt_iconv != ICONV_NONE) {
2870 ICONV_CONST char *inbuf = line;
2871 size_t inlen = strlen(line) + 1;
2873 char *outbuf = out_buffer;
2874 size_t outlen = sizeof(out_buffer);
2876 size_t ret;
2878 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2879 if (ret != (size_t) -1)
2880 line = out_buffer;
2881 }
2883 if (!view->ops->read(view, line)) {
2884 report("Allocation failure");
2885 end_update(view, TRUE);
2886 return FALSE;
2887 }
2888 }
2890 {
2891 unsigned long lines = view->lines;
2892 int digits;
2894 for (digits = 0; lines; digits++)
2895 lines /= 10;
2897 /* Keep the displayed view in sync with line number scaling. */
2898 if (digits != view->digits) {
2899 view->digits = digits;
2900 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2901 redraw = TRUE;
2902 }
2903 }
2905 if (io_error(view->pipe)) {
2906 report("Failed to read: %s", io_strerror(view->pipe));
2907 end_update(view, TRUE);
2909 } else if (io_eof(view->pipe)) {
2910 report("");
2911 end_update(view, FALSE);
2912 }
2914 if (restore_view_position(view))
2915 redraw = TRUE;
2917 if (!view_is_displayed(view))
2918 return TRUE;
2920 if (redraw)
2921 redraw_view_from(view, 0);
2922 else
2923 redraw_view_dirty(view);
2925 /* Update the title _after_ the redraw so that if the redraw picks up a
2926 * commit reference in view->ref it'll be available here. */
2927 update_view_title(view);
2928 return TRUE;
2929 }
2931 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2933 static struct line *
2934 add_line_data(struct view *view, void *data, enum line_type type)
2935 {
2936 struct line *line;
2938 if (!realloc_lines(&view->line, view->lines, 1))
2939 return NULL;
2941 line = &view->line[view->lines++];
2942 memset(line, 0, sizeof(*line));
2943 line->type = type;
2944 line->data = data;
2945 line->dirty = 1;
2947 return line;
2948 }
2950 static struct line *
2951 add_line_text(struct view *view, const char *text, enum line_type type)
2952 {
2953 char *data = text ? strdup(text) : NULL;
2955 return data ? add_line_data(view, data, type) : NULL;
2956 }
2958 static struct line *
2959 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2960 {
2961 char buf[SIZEOF_STR];
2962 va_list args;
2964 va_start(args, fmt);
2965 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2966 buf[0] = 0;
2967 va_end(args);
2969 return buf[0] ? add_line_text(view, buf, type) : NULL;
2970 }
2972 /*
2973 * View opening
2974 */
2976 enum open_flags {
2977 OPEN_DEFAULT = 0, /* Use default view switching. */
2978 OPEN_SPLIT = 1, /* Split current view. */
2979 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2980 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2981 OPEN_PREPARED = 32, /* Open already prepared command. */
2982 };
2984 static void
2985 open_view(struct view *prev, enum request request, enum open_flags flags)
2986 {
2987 bool split = !!(flags & OPEN_SPLIT);
2988 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2989 bool nomaximize = !!(flags & OPEN_REFRESH);
2990 struct view *view = VIEW(request);
2991 int nviews = displayed_views();
2992 struct view *base_view = display[0];
2994 if (view == prev && nviews == 1 && !reload) {
2995 report("Already in %s view", view->name);
2996 return;
2997 }
2999 if (view->git_dir && !opt_git_dir[0]) {
3000 report("The %s view is disabled in pager view", view->name);
3001 return;
3002 }
3004 if (split) {
3005 display[1] = view;
3006 current_view = 1;
3007 } else if (!nomaximize) {
3008 /* Maximize the current view. */
3009 memset(display, 0, sizeof(display));
3010 current_view = 0;
3011 display[current_view] = view;
3012 }
3014 /* Resize the view when switching between split- and full-screen,
3015 * or when switching between two different full-screen views. */
3016 if (nviews != displayed_views() ||
3017 (nviews == 1 && base_view != display[0]))
3018 resize_display();
3020 if (view->ops->open) {
3021 if (view->pipe)
3022 end_update(view, TRUE);
3023 if (!view->ops->open(view)) {
3024 report("Failed to load %s view", view->name);
3025 return;
3026 }
3027 restore_view_position(view);
3029 } else if ((reload || strcmp(view->vid, view->id)) &&
3030 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3031 report("Failed to load %s view", view->name);
3032 return;
3033 }
3035 if (split && prev->lineno - prev->offset >= prev->height) {
3036 /* Take the title line into account. */
3037 int lines = prev->lineno - prev->offset - prev->height + 1;
3039 /* Scroll the view that was split if the current line is
3040 * outside the new limited view. */
3041 do_scroll_view(prev, lines);
3042 }
3044 if (prev && view != prev) {
3045 if (split) {
3046 /* "Blur" the previous view. */
3047 update_view_title(prev);
3048 }
3050 view->parent = prev;
3051 }
3053 if (view->pipe && view->lines == 0) {
3054 /* Clear the old view and let the incremental updating refill
3055 * the screen. */
3056 werase(view->win);
3057 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3058 report("");
3059 } else if (view_is_displayed(view)) {
3060 redraw_view(view);
3061 report("");
3062 }
3063 }
3065 static void
3066 open_external_viewer(const char *argv[], const char *dir)
3067 {
3068 def_prog_mode(); /* save current tty modes */
3069 endwin(); /* restore original tty modes */
3070 run_io_fg(argv, dir);
3071 fprintf(stderr, "Press Enter to continue");
3072 getc(opt_tty);
3073 reset_prog_mode();
3074 redraw_display(TRUE);
3075 }
3077 static void
3078 open_mergetool(const char *file)
3079 {
3080 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3082 open_external_viewer(mergetool_argv, opt_cdup);
3083 }
3085 static void
3086 open_editor(bool from_root, const char *file)
3087 {
3088 const char *editor_argv[] = { "vi", file, NULL };
3089 const char *editor;
3091 editor = getenv("GIT_EDITOR");
3092 if (!editor && *opt_editor)
3093 editor = opt_editor;
3094 if (!editor)
3095 editor = getenv("VISUAL");
3096 if (!editor)
3097 editor = getenv("EDITOR");
3098 if (!editor)
3099 editor = "vi";
3101 editor_argv[0] = editor;
3102 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3103 }
3105 static void
3106 open_run_request(enum request request)
3107 {
3108 struct run_request *req = get_run_request(request);
3109 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3111 if (!req) {
3112 report("Unknown run request");
3113 return;
3114 }
3116 if (format_argv(argv, req->argv, FORMAT_ALL))
3117 open_external_viewer(argv, NULL);
3118 free_argv(argv);
3119 }
3121 /*
3122 * User request switch noodle
3123 */
3125 static int
3126 view_driver(struct view *view, enum request request)
3127 {
3128 int i;
3130 if (request == REQ_NONE)
3131 return TRUE;
3133 if (request > REQ_NONE) {
3134 open_run_request(request);
3135 /* FIXME: When all views can refresh always do this. */
3136 if (view == VIEW(REQ_VIEW_STATUS) ||
3137 view == VIEW(REQ_VIEW_MAIN) ||
3138 view == VIEW(REQ_VIEW_LOG) ||
3139 view == VIEW(REQ_VIEW_BRANCH) ||
3140 view == VIEW(REQ_VIEW_STAGE))
3141 request = REQ_REFRESH;
3142 else
3143 return TRUE;
3144 }
3146 if (view && view->lines) {
3147 request = view->ops->request(view, request, &view->line[view->lineno]);
3148 if (request == REQ_NONE)
3149 return TRUE;
3150 }
3152 switch (request) {
3153 case REQ_MOVE_UP:
3154 case REQ_MOVE_DOWN:
3155 case REQ_MOVE_PAGE_UP:
3156 case REQ_MOVE_PAGE_DOWN:
3157 case REQ_MOVE_FIRST_LINE:
3158 case REQ_MOVE_LAST_LINE:
3159 move_view(view, request);
3160 break;
3162 case REQ_SCROLL_LEFT:
3163 case REQ_SCROLL_RIGHT:
3164 case REQ_SCROLL_LINE_DOWN:
3165 case REQ_SCROLL_LINE_UP:
3166 case REQ_SCROLL_PAGE_DOWN:
3167 case REQ_SCROLL_PAGE_UP:
3168 scroll_view(view, request);
3169 break;
3171 case REQ_VIEW_BLAME:
3172 if (!opt_file[0]) {
3173 report("No file chosen, press %s to open tree view",
3174 get_key(REQ_VIEW_TREE));
3175 break;
3176 }
3177 open_view(view, request, OPEN_DEFAULT);
3178 break;
3180 case REQ_VIEW_BLOB:
3181 if (!ref_blob[0]) {
3182 report("No file chosen, press %s to open tree view",
3183 get_key(REQ_VIEW_TREE));
3184 break;
3185 }
3186 open_view(view, request, OPEN_DEFAULT);
3187 break;
3189 case REQ_VIEW_PAGER:
3190 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3191 report("No pager content, press %s to run command from prompt",
3192 get_key(REQ_PROMPT));
3193 break;
3194 }
3195 open_view(view, request, OPEN_DEFAULT);
3196 break;
3198 case REQ_VIEW_STAGE:
3199 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3200 report("No stage content, press %s to open the status view and choose file",
3201 get_key(REQ_VIEW_STATUS));
3202 break;
3203 }
3204 open_view(view, request, OPEN_DEFAULT);
3205 break;
3207 case REQ_VIEW_STATUS:
3208 if (opt_is_inside_work_tree == FALSE) {
3209 report("The status view requires a working tree");
3210 break;
3211 }
3212 open_view(view, request, OPEN_DEFAULT);
3213 break;
3215 case REQ_VIEW_MAIN:
3216 case REQ_VIEW_DIFF:
3217 case REQ_VIEW_LOG:
3218 case REQ_VIEW_TREE:
3219 case REQ_VIEW_HELP:
3220 case REQ_VIEW_BRANCH:
3221 open_view(view, request, OPEN_DEFAULT);
3222 break;
3224 case REQ_NEXT:
3225 case REQ_PREVIOUS:
3226 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3228 if ((view == VIEW(REQ_VIEW_DIFF) &&
3229 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3230 (view == VIEW(REQ_VIEW_DIFF) &&
3231 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3232 (view == VIEW(REQ_VIEW_STAGE) &&
3233 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3234 (view == VIEW(REQ_VIEW_BLOB) &&
3235 view->parent == VIEW(REQ_VIEW_TREE)) ||
3236 (view == VIEW(REQ_VIEW_MAIN) &&
3237 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3238 int line;
3240 view = view->parent;
3241 line = view->lineno;
3242 move_view(view, request);
3243 if (view_is_displayed(view))
3244 update_view_title(view);
3245 if (line != view->lineno)
3246 view->ops->request(view, REQ_ENTER,
3247 &view->line[view->lineno]);
3249 } else {
3250 move_view(view, request);
3251 }
3252 break;
3254 case REQ_VIEW_NEXT:
3255 {
3256 int nviews = displayed_views();
3257 int next_view = (current_view + 1) % nviews;
3259 if (next_view == current_view) {
3260 report("Only one view is displayed");
3261 break;
3262 }
3264 current_view = next_view;
3265 /* Blur out the title of the previous view. */
3266 update_view_title(view);
3267 report("");
3268 break;
3269 }
3270 case REQ_REFRESH:
3271 report("Refreshing is not yet supported for the %s view", view->name);
3272 break;
3274 case REQ_MAXIMIZE:
3275 if (displayed_views() == 2)
3276 maximize_view(view);
3277 break;
3279 case REQ_TOGGLE_LINENO:
3280 toggle_view_option(&opt_line_number, "line numbers");
3281 break;
3283 case REQ_TOGGLE_DATE:
3284 toggle_view_option(&opt_date, "date display");
3285 break;
3287 case REQ_TOGGLE_AUTHOR:
3288 toggle_view_option(&opt_author, "author display");
3289 break;
3291 case REQ_TOGGLE_REV_GRAPH:
3292 toggle_view_option(&opt_rev_graph, "revision graph display");
3293 break;
3295 case REQ_TOGGLE_REFS:
3296 toggle_view_option(&opt_show_refs, "reference display");
3297 break;
3299 case REQ_TOGGLE_SORT_FIELD:
3300 case REQ_TOGGLE_SORT_ORDER:
3301 report("Sorting is not yet supported for the %s view", view->name);
3302 break;
3304 case REQ_SEARCH:
3305 case REQ_SEARCH_BACK:
3306 search_view(view, request);
3307 break;
3309 case REQ_FIND_NEXT:
3310 case REQ_FIND_PREV:
3311 find_next(view, request);
3312 break;
3314 case REQ_STOP_LOADING:
3315 for (i = 0; i < ARRAY_SIZE(views); i++) {
3316 view = &views[i];
3317 if (view->pipe)
3318 report("Stopped loading the %s view", view->name),
3319 end_update(view, TRUE);
3320 }
3321 break;
3323 case REQ_SHOW_VERSION:
3324 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3325 return TRUE;
3327 case REQ_SCREEN_REDRAW:
3328 redraw_display(TRUE);
3329 break;
3331 case REQ_EDIT:
3332 report("Nothing to edit");
3333 break;
3335 case REQ_ENTER:
3336 report("Nothing to enter");
3337 break;
3339 case REQ_VIEW_CLOSE:
3340 /* XXX: Mark closed views by letting view->parent point to the
3341 * view itself. Parents to closed view should never be
3342 * followed. */
3343 if (view->parent &&
3344 view->parent->parent != view->parent) {
3345 maximize_view(view->parent);
3346 view->parent = view;
3347 break;
3348 }
3349 /* Fall-through */
3350 case REQ_QUIT:
3351 return FALSE;
3353 default:
3354 report("Unknown key, press 'h' for help");
3355 return TRUE;
3356 }
3358 return TRUE;
3359 }
3362 /*
3363 * View backend utilities
3364 */
3366 enum sort_field {
3367 ORDERBY_NAME,
3368 ORDERBY_DATE,
3369 ORDERBY_AUTHOR,
3370 };
3372 struct sort_state {
3373 const enum sort_field *fields;
3374 size_t size, current;
3375 bool reverse;
3376 };
3378 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3379 #define get_sort_field(state) ((state).fields[(state).current])
3380 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3382 static void
3383 sort_view(struct view *view, enum request request, struct sort_state *state,
3384 int (*compare)(const void *, const void *))
3385 {
3386 switch (request) {
3387 case REQ_TOGGLE_SORT_FIELD:
3388 state->current = (state->current + 1) % state->size;
3389 break;
3391 case REQ_TOGGLE_SORT_ORDER:
3392 state->reverse = !state->reverse;
3393 break;
3394 default:
3395 die("Not a sort request");
3396 }
3398 qsort(view->line, view->lines, sizeof(*view->line), compare);
3399 redraw_view(view);
3400 }
3402 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3404 /* Small author cache to reduce memory consumption. It uses binary
3405 * search to lookup or find place to position new entries. No entries
3406 * are ever freed. */
3407 static const char *
3408 get_author(const char *name)
3409 {
3410 static const char **authors;
3411 static size_t authors_size;
3412 int from = 0, to = authors_size - 1;
3414 while (from <= to) {
3415 size_t pos = (to + from) / 2;
3416 int cmp = strcmp(name, authors[pos]);
3418 if (!cmp)
3419 return authors[pos];
3421 if (cmp < 0)
3422 to = pos - 1;
3423 else
3424 from = pos + 1;
3425 }
3427 if (!realloc_authors(&authors, authors_size, 1))
3428 return NULL;
3429 name = strdup(name);
3430 if (!name)
3431 return NULL;
3433 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3434 authors[from] = name;
3435 authors_size++;
3437 return name;
3438 }
3440 static void
3441 parse_timezone(time_t *time, const char *zone)
3442 {
3443 long tz;
3445 tz = ('0' - zone[1]) * 60 * 60 * 10;
3446 tz += ('0' - zone[2]) * 60 * 60;
3447 tz += ('0' - zone[3]) * 60;
3448 tz += ('0' - zone[4]);
3450 if (zone[0] == '-')
3451 tz = -tz;
3453 *time -= tz;
3454 }
3456 /* Parse author lines where the name may be empty:
3457 * author <email@address.tld> 1138474660 +0100
3458 */
3459 static void
3460 parse_author_line(char *ident, const char **author, time_t *time)
3461 {
3462 char *nameend = strchr(ident, '<');
3463 char *emailend = strchr(ident, '>');
3465 if (nameend && emailend)
3466 *nameend = *emailend = 0;
3467 ident = chomp_string(ident);
3468 if (!*ident) {
3469 if (nameend)
3470 ident = chomp_string(nameend + 1);
3471 if (!*ident)
3472 ident = "Unknown";
3473 }
3475 *author = get_author(ident);
3477 /* Parse epoch and timezone */
3478 if (emailend && emailend[1] == ' ') {
3479 char *secs = emailend + 2;
3480 char *zone = strchr(secs, ' ');
3482 *time = (time_t) atol(secs);
3484 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3485 parse_timezone(time, zone + 1);
3486 }
3487 }
3489 static enum input_status
3490 select_commit_parent_handler(void *data, char *buf, int c)
3491 {
3492 size_t parents = *(size_t *) data;
3493 int parent = 0;
3495 if (!isdigit(c))
3496 return INPUT_SKIP;
3498 if (*buf)
3499 parent = atoi(buf) * 10;
3500 parent += c - '0';
3502 if (parent > parents)
3503 return INPUT_SKIP;
3504 return INPUT_OK;
3505 }
3507 static bool
3508 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3509 {
3510 char buf[SIZEOF_STR * 4];
3511 const char *revlist_argv[] = {
3512 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3513 };
3514 int parents;
3516 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3517 (parents = (strlen(buf) / 40) - 1) < 0) {
3518 report("Failed to get parent information");
3519 return FALSE;
3521 } else if (parents == 0) {
3522 if (path)
3523 report("Path '%s' does not exist in the parent", path);
3524 else
3525 report("The selected commit has no parents");
3526 return FALSE;
3527 }
3529 if (parents > 1) {
3530 char prompt[SIZEOF_STR];
3531 char *result;
3533 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3534 return FALSE;
3535 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3536 if (!result)
3537 return FALSE;
3538 parents = atoi(result);
3539 }
3541 string_copy_rev(rev, &buf[41 * parents]);
3542 return TRUE;
3543 }
3545 /*
3546 * Pager backend
3547 */
3549 static bool
3550 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3551 {
3552 char text[SIZEOF_STR];
3554 if (opt_line_number && draw_lineno(view, lineno))
3555 return TRUE;
3557 string_expand(text, sizeof(text), line->data, opt_tab_size);
3558 draw_text(view, line->type, text, TRUE);
3559 return TRUE;
3560 }
3562 static bool
3563 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3564 {
3565 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3566 char ref[SIZEOF_STR];
3568 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3569 return TRUE;
3571 /* This is the only fatal call, since it can "corrupt" the buffer. */
3572 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3573 return FALSE;
3575 return TRUE;
3576 }
3578 static void
3579 add_pager_refs(struct view *view, struct line *line)
3580 {
3581 char buf[SIZEOF_STR];
3582 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3583 struct ref_list *list;
3584 size_t bufpos = 0, i;
3585 const char *sep = "Refs: ";
3586 bool is_tag = FALSE;
3588 assert(line->type == LINE_COMMIT);
3590 list = get_ref_list(commit_id);
3591 if (!list) {
3592 if (view == VIEW(REQ_VIEW_DIFF))
3593 goto try_add_describe_ref;
3594 return;
3595 }
3597 for (i = 0; i < list->size; i++) {
3598 struct ref *ref = list->refs[i];
3599 const char *fmt = ref->tag ? "%s[%s]" :
3600 ref->remote ? "%s<%s>" : "%s%s";
3602 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3603 return;
3604 sep = ", ";
3605 if (ref->tag)
3606 is_tag = TRUE;
3607 }
3609 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3610 try_add_describe_ref:
3611 /* Add <tag>-g<commit_id> "fake" reference. */
3612 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3613 return;
3614 }
3616 if (bufpos == 0)
3617 return;
3619 add_line_text(view, buf, LINE_PP_REFS);
3620 }
3622 static bool
3623 pager_read(struct view *view, char *data)
3624 {
3625 struct line *line;
3627 if (!data)
3628 return TRUE;
3630 line = add_line_text(view, data, get_line_type(data));
3631 if (!line)
3632 return FALSE;
3634 if (line->type == LINE_COMMIT &&
3635 (view == VIEW(REQ_VIEW_DIFF) ||
3636 view == VIEW(REQ_VIEW_LOG)))
3637 add_pager_refs(view, line);
3639 return TRUE;
3640 }
3642 static enum request
3643 pager_request(struct view *view, enum request request, struct line *line)
3644 {
3645 int split = 0;
3647 if (request != REQ_ENTER)
3648 return request;
3650 if (line->type == LINE_COMMIT &&
3651 (view == VIEW(REQ_VIEW_LOG) ||
3652 view == VIEW(REQ_VIEW_PAGER))) {
3653 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3654 split = 1;
3655 }
3657 /* Always scroll the view even if it was split. That way
3658 * you can use Enter to scroll through the log view and
3659 * split open each commit diff. */
3660 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3662 /* FIXME: A minor workaround. Scrolling the view will call report("")
3663 * but if we are scrolling a non-current view this won't properly
3664 * update the view title. */
3665 if (split)
3666 update_view_title(view);
3668 return REQ_NONE;
3669 }
3671 static bool
3672 pager_grep(struct view *view, struct line *line)
3673 {
3674 const char *text[] = { line->data, NULL };
3676 return grep_text(view, text);
3677 }
3679 static void
3680 pager_select(struct view *view, struct line *line)
3681 {
3682 if (line->type == LINE_COMMIT) {
3683 char *text = (char *)line->data + STRING_SIZE("commit ");
3685 if (view != VIEW(REQ_VIEW_PAGER))
3686 string_copy_rev(view->ref, text);
3687 string_copy_rev(ref_commit, text);
3688 }
3689 }
3691 static struct view_ops pager_ops = {
3692 "line",
3693 NULL,
3694 NULL,
3695 pager_read,
3696 pager_draw,
3697 pager_request,
3698 pager_grep,
3699 pager_select,
3700 };
3702 static const char *log_argv[SIZEOF_ARG] = {
3703 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3704 };
3706 static enum request
3707 log_request(struct view *view, enum request request, struct line *line)
3708 {
3709 switch (request) {
3710 case REQ_REFRESH:
3711 load_refs();
3712 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3713 return REQ_NONE;
3714 default:
3715 return pager_request(view, request, line);
3716 }
3717 }
3719 static struct view_ops log_ops = {
3720 "line",
3721 log_argv,
3722 NULL,
3723 pager_read,
3724 pager_draw,
3725 log_request,
3726 pager_grep,
3727 pager_select,
3728 };
3730 static const char *diff_argv[SIZEOF_ARG] = {
3731 "git", "show", "--pretty=fuller", "--no-color", "--root",
3732 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3733 };
3735 static struct view_ops diff_ops = {
3736 "line",
3737 diff_argv,
3738 NULL,
3739 pager_read,
3740 pager_draw,
3741 pager_request,
3742 pager_grep,
3743 pager_select,
3744 };
3746 /*
3747 * Help backend
3748 */
3750 static bool
3751 help_open(struct view *view)
3752 {
3753 char buf[SIZEOF_STR];
3754 size_t bufpos;
3755 int i;
3757 if (view->lines > 0)
3758 return TRUE;
3760 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3762 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3763 const char *key;
3765 if (req_info[i].request == REQ_NONE)
3766 continue;
3768 if (!req_info[i].request) {
3769 add_line_text(view, "", LINE_DEFAULT);
3770 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3771 continue;
3772 }
3774 key = get_key(req_info[i].request);
3775 if (!*key)
3776 key = "(no key defined)";
3778 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3779 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3780 if (buf[bufpos] == '_')
3781 buf[bufpos] = '-';
3782 }
3784 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3785 key, buf, req_info[i].help);
3786 }
3788 if (run_requests) {
3789 add_line_text(view, "", LINE_DEFAULT);
3790 add_line_text(view, "External commands:", LINE_DEFAULT);
3791 }
3793 for (i = 0; i < run_requests; i++) {
3794 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3795 const char *key;
3796 int argc;
3798 if (!req)
3799 continue;
3801 key = get_key_name(req->key);
3802 if (!*key)
3803 key = "(no key defined)";
3805 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3806 if (!string_format_from(buf, &bufpos, "%s%s",
3807 argc ? " " : "", req->argv[argc]))
3808 return REQ_NONE;
3810 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3811 keymap_table[req->keymap].name, key, buf);
3812 }
3814 return TRUE;
3815 }
3817 static struct view_ops help_ops = {
3818 "line",
3819 NULL,
3820 help_open,
3821 NULL,
3822 pager_draw,
3823 pager_request,
3824 pager_grep,
3825 pager_select,
3826 };
3829 /*
3830 * Tree backend
3831 */
3833 struct tree_stack_entry {
3834 struct tree_stack_entry *prev; /* Entry below this in the stack */
3835 unsigned long lineno; /* Line number to restore */
3836 char *name; /* Position of name in opt_path */
3837 };
3839 /* The top of the path stack. */
3840 static struct tree_stack_entry *tree_stack = NULL;
3841 unsigned long tree_lineno = 0;
3843 static void
3844 pop_tree_stack_entry(void)
3845 {
3846 struct tree_stack_entry *entry = tree_stack;
3848 tree_lineno = entry->lineno;
3849 entry->name[0] = 0;
3850 tree_stack = entry->prev;
3851 free(entry);
3852 }
3854 static void
3855 push_tree_stack_entry(const char *name, unsigned long lineno)
3856 {
3857 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3858 size_t pathlen = strlen(opt_path);
3860 if (!entry)
3861 return;
3863 entry->prev = tree_stack;
3864 entry->name = opt_path + pathlen;
3865 tree_stack = entry;
3867 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3868 pop_tree_stack_entry();
3869 return;
3870 }
3872 /* Move the current line to the first tree entry. */
3873 tree_lineno = 1;
3874 entry->lineno = lineno;
3875 }
3877 /* Parse output from git-ls-tree(1):
3878 *
3879 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3880 */
3882 #define SIZEOF_TREE_ATTR \
3883 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3885 #define SIZEOF_TREE_MODE \
3886 STRING_SIZE("100644 ")
3888 #define TREE_ID_OFFSET \
3889 STRING_SIZE("100644 blob ")
3891 struct tree_entry {
3892 char id[SIZEOF_REV];
3893 mode_t mode;
3894 time_t time; /* Date from the author ident. */
3895 const char *author; /* Author of the commit. */
3896 char name[1];
3897 };
3899 static const char *
3900 tree_path(const struct line *line)
3901 {
3902 return ((struct tree_entry *) line->data)->name;
3903 }
3905 static int
3906 tree_compare_entry(const struct line *line1, const struct line *line2)
3907 {
3908 if (line1->type != line2->type)
3909 return line1->type == LINE_TREE_DIR ? -1 : 1;
3910 return strcmp(tree_path(line1), tree_path(line2));
3911 }
3913 static const enum sort_field tree_sort_fields[] = {
3914 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
3915 };
3916 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
3918 static int
3919 tree_compare(const void *l1, const void *l2)
3920 {
3921 const struct line *line1 = (const struct line *) l1;
3922 const struct line *line2 = (const struct line *) l2;
3923 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
3924 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
3926 if (line1->type == LINE_TREE_HEAD)
3927 return -1;
3928 if (line2->type == LINE_TREE_HEAD)
3929 return 1;
3931 switch (get_sort_field(tree_sort_state)) {
3932 case ORDERBY_DATE:
3933 return sort_order(tree_sort_state, entry1->time - entry2->time);
3935 case ORDERBY_AUTHOR:
3936 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
3938 case ORDERBY_NAME:
3939 default:
3940 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
3941 }
3942 }
3945 static struct line *
3946 tree_entry(struct view *view, enum line_type type, const char *path,
3947 const char *mode, const char *id)
3948 {
3949 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3950 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3952 if (!entry || !line) {
3953 free(entry);
3954 return NULL;
3955 }
3957 strncpy(entry->name, path, strlen(path));
3958 if (mode)
3959 entry->mode = strtoul(mode, NULL, 8);
3960 if (id)
3961 string_copy_rev(entry->id, id);
3963 return line;
3964 }
3966 static bool
3967 tree_read_date(struct view *view, char *text, bool *read_date)
3968 {
3969 static const char *author_name;
3970 static time_t author_time;
3972 if (!text && *read_date) {
3973 *read_date = FALSE;
3974 return TRUE;
3976 } else if (!text) {
3977 char *path = *opt_path ? opt_path : ".";
3978 /* Find next entry to process */
3979 const char *log_file[] = {
3980 "git", "log", "--no-color", "--pretty=raw",
3981 "--cc", "--raw", view->id, "--", path, NULL
3982 };
3983 struct io io = {};
3985 if (!view->lines) {
3986 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3987 report("Tree is empty");
3988 return TRUE;
3989 }
3991 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3992 report("Failed to load tree data");
3993 return TRUE;
3994 }
3996 done_io(view->pipe);
3997 view->io = io;
3998 *read_date = TRUE;
3999 return FALSE;
4001 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4002 parse_author_line(text + STRING_SIZE("author "),
4003 &author_name, &author_time);
4005 } else if (*text == ':') {
4006 char *pos;
4007 size_t annotated = 1;
4008 size_t i;
4010 pos = strchr(text, '\t');
4011 if (!pos)
4012 return TRUE;
4013 text = pos + 1;
4014 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4015 text += strlen(opt_prefix);
4016 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4017 text += strlen(opt_path);
4018 pos = strchr(text, '/');
4019 if (pos)
4020 *pos = 0;
4022 for (i = 1; i < view->lines; i++) {
4023 struct line *line = &view->line[i];
4024 struct tree_entry *entry = line->data;
4026 annotated += !!entry->author;
4027 if (entry->author || strcmp(entry->name, text))
4028 continue;
4030 entry->author = author_name;
4031 entry->time = author_time;
4032 line->dirty = 1;
4033 break;
4034 }
4036 if (annotated == view->lines)
4037 kill_io(view->pipe);
4038 }
4039 return TRUE;
4040 }
4042 static bool
4043 tree_read(struct view *view, char *text)
4044 {
4045 static bool read_date = FALSE;
4046 struct tree_entry *data;
4047 struct line *entry, *line;
4048 enum line_type type;
4049 size_t textlen = text ? strlen(text) : 0;
4050 char *path = text + SIZEOF_TREE_ATTR;
4052 if (read_date || !text)
4053 return tree_read_date(view, text, &read_date);
4055 if (textlen <= SIZEOF_TREE_ATTR)
4056 return FALSE;
4057 if (view->lines == 0 &&
4058 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4059 return FALSE;
4061 /* Strip the path part ... */
4062 if (*opt_path) {
4063 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4064 size_t striplen = strlen(opt_path);
4066 if (pathlen > striplen)
4067 memmove(path, path + striplen,
4068 pathlen - striplen + 1);
4070 /* Insert "link" to parent directory. */
4071 if (view->lines == 1 &&
4072 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4073 return FALSE;
4074 }
4076 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4077 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4078 if (!entry)
4079 return FALSE;
4080 data = entry->data;
4082 /* Skip "Directory ..." and ".." line. */
4083 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4084 if (tree_compare_entry(line, entry) <= 0)
4085 continue;
4087 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4089 line->data = data;
4090 line->type = type;
4091 for (; line <= entry; line++)
4092 line->dirty = line->cleareol = 1;
4093 return TRUE;
4094 }
4096 if (tree_lineno > view->lineno) {
4097 view->lineno = tree_lineno;
4098 tree_lineno = 0;
4099 }
4101 return TRUE;
4102 }
4104 static bool
4105 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4106 {
4107 struct tree_entry *entry = line->data;
4109 if (line->type == LINE_TREE_HEAD) {
4110 if (draw_text(view, line->type, "Directory path /", TRUE))
4111 return TRUE;
4112 } else {
4113 if (draw_mode(view, entry->mode))
4114 return TRUE;
4116 if (opt_author && draw_author(view, entry->author))
4117 return TRUE;
4119 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4120 return TRUE;
4121 }
4122 if (draw_text(view, line->type, entry->name, TRUE))
4123 return TRUE;
4124 return TRUE;
4125 }
4127 static void
4128 open_blob_editor()
4129 {
4130 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4131 int fd = mkstemp(file);
4133 if (fd == -1)
4134 report("Failed to create temporary file");
4135 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4136 report("Failed to save blob data to file");
4137 else
4138 open_editor(FALSE, file);
4139 if (fd != -1)
4140 unlink(file);
4141 }
4143 static enum request
4144 tree_request(struct view *view, enum request request, struct line *line)
4145 {
4146 enum open_flags flags;
4148 switch (request) {
4149 case REQ_VIEW_BLAME:
4150 if (line->type != LINE_TREE_FILE) {
4151 report("Blame only supported for files");
4152 return REQ_NONE;
4153 }
4155 string_copy(opt_ref, view->vid);
4156 return request;
4158 case REQ_EDIT:
4159 if (line->type != LINE_TREE_FILE) {
4160 report("Edit only supported for files");
4161 } else if (!is_head_commit(view->vid)) {
4162 open_blob_editor();
4163 } else {
4164 open_editor(TRUE, opt_file);
4165 }
4166 return REQ_NONE;
4168 case REQ_TOGGLE_SORT_FIELD:
4169 case REQ_TOGGLE_SORT_ORDER:
4170 sort_view(view, request, &tree_sort_state, tree_compare);
4171 return REQ_NONE;
4173 case REQ_PARENT:
4174 if (!*opt_path) {
4175 /* quit view if at top of tree */
4176 return REQ_VIEW_CLOSE;
4177 }
4178 /* fake 'cd ..' */
4179 line = &view->line[1];
4180 break;
4182 case REQ_ENTER:
4183 break;
4185 default:
4186 return request;
4187 }
4189 /* Cleanup the stack if the tree view is at a different tree. */
4190 while (!*opt_path && tree_stack)
4191 pop_tree_stack_entry();
4193 switch (line->type) {
4194 case LINE_TREE_DIR:
4195 /* Depending on whether it is a subdirectory or parent link
4196 * mangle the path buffer. */
4197 if (line == &view->line[1] && *opt_path) {
4198 pop_tree_stack_entry();
4200 } else {
4201 const char *basename = tree_path(line);
4203 push_tree_stack_entry(basename, view->lineno);
4204 }
4206 /* Trees and subtrees share the same ID, so they are not not
4207 * unique like blobs. */
4208 flags = OPEN_RELOAD;
4209 request = REQ_VIEW_TREE;
4210 break;
4212 case LINE_TREE_FILE:
4213 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4214 request = REQ_VIEW_BLOB;
4215 break;
4217 default:
4218 return REQ_NONE;
4219 }
4221 open_view(view, request, flags);
4222 if (request == REQ_VIEW_TREE)
4223 view->lineno = tree_lineno;
4225 return REQ_NONE;
4226 }
4228 static bool
4229 tree_grep(struct view *view, struct line *line)
4230 {
4231 struct tree_entry *entry = line->data;
4232 const char *text[] = {
4233 entry->name,
4234 opt_author ? entry->author : "",
4235 opt_date ? mkdate(&entry->time) : "",
4236 NULL
4237 };
4239 return grep_text(view, text);
4240 }
4242 static void
4243 tree_select(struct view *view, struct line *line)
4244 {
4245 struct tree_entry *entry = line->data;
4247 if (line->type == LINE_TREE_FILE) {
4248 string_copy_rev(ref_blob, entry->id);
4249 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4251 } else if (line->type != LINE_TREE_DIR) {
4252 return;
4253 }
4255 string_copy_rev(view->ref, entry->id);
4256 }
4258 static const char *tree_argv[SIZEOF_ARG] = {
4259 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4260 };
4262 static struct view_ops tree_ops = {
4263 "file",
4264 tree_argv,
4265 NULL,
4266 tree_read,
4267 tree_draw,
4268 tree_request,
4269 tree_grep,
4270 tree_select,
4271 };
4273 static bool
4274 blob_read(struct view *view, char *line)
4275 {
4276 if (!line)
4277 return TRUE;
4278 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4279 }
4281 static enum request
4282 blob_request(struct view *view, enum request request, struct line *line)
4283 {
4284 switch (request) {
4285 case REQ_EDIT:
4286 open_blob_editor();
4287 return REQ_NONE;
4288 default:
4289 return pager_request(view, request, line);
4290 }
4291 }
4293 static const char *blob_argv[SIZEOF_ARG] = {
4294 "git", "cat-file", "blob", "%(blob)", NULL
4295 };
4297 static struct view_ops blob_ops = {
4298 "line",
4299 blob_argv,
4300 NULL,
4301 blob_read,
4302 pager_draw,
4303 blob_request,
4304 pager_grep,
4305 pager_select,
4306 };
4308 /*
4309 * Blame backend
4310 *
4311 * Loading the blame view is a two phase job:
4312 *
4313 * 1. File content is read either using opt_file from the
4314 * filesystem or using git-cat-file.
4315 * 2. Then blame information is incrementally added by
4316 * reading output from git-blame.
4317 */
4319 static const char *blame_head_argv[] = {
4320 "git", "blame", "--incremental", "--", "%(file)", NULL
4321 };
4323 static const char *blame_ref_argv[] = {
4324 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4325 };
4327 static const char *blame_cat_file_argv[] = {
4328 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4329 };
4331 struct blame_commit {
4332 char id[SIZEOF_REV]; /* SHA1 ID. */
4333 char title[128]; /* First line of the commit message. */
4334 const char *author; /* Author of the commit. */
4335 time_t time; /* Date from the author ident. */
4336 char filename[128]; /* Name of file. */
4337 bool has_previous; /* Was a "previous" line detected. */
4338 };
4340 struct blame {
4341 struct blame_commit *commit;
4342 unsigned long lineno;
4343 char text[1];
4344 };
4346 static bool
4347 blame_open(struct view *view)
4348 {
4349 if (*opt_ref || !io_open(&view->io, opt_file)) {
4350 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4351 return FALSE;
4352 }
4354 setup_update(view, opt_file);
4355 string_format(view->ref, "%s ...", opt_file);
4357 return TRUE;
4358 }
4360 static struct blame_commit *
4361 get_blame_commit(struct view *view, const char *id)
4362 {
4363 size_t i;
4365 for (i = 0; i < view->lines; i++) {
4366 struct blame *blame = view->line[i].data;
4368 if (!blame->commit)
4369 continue;
4371 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4372 return blame->commit;
4373 }
4375 {
4376 struct blame_commit *commit = calloc(1, sizeof(*commit));
4378 if (commit)
4379 string_ncopy(commit->id, id, SIZEOF_REV);
4380 return commit;
4381 }
4382 }
4384 static bool
4385 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4386 {
4387 const char *pos = *posref;
4389 *posref = NULL;
4390 pos = strchr(pos + 1, ' ');
4391 if (!pos || !isdigit(pos[1]))
4392 return FALSE;
4393 *number = atoi(pos + 1);
4394 if (*number < min || *number > max)
4395 return FALSE;
4397 *posref = pos;
4398 return TRUE;
4399 }
4401 static struct blame_commit *
4402 parse_blame_commit(struct view *view, const char *text, int *blamed)
4403 {
4404 struct blame_commit *commit;
4405 struct blame *blame;
4406 const char *pos = text + SIZEOF_REV - 2;
4407 size_t orig_lineno = 0;
4408 size_t lineno;
4409 size_t group;
4411 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4412 return NULL;
4414 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4415 !parse_number(&pos, &lineno, 1, view->lines) ||
4416 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4417 return NULL;
4419 commit = get_blame_commit(view, text);
4420 if (!commit)
4421 return NULL;
4423 *blamed += group;
4424 while (group--) {
4425 struct line *line = &view->line[lineno + group - 1];
4427 blame = line->data;
4428 blame->commit = commit;
4429 blame->lineno = orig_lineno + group - 1;
4430 line->dirty = 1;
4431 }
4433 return commit;
4434 }
4436 static bool
4437 blame_read_file(struct view *view, const char *line, bool *read_file)
4438 {
4439 if (!line) {
4440 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4441 struct io io = {};
4443 if (view->lines == 0 && !view->parent)
4444 die("No blame exist for %s", view->vid);
4446 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4447 report("Failed to load blame data");
4448 return TRUE;
4449 }
4451 done_io(view->pipe);
4452 view->io = io;
4453 *read_file = FALSE;
4454 return FALSE;
4456 } else {
4457 size_t linelen = strlen(line);
4458 struct blame *blame = malloc(sizeof(*blame) + linelen);
4460 if (!blame)
4461 return FALSE;
4463 blame->commit = NULL;
4464 strncpy(blame->text, line, linelen);
4465 blame->text[linelen] = 0;
4466 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4467 }
4468 }
4470 static bool
4471 match_blame_header(const char *name, char **line)
4472 {
4473 size_t namelen = strlen(name);
4474 bool matched = !strncmp(name, *line, namelen);
4476 if (matched)
4477 *line += namelen;
4479 return matched;
4480 }
4482 static bool
4483 blame_read(struct view *view, char *line)
4484 {
4485 static struct blame_commit *commit = NULL;
4486 static int blamed = 0;
4487 static bool read_file = TRUE;
4489 if (read_file)
4490 return blame_read_file(view, line, &read_file);
4492 if (!line) {
4493 /* Reset all! */
4494 commit = NULL;
4495 blamed = 0;
4496 read_file = TRUE;
4497 string_format(view->ref, "%s", view->vid);
4498 if (view_is_displayed(view)) {
4499 update_view_title(view);
4500 redraw_view_from(view, 0);
4501 }
4502 return TRUE;
4503 }
4505 if (!commit) {
4506 commit = parse_blame_commit(view, line, &blamed);
4507 string_format(view->ref, "%s %2d%%", view->vid,
4508 view->lines ? blamed * 100 / view->lines : 0);
4510 } else if (match_blame_header("author ", &line)) {
4511 commit->author = get_author(line);
4513 } else if (match_blame_header("author-time ", &line)) {
4514 commit->time = (time_t) atol(line);
4516 } else if (match_blame_header("author-tz ", &line)) {
4517 parse_timezone(&commit->time, line);
4519 } else if (match_blame_header("summary ", &line)) {
4520 string_ncopy(commit->title, line, strlen(line));
4522 } else if (match_blame_header("previous ", &line)) {
4523 commit->has_previous = TRUE;
4525 } else if (match_blame_header("filename ", &line)) {
4526 string_ncopy(commit->filename, line, strlen(line));
4527 commit = NULL;
4528 }
4530 return TRUE;
4531 }
4533 static bool
4534 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4535 {
4536 struct blame *blame = line->data;
4537 time_t *time = NULL;
4538 const char *id = NULL, *author = NULL;
4539 char text[SIZEOF_STR];
4541 if (blame->commit && *blame->commit->filename) {
4542 id = blame->commit->id;
4543 author = blame->commit->author;
4544 time = &blame->commit->time;
4545 }
4547 if (opt_date && draw_date(view, time))
4548 return TRUE;
4550 if (opt_author && draw_author(view, author))
4551 return TRUE;
4553 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4554 return TRUE;
4556 if (draw_lineno(view, lineno))
4557 return TRUE;
4559 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4560 draw_text(view, LINE_DEFAULT, text, TRUE);
4561 return TRUE;
4562 }
4564 static bool
4565 check_blame_commit(struct blame *blame, bool check_null_id)
4566 {
4567 if (!blame->commit)
4568 report("Commit data not loaded yet");
4569 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4570 report("No commit exist for the selected line");
4571 else
4572 return TRUE;
4573 return FALSE;
4574 }
4576 static void
4577 setup_blame_parent_line(struct view *view, struct blame *blame)
4578 {
4579 const char *diff_tree_argv[] = {
4580 "git", "diff-tree", "-U0", blame->commit->id,
4581 "--", blame->commit->filename, NULL
4582 };
4583 struct io io = {};
4584 int parent_lineno = -1;
4585 int blamed_lineno = -1;
4586 char *line;
4588 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4589 return;
4591 while ((line = io_get(&io, '\n', TRUE))) {
4592 if (*line == '@') {
4593 char *pos = strchr(line, '+');
4595 parent_lineno = atoi(line + 4);
4596 if (pos)
4597 blamed_lineno = atoi(pos + 1);
4599 } else if (*line == '+' && parent_lineno != -1) {
4600 if (blame->lineno == blamed_lineno - 1 &&
4601 !strcmp(blame->text, line + 1)) {
4602 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4603 break;
4604 }
4605 blamed_lineno++;
4606 }
4607 }
4609 done_io(&io);
4610 }
4612 static enum request
4613 blame_request(struct view *view, enum request request, struct line *line)
4614 {
4615 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4616 struct blame *blame = line->data;
4618 switch (request) {
4619 case REQ_VIEW_BLAME:
4620 if (check_blame_commit(blame, TRUE)) {
4621 string_copy(opt_ref, blame->commit->id);
4622 string_copy(opt_file, blame->commit->filename);
4623 if (blame->lineno)
4624 view->lineno = blame->lineno;
4625 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4626 }
4627 break;
4629 case REQ_PARENT:
4630 if (check_blame_commit(blame, TRUE) &&
4631 select_commit_parent(blame->commit->id, opt_ref,
4632 blame->commit->filename)) {
4633 string_copy(opt_file, blame->commit->filename);
4634 setup_blame_parent_line(view, blame);
4635 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4636 }
4637 break;
4639 case REQ_ENTER:
4640 if (!check_blame_commit(blame, FALSE))
4641 break;
4643 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4644 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4645 break;
4647 if (!strcmp(blame->commit->id, NULL_ID)) {
4648 struct view *diff = VIEW(REQ_VIEW_DIFF);
4649 const char *diff_index_argv[] = {
4650 "git", "diff-index", "--root", "--patch-with-stat",
4651 "-C", "-M", "HEAD", "--", view->vid, NULL
4652 };
4654 if (!blame->commit->has_previous) {
4655 diff_index_argv[1] = "diff";
4656 diff_index_argv[2] = "--no-color";
4657 diff_index_argv[6] = "--";
4658 diff_index_argv[7] = "/dev/null";
4659 }
4661 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4662 report("Failed to allocate diff command");
4663 break;
4664 }
4665 flags |= OPEN_PREPARED;
4666 }
4668 open_view(view, REQ_VIEW_DIFF, flags);
4669 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4670 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4671 break;
4673 default:
4674 return request;
4675 }
4677 return REQ_NONE;
4678 }
4680 static bool
4681 blame_grep(struct view *view, struct line *line)
4682 {
4683 struct blame *blame = line->data;
4684 struct blame_commit *commit = blame->commit;
4685 const char *text[] = {
4686 blame->text,
4687 commit ? commit->title : "",
4688 commit ? commit->id : "",
4689 commit && opt_author ? commit->author : "",
4690 commit && opt_date ? mkdate(&commit->time) : "",
4691 NULL
4692 };
4694 return grep_text(view, text);
4695 }
4697 static void
4698 blame_select(struct view *view, struct line *line)
4699 {
4700 struct blame *blame = line->data;
4701 struct blame_commit *commit = blame->commit;
4703 if (!commit)
4704 return;
4706 if (!strcmp(commit->id, NULL_ID))
4707 string_ncopy(ref_commit, "HEAD", 4);
4708 else
4709 string_copy_rev(ref_commit, commit->id);
4710 }
4712 static struct view_ops blame_ops = {
4713 "line",
4714 NULL,
4715 blame_open,
4716 blame_read,
4717 blame_draw,
4718 blame_request,
4719 blame_grep,
4720 blame_select,
4721 };
4723 /*
4724 * Branch backend
4725 */
4727 struct branch {
4728 const char *author; /* Author of the last commit. */
4729 time_t time; /* Date of the last activity. */
4730 struct ref *ref; /* Name and commit ID information. */
4731 };
4733 static const enum sort_field branch_sort_fields[] = {
4734 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4735 };
4736 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4738 static int
4739 branch_compare(const void *l1, const void *l2)
4740 {
4741 const struct branch *branch1 = ((const struct line *) l1)->data;
4742 const struct branch *branch2 = ((const struct line *) l2)->data;
4744 switch (get_sort_field(branch_sort_state)) {
4745 case ORDERBY_DATE:
4746 return sort_order(branch_sort_state, branch1->time - branch2->time);
4748 case ORDERBY_AUTHOR:
4749 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4751 case ORDERBY_NAME:
4752 default:
4753 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4754 }
4755 }
4757 static bool
4758 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4759 {
4760 struct branch *branch = line->data;
4761 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4763 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4764 return TRUE;
4766 if (opt_author && draw_author(view, branch->author))
4767 return TRUE;
4769 draw_text(view, type, branch->ref->name, TRUE);
4770 return TRUE;
4771 }
4773 static enum request
4774 branch_request(struct view *view, enum request request, struct line *line)
4775 {
4776 switch (request) {
4777 case REQ_REFRESH:
4778 load_refs();
4779 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4780 return REQ_NONE;
4782 case REQ_TOGGLE_SORT_FIELD:
4783 case REQ_TOGGLE_SORT_ORDER:
4784 sort_view(view, request, &branch_sort_state, branch_compare);
4785 return REQ_NONE;
4787 case REQ_ENTER:
4788 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4789 return REQ_NONE;
4791 default:
4792 return request;
4793 }
4794 }
4796 static bool
4797 branch_read(struct view *view, char *line)
4798 {
4799 static char id[SIZEOF_REV];
4800 struct branch *reference;
4801 size_t i;
4803 if (!line)
4804 return TRUE;
4806 switch (get_line_type(line)) {
4807 case LINE_COMMIT:
4808 string_copy_rev(id, line + STRING_SIZE("commit "));
4809 return TRUE;
4811 case LINE_AUTHOR:
4812 for (i = 0, reference = NULL; i < view->lines; i++) {
4813 struct branch *branch = view->line[i].data;
4815 if (strcmp(branch->ref->id, id))
4816 continue;
4818 view->line[i].dirty = TRUE;
4819 if (reference) {
4820 branch->author = reference->author;
4821 branch->time = reference->time;
4822 continue;
4823 }
4825 parse_author_line(line + STRING_SIZE("author "),
4826 &branch->author, &branch->time);
4827 reference = branch;
4828 }
4829 return TRUE;
4831 default:
4832 return TRUE;
4833 }
4835 }
4837 static bool
4838 branch_open_visitor(void *data, struct ref *ref)
4839 {
4840 struct view *view = data;
4841 struct branch *branch;
4843 if (ref->tag || ref->ltag || ref->remote)
4844 return TRUE;
4846 branch = calloc(1, sizeof(*branch));
4847 if (!branch)
4848 return FALSE;
4850 branch->ref = ref;
4851 return !!add_line_data(view, branch, LINE_DEFAULT);
4852 }
4854 static bool
4855 branch_open(struct view *view)
4856 {
4857 const char *branch_log[] = {
4858 "git", "log", "--no-color", "--pretty=raw",
4859 "--simplify-by-decoration", "--all", NULL
4860 };
4862 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4863 report("Failed to load branch data");
4864 return TRUE;
4865 }
4867 setup_update(view, view->id);
4868 foreach_ref(branch_open_visitor, view);
4870 return TRUE;
4871 }
4873 static bool
4874 branch_grep(struct view *view, struct line *line)
4875 {
4876 struct branch *branch = line->data;
4877 const char *text[] = {
4878 branch->ref->name,
4879 branch->author,
4880 NULL
4881 };
4883 return grep_text(view, text);
4884 }
4886 static void
4887 branch_select(struct view *view, struct line *line)
4888 {
4889 struct branch *branch = line->data;
4891 string_copy_rev(view->ref, branch->ref->id);
4892 string_copy_rev(ref_commit, branch->ref->id);
4893 string_copy_rev(ref_head, branch->ref->id);
4894 }
4896 static struct view_ops branch_ops = {
4897 "branch",
4898 NULL,
4899 branch_open,
4900 branch_read,
4901 branch_draw,
4902 branch_request,
4903 branch_grep,
4904 branch_select,
4905 };
4907 /*
4908 * Status backend
4909 */
4911 struct status {
4912 char status;
4913 struct {
4914 mode_t mode;
4915 char rev[SIZEOF_REV];
4916 char name[SIZEOF_STR];
4917 } old;
4918 struct {
4919 mode_t mode;
4920 char rev[SIZEOF_REV];
4921 char name[SIZEOF_STR];
4922 } new;
4923 };
4925 static char status_onbranch[SIZEOF_STR];
4926 static struct status stage_status;
4927 static enum line_type stage_line_type;
4928 static size_t stage_chunks;
4929 static int *stage_chunk;
4931 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4933 /* This should work even for the "On branch" line. */
4934 static inline bool
4935 status_has_none(struct view *view, struct line *line)
4936 {
4937 return line < view->line + view->lines && !line[1].data;
4938 }
4940 /* Get fields from the diff line:
4941 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4942 */
4943 static inline bool
4944 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4945 {
4946 const char *old_mode = buf + 1;
4947 const char *new_mode = buf + 8;
4948 const char *old_rev = buf + 15;
4949 const char *new_rev = buf + 56;
4950 const char *status = buf + 97;
4952 if (bufsize < 98 ||
4953 old_mode[-1] != ':' ||
4954 new_mode[-1] != ' ' ||
4955 old_rev[-1] != ' ' ||
4956 new_rev[-1] != ' ' ||
4957 status[-1] != ' ')
4958 return FALSE;
4960 file->status = *status;
4962 string_copy_rev(file->old.rev, old_rev);
4963 string_copy_rev(file->new.rev, new_rev);
4965 file->old.mode = strtoul(old_mode, NULL, 8);
4966 file->new.mode = strtoul(new_mode, NULL, 8);
4968 file->old.name[0] = file->new.name[0] = 0;
4970 return TRUE;
4971 }
4973 static bool
4974 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4975 {
4976 struct status *unmerged = NULL;
4977 char *buf;
4978 struct io io = {};
4980 if (!run_io(&io, argv, NULL, IO_RD))
4981 return FALSE;
4983 add_line_data(view, NULL, type);
4985 while ((buf = io_get(&io, 0, TRUE))) {
4986 struct status *file = unmerged;
4988 if (!file) {
4989 file = calloc(1, sizeof(*file));
4990 if (!file || !add_line_data(view, file, type))
4991 goto error_out;
4992 }
4994 /* Parse diff info part. */
4995 if (status) {
4996 file->status = status;
4997 if (status == 'A')
4998 string_copy(file->old.rev, NULL_ID);
5000 } else if (!file->status || file == unmerged) {
5001 if (!status_get_diff(file, buf, strlen(buf)))
5002 goto error_out;
5004 buf = io_get(&io, 0, TRUE);
5005 if (!buf)
5006 break;
5008 /* Collapse all modified entries that follow an
5009 * associated unmerged entry. */
5010 if (unmerged == file) {
5011 unmerged->status = 'U';
5012 unmerged = NULL;
5013 } else if (file->status == 'U') {
5014 unmerged = file;
5015 }
5016 }
5018 /* Grab the old name for rename/copy. */
5019 if (!*file->old.name &&
5020 (file->status == 'R' || file->status == 'C')) {
5021 string_ncopy(file->old.name, buf, strlen(buf));
5023 buf = io_get(&io, 0, TRUE);
5024 if (!buf)
5025 break;
5026 }
5028 /* git-ls-files just delivers a NUL separated list of
5029 * file names similar to the second half of the
5030 * git-diff-* output. */
5031 string_ncopy(file->new.name, buf, strlen(buf));
5032 if (!*file->old.name)
5033 string_copy(file->old.name, file->new.name);
5034 file = NULL;
5035 }
5037 if (io_error(&io)) {
5038 error_out:
5039 done_io(&io);
5040 return FALSE;
5041 }
5043 if (!view->line[view->lines - 1].data)
5044 add_line_data(view, NULL, LINE_STAT_NONE);
5046 done_io(&io);
5047 return TRUE;
5048 }
5050 /* Don't show unmerged entries in the staged section. */
5051 static const char *status_diff_index_argv[] = {
5052 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5053 "--cached", "-M", "HEAD", NULL
5054 };
5056 static const char *status_diff_files_argv[] = {
5057 "git", "diff-files", "-z", NULL
5058 };
5060 static const char *status_list_other_argv[] = {
5061 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5062 };
5064 static const char *status_list_no_head_argv[] = {
5065 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5066 };
5068 static const char *update_index_argv[] = {
5069 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5070 };
5072 /* Restore the previous line number to stay in the context or select a
5073 * line with something that can be updated. */
5074 static void
5075 status_restore(struct view *view)
5076 {
5077 if (view->p_lineno >= view->lines)
5078 view->p_lineno = view->lines - 1;
5079 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5080 view->p_lineno++;
5081 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5082 view->p_lineno--;
5084 /* If the above fails, always skip the "On branch" line. */
5085 if (view->p_lineno < view->lines)
5086 view->lineno = view->p_lineno;
5087 else
5088 view->lineno = 1;
5090 if (view->lineno < view->offset)
5091 view->offset = view->lineno;
5092 else if (view->offset + view->height <= view->lineno)
5093 view->offset = view->lineno - view->height + 1;
5095 view->p_restore = FALSE;
5096 }
5098 static void
5099 status_update_onbranch(void)
5100 {
5101 static const char *paths[][2] = {
5102 { "rebase-apply/rebasing", "Rebasing" },
5103 { "rebase-apply/applying", "Applying mailbox" },
5104 { "rebase-apply/", "Rebasing mailbox" },
5105 { "rebase-merge/interactive", "Interactive rebase" },
5106 { "rebase-merge/", "Rebase merge" },
5107 { "MERGE_HEAD", "Merging" },
5108 { "BISECT_LOG", "Bisecting" },
5109 { "HEAD", "On branch" },
5110 };
5111 char buf[SIZEOF_STR];
5112 struct stat stat;
5113 int i;
5115 if (is_initial_commit()) {
5116 string_copy(status_onbranch, "Initial commit");
5117 return;
5118 }
5120 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5121 char *head = opt_head;
5123 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5124 lstat(buf, &stat) < 0)
5125 continue;
5127 if (!*opt_head) {
5128 struct io io = {};
5130 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5131 io_open(&io, buf) &&
5132 io_read_buf(&io, buf, sizeof(buf))) {
5133 head = buf;
5134 if (!prefixcmp(head, "refs/heads/"))
5135 head += STRING_SIZE("refs/heads/");
5136 }
5137 }
5139 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5140 string_copy(status_onbranch, opt_head);
5141 return;
5142 }
5144 string_copy(status_onbranch, "Not currently on any branch");
5145 }
5147 /* First parse staged info using git-diff-index(1), then parse unstaged
5148 * info using git-diff-files(1), and finally untracked files using
5149 * git-ls-files(1). */
5150 static bool
5151 status_open(struct view *view)
5152 {
5153 reset_view(view);
5155 add_line_data(view, NULL, LINE_STAT_HEAD);
5156 status_update_onbranch();
5158 run_io_bg(update_index_argv);
5160 if (is_initial_commit()) {
5161 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5162 return FALSE;
5163 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5164 return FALSE;
5165 }
5167 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5168 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5169 return FALSE;
5171 /* Restore the exact position or use the specialized restore
5172 * mode? */
5173 if (!view->p_restore)
5174 status_restore(view);
5175 return TRUE;
5176 }
5178 static bool
5179 status_draw(struct view *view, struct line *line, unsigned int lineno)
5180 {
5181 struct status *status = line->data;
5182 enum line_type type;
5183 const char *text;
5185 if (!status) {
5186 switch (line->type) {
5187 case LINE_STAT_STAGED:
5188 type = LINE_STAT_SECTION;
5189 text = "Changes to be committed:";
5190 break;
5192 case LINE_STAT_UNSTAGED:
5193 type = LINE_STAT_SECTION;
5194 text = "Changed but not updated:";
5195 break;
5197 case LINE_STAT_UNTRACKED:
5198 type = LINE_STAT_SECTION;
5199 text = "Untracked files:";
5200 break;
5202 case LINE_STAT_NONE:
5203 type = LINE_DEFAULT;
5204 text = " (no files)";
5205 break;
5207 case LINE_STAT_HEAD:
5208 type = LINE_STAT_HEAD;
5209 text = status_onbranch;
5210 break;
5212 default:
5213 return FALSE;
5214 }
5215 } else {
5216 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5218 buf[0] = status->status;
5219 if (draw_text(view, line->type, buf, TRUE))
5220 return TRUE;
5221 type = LINE_DEFAULT;
5222 text = status->new.name;
5223 }
5225 draw_text(view, type, text, TRUE);
5226 return TRUE;
5227 }
5229 static enum request
5230 status_load_error(struct view *view, struct view *stage, const char *path)
5231 {
5232 if (displayed_views() == 2 || display[current_view] != view)
5233 maximize_view(view);
5234 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5235 return REQ_NONE;
5236 }
5238 static enum request
5239 status_enter(struct view *view, struct line *line)
5240 {
5241 struct status *status = line->data;
5242 const char *oldpath = status ? status->old.name : NULL;
5243 /* Diffs for unmerged entries are empty when passing the new
5244 * path, so leave it empty. */
5245 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5246 const char *info;
5247 enum open_flags split;
5248 struct view *stage = VIEW(REQ_VIEW_STAGE);
5250 if (line->type == LINE_STAT_NONE ||
5251 (!status && line[1].type == LINE_STAT_NONE)) {
5252 report("No file to diff");
5253 return REQ_NONE;
5254 }
5256 switch (line->type) {
5257 case LINE_STAT_STAGED:
5258 if (is_initial_commit()) {
5259 const char *no_head_diff_argv[] = {
5260 "git", "diff", "--no-color", "--patch-with-stat",
5261 "--", "/dev/null", newpath, NULL
5262 };
5264 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5265 return status_load_error(view, stage, newpath);
5266 } else {
5267 const char *index_show_argv[] = {
5268 "git", "diff-index", "--root", "--patch-with-stat",
5269 "-C", "-M", "--cached", "HEAD", "--",
5270 oldpath, newpath, NULL
5271 };
5273 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5274 return status_load_error(view, stage, newpath);
5275 }
5277 if (status)
5278 info = "Staged changes to %s";
5279 else
5280 info = "Staged changes";
5281 break;
5283 case LINE_STAT_UNSTAGED:
5284 {
5285 const char *files_show_argv[] = {
5286 "git", "diff-files", "--root", "--patch-with-stat",
5287 "-C", "-M", "--", oldpath, newpath, NULL
5288 };
5290 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5291 return status_load_error(view, stage, newpath);
5292 if (status)
5293 info = "Unstaged changes to %s";
5294 else
5295 info = "Unstaged changes";
5296 break;
5297 }
5298 case LINE_STAT_UNTRACKED:
5299 if (!newpath) {
5300 report("No file to show");
5301 return REQ_NONE;
5302 }
5304 if (!suffixcmp(status->new.name, -1, "/")) {
5305 report("Cannot display a directory");
5306 return REQ_NONE;
5307 }
5309 if (!prepare_update_file(stage, newpath))
5310 return status_load_error(view, stage, newpath);
5311 info = "Untracked file %s";
5312 break;
5314 case LINE_STAT_HEAD:
5315 return REQ_NONE;
5317 default:
5318 die("line type %d not handled in switch", line->type);
5319 }
5321 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5322 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5323 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5324 if (status) {
5325 stage_status = *status;
5326 } else {
5327 memset(&stage_status, 0, sizeof(stage_status));
5328 }
5330 stage_line_type = line->type;
5331 stage_chunks = 0;
5332 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5333 }
5335 return REQ_NONE;
5336 }
5338 static bool
5339 status_exists(struct status *status, enum line_type type)
5340 {
5341 struct view *view = VIEW(REQ_VIEW_STATUS);
5342 unsigned long lineno;
5344 for (lineno = 0; lineno < view->lines; lineno++) {
5345 struct line *line = &view->line[lineno];
5346 struct status *pos = line->data;
5348 if (line->type != type)
5349 continue;
5350 if (!pos && (!status || !status->status) && line[1].data) {
5351 select_view_line(view, lineno);
5352 return TRUE;
5353 }
5354 if (pos && !strcmp(status->new.name, pos->new.name)) {
5355 select_view_line(view, lineno);
5356 return TRUE;
5357 }
5358 }
5360 return FALSE;
5361 }
5364 static bool
5365 status_update_prepare(struct io *io, enum line_type type)
5366 {
5367 const char *staged_argv[] = {
5368 "git", "update-index", "-z", "--index-info", NULL
5369 };
5370 const char *others_argv[] = {
5371 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5372 };
5374 switch (type) {
5375 case LINE_STAT_STAGED:
5376 return run_io(io, staged_argv, opt_cdup, IO_WR);
5378 case LINE_STAT_UNSTAGED:
5379 return run_io(io, others_argv, opt_cdup, IO_WR);
5381 case LINE_STAT_UNTRACKED:
5382 return run_io(io, others_argv, NULL, IO_WR);
5384 default:
5385 die("line type %d not handled in switch", type);
5386 return FALSE;
5387 }
5388 }
5390 static bool
5391 status_update_write(struct io *io, struct status *status, enum line_type type)
5392 {
5393 char buf[SIZEOF_STR];
5394 size_t bufsize = 0;
5396 switch (type) {
5397 case LINE_STAT_STAGED:
5398 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5399 status->old.mode,
5400 status->old.rev,
5401 status->old.name, 0))
5402 return FALSE;
5403 break;
5405 case LINE_STAT_UNSTAGED:
5406 case LINE_STAT_UNTRACKED:
5407 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5408 return FALSE;
5409 break;
5411 default:
5412 die("line type %d not handled in switch", type);
5413 }
5415 return io_write(io, buf, bufsize);
5416 }
5418 static bool
5419 status_update_file(struct status *status, enum line_type type)
5420 {
5421 struct io io = {};
5422 bool result;
5424 if (!status_update_prepare(&io, type))
5425 return FALSE;
5427 result = status_update_write(&io, status, type);
5428 return done_io(&io) && result;
5429 }
5431 static bool
5432 status_update_files(struct view *view, struct line *line)
5433 {
5434 char buf[sizeof(view->ref)];
5435 struct io io = {};
5436 bool result = TRUE;
5437 struct line *pos = view->line + view->lines;
5438 int files = 0;
5439 int file, done;
5440 int cursor_y, cursor_x;
5442 if (!status_update_prepare(&io, line->type))
5443 return FALSE;
5445 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5446 files++;
5448 string_copy(buf, view->ref);
5449 getsyx(cursor_y, cursor_x);
5450 for (file = 0, done = 5; result && file < files; line++, file++) {
5451 int almost_done = file * 100 / files;
5453 if (almost_done > done) {
5454 done = almost_done;
5455 string_format(view->ref, "updating file %u of %u (%d%% done)",
5456 file, files, done);
5457 update_view_title(view);
5458 setsyx(cursor_y, cursor_x);
5459 doupdate();
5460 }
5461 result = status_update_write(&io, line->data, line->type);
5462 }
5463 string_copy(view->ref, buf);
5465 return done_io(&io) && result;
5466 }
5468 static bool
5469 status_update(struct view *view)
5470 {
5471 struct line *line = &view->line[view->lineno];
5473 assert(view->lines);
5475 if (!line->data) {
5476 /* This should work even for the "On branch" line. */
5477 if (line < view->line + view->lines && !line[1].data) {
5478 report("Nothing to update");
5479 return FALSE;
5480 }
5482 if (!status_update_files(view, line + 1)) {
5483 report("Failed to update file status");
5484 return FALSE;
5485 }
5487 } else if (!status_update_file(line->data, line->type)) {
5488 report("Failed to update file status");
5489 return FALSE;
5490 }
5492 return TRUE;
5493 }
5495 static bool
5496 status_revert(struct status *status, enum line_type type, bool has_none)
5497 {
5498 if (!status || type != LINE_STAT_UNSTAGED) {
5499 if (type == LINE_STAT_STAGED) {
5500 report("Cannot revert changes to staged files");
5501 } else if (type == LINE_STAT_UNTRACKED) {
5502 report("Cannot revert changes to untracked files");
5503 } else if (has_none) {
5504 report("Nothing to revert");
5505 } else {
5506 report("Cannot revert changes to multiple files");
5507 }
5508 return FALSE;
5510 } else {
5511 char mode[10] = "100644";
5512 const char *reset_argv[] = {
5513 "git", "update-index", "--cacheinfo", mode,
5514 status->old.rev, status->old.name, NULL
5515 };
5516 const char *checkout_argv[] = {
5517 "git", "checkout", "--", status->old.name, NULL
5518 };
5520 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5521 return FALSE;
5522 string_format(mode, "%o", status->old.mode);
5523 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5524 run_io_fg(checkout_argv, opt_cdup);
5525 }
5526 }
5528 static enum request
5529 status_request(struct view *view, enum request request, struct line *line)
5530 {
5531 struct status *status = line->data;
5533 switch (request) {
5534 case REQ_STATUS_UPDATE:
5535 if (!status_update(view))
5536 return REQ_NONE;
5537 break;
5539 case REQ_STATUS_REVERT:
5540 if (!status_revert(status, line->type, status_has_none(view, line)))
5541 return REQ_NONE;
5542 break;
5544 case REQ_STATUS_MERGE:
5545 if (!status || status->status != 'U') {
5546 report("Merging only possible for files with unmerged status ('U').");
5547 return REQ_NONE;
5548 }
5549 open_mergetool(status->new.name);
5550 break;
5552 case REQ_EDIT:
5553 if (!status)
5554 return request;
5555 if (status->status == 'D') {
5556 report("File has been deleted.");
5557 return REQ_NONE;
5558 }
5560 open_editor(status->status != '?', status->new.name);
5561 break;
5563 case REQ_VIEW_BLAME:
5564 if (status) {
5565 string_copy(opt_file, status->new.name);
5566 opt_ref[0] = 0;
5567 }
5568 return request;
5570 case REQ_ENTER:
5571 /* After returning the status view has been split to
5572 * show the stage view. No further reloading is
5573 * necessary. */
5574 return status_enter(view, line);
5576 case REQ_REFRESH:
5577 /* Simply reload the view. */
5578 break;
5580 default:
5581 return request;
5582 }
5584 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5586 return REQ_NONE;
5587 }
5589 static void
5590 status_select(struct view *view, struct line *line)
5591 {
5592 struct status *status = line->data;
5593 char file[SIZEOF_STR] = "all files";
5594 const char *text;
5595 const char *key;
5597 if (status && !string_format(file, "'%s'", status->new.name))
5598 return;
5600 if (!status && line[1].type == LINE_STAT_NONE)
5601 line++;
5603 switch (line->type) {
5604 case LINE_STAT_STAGED:
5605 text = "Press %s to unstage %s for commit";
5606 break;
5608 case LINE_STAT_UNSTAGED:
5609 text = "Press %s to stage %s for commit";
5610 break;
5612 case LINE_STAT_UNTRACKED:
5613 text = "Press %s to stage %s for addition";
5614 break;
5616 case LINE_STAT_HEAD:
5617 case LINE_STAT_NONE:
5618 text = "Nothing to update";
5619 break;
5621 default:
5622 die("line type %d not handled in switch", line->type);
5623 }
5625 if (status && status->status == 'U') {
5626 text = "Press %s to resolve conflict in %s";
5627 key = get_key(REQ_STATUS_MERGE);
5629 } else {
5630 key = get_key(REQ_STATUS_UPDATE);
5631 }
5633 string_format(view->ref, text, key, file);
5634 }
5636 static bool
5637 status_grep(struct view *view, struct line *line)
5638 {
5639 struct status *status = line->data;
5641 if (status) {
5642 const char buf[2] = { status->status, 0 };
5643 const char *text[] = { status->new.name, buf, NULL };
5645 return grep_text(view, text);
5646 }
5648 return FALSE;
5649 }
5651 static struct view_ops status_ops = {
5652 "file",
5653 NULL,
5654 status_open,
5655 NULL,
5656 status_draw,
5657 status_request,
5658 status_grep,
5659 status_select,
5660 };
5663 static bool
5664 stage_diff_write(struct io *io, struct line *line, struct line *end)
5665 {
5666 while (line < end) {
5667 if (!io_write(io, line->data, strlen(line->data)) ||
5668 !io_write(io, "\n", 1))
5669 return FALSE;
5670 line++;
5671 if (line->type == LINE_DIFF_CHUNK ||
5672 line->type == LINE_DIFF_HEADER)
5673 break;
5674 }
5676 return TRUE;
5677 }
5679 static struct line *
5680 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5681 {
5682 for (; view->line < line; line--)
5683 if (line->type == type)
5684 return line;
5686 return NULL;
5687 }
5689 static bool
5690 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5691 {
5692 const char *apply_argv[SIZEOF_ARG] = {
5693 "git", "apply", "--whitespace=nowarn", NULL
5694 };
5695 struct line *diff_hdr;
5696 struct io io = {};
5697 int argc = 3;
5699 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5700 if (!diff_hdr)
5701 return FALSE;
5703 if (!revert)
5704 apply_argv[argc++] = "--cached";
5705 if (revert || stage_line_type == LINE_STAT_STAGED)
5706 apply_argv[argc++] = "-R";
5707 apply_argv[argc++] = "-";
5708 apply_argv[argc++] = NULL;
5709 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5710 return FALSE;
5712 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5713 !stage_diff_write(&io, chunk, view->line + view->lines))
5714 chunk = NULL;
5716 done_io(&io);
5717 run_io_bg(update_index_argv);
5719 return chunk ? TRUE : FALSE;
5720 }
5722 static bool
5723 stage_update(struct view *view, struct line *line)
5724 {
5725 struct line *chunk = NULL;
5727 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5728 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5730 if (chunk) {
5731 if (!stage_apply_chunk(view, chunk, FALSE)) {
5732 report("Failed to apply chunk");
5733 return FALSE;
5734 }
5736 } else if (!stage_status.status) {
5737 view = VIEW(REQ_VIEW_STATUS);
5739 for (line = view->line; line < view->line + view->lines; line++)
5740 if (line->type == stage_line_type)
5741 break;
5743 if (!status_update_files(view, line + 1)) {
5744 report("Failed to update files");
5745 return FALSE;
5746 }
5748 } else if (!status_update_file(&stage_status, stage_line_type)) {
5749 report("Failed to update file");
5750 return FALSE;
5751 }
5753 return TRUE;
5754 }
5756 static bool
5757 stage_revert(struct view *view, struct line *line)
5758 {
5759 struct line *chunk = NULL;
5761 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5762 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5764 if (chunk) {
5765 if (!prompt_yesno("Are you sure you want to revert changes?"))
5766 return FALSE;
5768 if (!stage_apply_chunk(view, chunk, TRUE)) {
5769 report("Failed to revert chunk");
5770 return FALSE;
5771 }
5772 return TRUE;
5774 } else {
5775 return status_revert(stage_status.status ? &stage_status : NULL,
5776 stage_line_type, FALSE);
5777 }
5778 }
5781 static void
5782 stage_next(struct view *view, struct line *line)
5783 {
5784 int i;
5786 if (!stage_chunks) {
5787 for (line = view->line; line < view->line + view->lines; line++) {
5788 if (line->type != LINE_DIFF_CHUNK)
5789 continue;
5791 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5792 report("Allocation failure");
5793 return;
5794 }
5796 stage_chunk[stage_chunks++] = line - view->line;
5797 }
5798 }
5800 for (i = 0; i < stage_chunks; i++) {
5801 if (stage_chunk[i] > view->lineno) {
5802 do_scroll_view(view, stage_chunk[i] - view->lineno);
5803 report("Chunk %d of %d", i + 1, stage_chunks);
5804 return;
5805 }
5806 }
5808 report("No next chunk found");
5809 }
5811 static enum request
5812 stage_request(struct view *view, enum request request, struct line *line)
5813 {
5814 switch (request) {
5815 case REQ_STATUS_UPDATE:
5816 if (!stage_update(view, line))
5817 return REQ_NONE;
5818 break;
5820 case REQ_STATUS_REVERT:
5821 if (!stage_revert(view, line))
5822 return REQ_NONE;
5823 break;
5825 case REQ_STAGE_NEXT:
5826 if (stage_line_type == LINE_STAT_UNTRACKED) {
5827 report("File is untracked; press %s to add",
5828 get_key(REQ_STATUS_UPDATE));
5829 return REQ_NONE;
5830 }
5831 stage_next(view, line);
5832 return REQ_NONE;
5834 case REQ_EDIT:
5835 if (!stage_status.new.name[0])
5836 return request;
5837 if (stage_status.status == 'D') {
5838 report("File has been deleted.");
5839 return REQ_NONE;
5840 }
5842 open_editor(stage_status.status != '?', stage_status.new.name);
5843 break;
5845 case REQ_REFRESH:
5846 /* Reload everything ... */
5847 break;
5849 case REQ_VIEW_BLAME:
5850 if (stage_status.new.name[0]) {
5851 string_copy(opt_file, stage_status.new.name);
5852 opt_ref[0] = 0;
5853 }
5854 return request;
5856 case REQ_ENTER:
5857 return pager_request(view, request, line);
5859 default:
5860 return request;
5861 }
5863 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5864 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5866 /* Check whether the staged entry still exists, and close the
5867 * stage view if it doesn't. */
5868 if (!status_exists(&stage_status, stage_line_type)) {
5869 status_restore(VIEW(REQ_VIEW_STATUS));
5870 return REQ_VIEW_CLOSE;
5871 }
5873 if (stage_line_type == LINE_STAT_UNTRACKED) {
5874 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5875 report("Cannot display a directory");
5876 return REQ_NONE;
5877 }
5879 if (!prepare_update_file(view, stage_status.new.name)) {
5880 report("Failed to open file: %s", strerror(errno));
5881 return REQ_NONE;
5882 }
5883 }
5884 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5886 return REQ_NONE;
5887 }
5889 static struct view_ops stage_ops = {
5890 "line",
5891 NULL,
5892 NULL,
5893 pager_read,
5894 pager_draw,
5895 stage_request,
5896 pager_grep,
5897 pager_select,
5898 };
5901 /*
5902 * Revision graph
5903 */
5905 struct commit {
5906 char id[SIZEOF_REV]; /* SHA1 ID. */
5907 char title[128]; /* First line of the commit message. */
5908 const char *author; /* Author of the commit. */
5909 time_t time; /* Date from the author ident. */
5910 struct ref_list *refs; /* Repository references. */
5911 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5912 size_t graph_size; /* The width of the graph array. */
5913 bool has_parents; /* Rewritten --parents seen. */
5914 };
5916 /* Size of rev graph with no "padding" columns */
5917 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5919 struct rev_graph {
5920 struct rev_graph *prev, *next, *parents;
5921 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5922 size_t size;
5923 struct commit *commit;
5924 size_t pos;
5925 unsigned int boundary:1;
5926 };
5928 /* Parents of the commit being visualized. */
5929 static struct rev_graph graph_parents[4];
5931 /* The current stack of revisions on the graph. */
5932 static struct rev_graph graph_stacks[4] = {
5933 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5934 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5935 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5936 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5937 };
5939 static inline bool
5940 graph_parent_is_merge(struct rev_graph *graph)
5941 {
5942 return graph->parents->size > 1;
5943 }
5945 static inline void
5946 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5947 {
5948 struct commit *commit = graph->commit;
5950 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5951 commit->graph[commit->graph_size++] = symbol;
5952 }
5954 static void
5955 clear_rev_graph(struct rev_graph *graph)
5956 {
5957 graph->boundary = 0;
5958 graph->size = graph->pos = 0;
5959 graph->commit = NULL;
5960 memset(graph->parents, 0, sizeof(*graph->parents));
5961 }
5963 static void
5964 done_rev_graph(struct rev_graph *graph)
5965 {
5966 if (graph_parent_is_merge(graph) &&
5967 graph->pos < graph->size - 1 &&
5968 graph->next->size == graph->size + graph->parents->size - 1) {
5969 size_t i = graph->pos + graph->parents->size - 1;
5971 graph->commit->graph_size = i * 2;
5972 while (i < graph->next->size - 1) {
5973 append_to_rev_graph(graph, ' ');
5974 append_to_rev_graph(graph, '\\');
5975 i++;
5976 }
5977 }
5979 clear_rev_graph(graph);
5980 }
5982 static void
5983 push_rev_graph(struct rev_graph *graph, const char *parent)
5984 {
5985 int i;
5987 /* "Collapse" duplicate parents lines.
5988 *
5989 * FIXME: This needs to also update update the drawn graph but
5990 * for now it just serves as a method for pruning graph lines. */
5991 for (i = 0; i < graph->size; i++)
5992 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5993 return;
5995 if (graph->size < SIZEOF_REVITEMS) {
5996 string_copy_rev(graph->rev[graph->size++], parent);
5997 }
5998 }
6000 static chtype
6001 get_rev_graph_symbol(struct rev_graph *graph)
6002 {
6003 chtype symbol;
6005 if (graph->boundary)
6006 symbol = REVGRAPH_BOUND;
6007 else if (graph->parents->size == 0)
6008 symbol = REVGRAPH_INIT;
6009 else if (graph_parent_is_merge(graph))
6010 symbol = REVGRAPH_MERGE;
6011 else if (graph->pos >= graph->size)
6012 symbol = REVGRAPH_BRANCH;
6013 else
6014 symbol = REVGRAPH_COMMIT;
6016 return symbol;
6017 }
6019 static void
6020 draw_rev_graph(struct rev_graph *graph)
6021 {
6022 struct rev_filler {
6023 chtype separator, line;
6024 };
6025 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6026 static struct rev_filler fillers[] = {
6027 { ' ', '|' },
6028 { '`', '.' },
6029 { '\'', ' ' },
6030 { '/', ' ' },
6031 };
6032 chtype symbol = get_rev_graph_symbol(graph);
6033 struct rev_filler *filler;
6034 size_t i;
6036 if (opt_line_graphics)
6037 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6039 filler = &fillers[DEFAULT];
6041 for (i = 0; i < graph->pos; i++) {
6042 append_to_rev_graph(graph, filler->line);
6043 if (graph_parent_is_merge(graph->prev) &&
6044 graph->prev->pos == i)
6045 filler = &fillers[RSHARP];
6047 append_to_rev_graph(graph, filler->separator);
6048 }
6050 /* Place the symbol for this revision. */
6051 append_to_rev_graph(graph, symbol);
6053 if (graph->prev->size > graph->size)
6054 filler = &fillers[RDIAG];
6055 else
6056 filler = &fillers[DEFAULT];
6058 i++;
6060 for (; i < graph->size; i++) {
6061 append_to_rev_graph(graph, filler->separator);
6062 append_to_rev_graph(graph, filler->line);
6063 if (graph_parent_is_merge(graph->prev) &&
6064 i < graph->prev->pos + graph->parents->size)
6065 filler = &fillers[RSHARP];
6066 if (graph->prev->size > graph->size)
6067 filler = &fillers[LDIAG];
6068 }
6070 if (graph->prev->size > graph->size) {
6071 append_to_rev_graph(graph, filler->separator);
6072 if (filler->line != ' ')
6073 append_to_rev_graph(graph, filler->line);
6074 }
6075 }
6077 /* Prepare the next rev graph */
6078 static void
6079 prepare_rev_graph(struct rev_graph *graph)
6080 {
6081 size_t i;
6083 /* First, traverse all lines of revisions up to the active one. */
6084 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6085 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6086 break;
6088 push_rev_graph(graph->next, graph->rev[graph->pos]);
6089 }
6091 /* Interleave the new revision parent(s). */
6092 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6093 push_rev_graph(graph->next, graph->parents->rev[i]);
6095 /* Lastly, put any remaining revisions. */
6096 for (i = graph->pos + 1; i < graph->size; i++)
6097 push_rev_graph(graph->next, graph->rev[i]);
6098 }
6100 static void
6101 update_rev_graph(struct view *view, struct rev_graph *graph)
6102 {
6103 /* If this is the finalizing update ... */
6104 if (graph->commit)
6105 prepare_rev_graph(graph);
6107 /* Graph visualization needs a one rev look-ahead,
6108 * so the first update doesn't visualize anything. */
6109 if (!graph->prev->commit)
6110 return;
6112 if (view->lines > 2)
6113 view->line[view->lines - 3].dirty = 1;
6114 if (view->lines > 1)
6115 view->line[view->lines - 2].dirty = 1;
6116 draw_rev_graph(graph->prev);
6117 done_rev_graph(graph->prev->prev);
6118 }
6121 /*
6122 * Main view backend
6123 */
6125 static const char *main_argv[SIZEOF_ARG] = {
6126 "git", "log", "--no-color", "--pretty=raw", "--parents",
6127 "--topo-order", "%(head)", NULL
6128 };
6130 static bool
6131 main_draw(struct view *view, struct line *line, unsigned int lineno)
6132 {
6133 struct commit *commit = line->data;
6135 if (!commit->author)
6136 return FALSE;
6138 if (opt_date && draw_date(view, &commit->time))
6139 return TRUE;
6141 if (opt_author && draw_author(view, commit->author))
6142 return TRUE;
6144 if (opt_rev_graph && commit->graph_size &&
6145 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6146 return TRUE;
6148 if (opt_show_refs && commit->refs) {
6149 size_t i;
6151 for (i = 0; i < commit->refs->size; i++) {
6152 struct ref *ref = commit->refs->refs[i];
6153 enum line_type type;
6155 if (ref->head)
6156 type = LINE_MAIN_HEAD;
6157 else if (ref->ltag)
6158 type = LINE_MAIN_LOCAL_TAG;
6159 else if (ref->tag)
6160 type = LINE_MAIN_TAG;
6161 else if (ref->tracked)
6162 type = LINE_MAIN_TRACKED;
6163 else if (ref->remote)
6164 type = LINE_MAIN_REMOTE;
6165 else
6166 type = LINE_MAIN_REF;
6168 if (draw_text(view, type, "[", TRUE) ||
6169 draw_text(view, type, ref->name, TRUE) ||
6170 draw_text(view, type, "]", TRUE))
6171 return TRUE;
6173 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6174 return TRUE;
6175 }
6176 }
6178 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6179 return TRUE;
6180 }
6182 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6183 static bool
6184 main_read(struct view *view, char *line)
6185 {
6186 static struct rev_graph *graph = graph_stacks;
6187 enum line_type type;
6188 struct commit *commit;
6190 if (!line) {
6191 int i;
6193 if (!view->lines && !view->parent)
6194 die("No revisions match the given arguments.");
6195 if (view->lines > 0) {
6196 commit = view->line[view->lines - 1].data;
6197 view->line[view->lines - 1].dirty = 1;
6198 if (!commit->author) {
6199 view->lines--;
6200 free(commit);
6201 graph->commit = NULL;
6202 }
6203 }
6204 update_rev_graph(view, graph);
6206 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6207 clear_rev_graph(&graph_stacks[i]);
6208 return TRUE;
6209 }
6211 type = get_line_type(line);
6212 if (type == LINE_COMMIT) {
6213 commit = calloc(1, sizeof(struct commit));
6214 if (!commit)
6215 return FALSE;
6217 line += STRING_SIZE("commit ");
6218 if (*line == '-') {
6219 graph->boundary = 1;
6220 line++;
6221 }
6223 string_copy_rev(commit->id, line);
6224 commit->refs = get_ref_list(commit->id);
6225 graph->commit = commit;
6226 add_line_data(view, commit, LINE_MAIN_COMMIT);
6228 while ((line = strchr(line, ' '))) {
6229 line++;
6230 push_rev_graph(graph->parents, line);
6231 commit->has_parents = TRUE;
6232 }
6233 return TRUE;
6234 }
6236 if (!view->lines)
6237 return TRUE;
6238 commit = view->line[view->lines - 1].data;
6240 switch (type) {
6241 case LINE_PARENT:
6242 if (commit->has_parents)
6243 break;
6244 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6245 break;
6247 case LINE_AUTHOR:
6248 parse_author_line(line + STRING_SIZE("author "),
6249 &commit->author, &commit->time);
6250 update_rev_graph(view, graph);
6251 graph = graph->next;
6252 break;
6254 default:
6255 /* Fill in the commit title if it has not already been set. */
6256 if (commit->title[0])
6257 break;
6259 /* Require titles to start with a non-space character at the
6260 * offset used by git log. */
6261 if (strncmp(line, " ", 4))
6262 break;
6263 line += 4;
6264 /* Well, if the title starts with a whitespace character,
6265 * try to be forgiving. Otherwise we end up with no title. */
6266 while (isspace(*line))
6267 line++;
6268 if (*line == '\0')
6269 break;
6270 /* FIXME: More graceful handling of titles; append "..." to
6271 * shortened titles, etc. */
6273 string_expand(commit->title, sizeof(commit->title), line, 1);
6274 view->line[view->lines - 1].dirty = 1;
6275 }
6277 return TRUE;
6278 }
6280 static enum request
6281 main_request(struct view *view, enum request request, struct line *line)
6282 {
6283 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6285 switch (request) {
6286 case REQ_ENTER:
6287 open_view(view, REQ_VIEW_DIFF, flags);
6288 break;
6289 case REQ_REFRESH:
6290 load_refs();
6291 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6292 break;
6293 default:
6294 return request;
6295 }
6297 return REQ_NONE;
6298 }
6300 static bool
6301 grep_refs(struct ref_list *list, regex_t *regex)
6302 {
6303 regmatch_t pmatch;
6304 size_t i;
6306 if (!opt_show_refs || !list)
6307 return FALSE;
6309 for (i = 0; i < list->size; i++) {
6310 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6311 return TRUE;
6312 }
6314 return FALSE;
6315 }
6317 static bool
6318 main_grep(struct view *view, struct line *line)
6319 {
6320 struct commit *commit = line->data;
6321 const char *text[] = {
6322 commit->title,
6323 opt_author ? commit->author : "",
6324 opt_date ? mkdate(&commit->time) : "",
6325 NULL
6326 };
6328 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6329 }
6331 static void
6332 main_select(struct view *view, struct line *line)
6333 {
6334 struct commit *commit = line->data;
6336 string_copy_rev(view->ref, commit->id);
6337 string_copy_rev(ref_commit, view->ref);
6338 }
6340 static struct view_ops main_ops = {
6341 "commit",
6342 main_argv,
6343 NULL,
6344 main_read,
6345 main_draw,
6346 main_request,
6347 main_grep,
6348 main_select,
6349 };
6352 /*
6353 * Unicode / UTF-8 handling
6354 *
6355 * NOTE: Much of the following code for dealing with Unicode is derived from
6356 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6357 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6358 */
6360 static inline int
6361 unicode_width(unsigned long c)
6362 {
6363 if (c >= 0x1100 &&
6364 (c <= 0x115f /* Hangul Jamo */
6365 || c == 0x2329
6366 || c == 0x232a
6367 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6368 /* CJK ... Yi */
6369 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6370 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6371 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6372 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6373 || (c >= 0xffe0 && c <= 0xffe6)
6374 || (c >= 0x20000 && c <= 0x2fffd)
6375 || (c >= 0x30000 && c <= 0x3fffd)))
6376 return 2;
6378 if (c == '\t')
6379 return opt_tab_size;
6381 return 1;
6382 }
6384 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6385 * Illegal bytes are set one. */
6386 static const unsigned char utf8_bytes[256] = {
6387 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,
6388 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,
6389 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,
6390 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,
6391 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,
6392 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,
6393 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,
6394 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,
6395 };
6397 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6398 static inline unsigned long
6399 utf8_to_unicode(const char *string, size_t length)
6400 {
6401 unsigned long unicode;
6403 switch (length) {
6404 case 1:
6405 unicode = string[0];
6406 break;
6407 case 2:
6408 unicode = (string[0] & 0x1f) << 6;
6409 unicode += (string[1] & 0x3f);
6410 break;
6411 case 3:
6412 unicode = (string[0] & 0x0f) << 12;
6413 unicode += ((string[1] & 0x3f) << 6);
6414 unicode += (string[2] & 0x3f);
6415 break;
6416 case 4:
6417 unicode = (string[0] & 0x0f) << 18;
6418 unicode += ((string[1] & 0x3f) << 12);
6419 unicode += ((string[2] & 0x3f) << 6);
6420 unicode += (string[3] & 0x3f);
6421 break;
6422 case 5:
6423 unicode = (string[0] & 0x0f) << 24;
6424 unicode += ((string[1] & 0x3f) << 18);
6425 unicode += ((string[2] & 0x3f) << 12);
6426 unicode += ((string[3] & 0x3f) << 6);
6427 unicode += (string[4] & 0x3f);
6428 break;
6429 case 6:
6430 unicode = (string[0] & 0x01) << 30;
6431 unicode += ((string[1] & 0x3f) << 24);
6432 unicode += ((string[2] & 0x3f) << 18);
6433 unicode += ((string[3] & 0x3f) << 12);
6434 unicode += ((string[4] & 0x3f) << 6);
6435 unicode += (string[5] & 0x3f);
6436 break;
6437 default:
6438 die("Invalid Unicode length");
6439 }
6441 /* Invalid characters could return the special 0xfffd value but NUL
6442 * should be just as good. */
6443 return unicode > 0xffff ? 0 : unicode;
6444 }
6446 /* Calculates how much of string can be shown within the given maximum width
6447 * and sets trimmed parameter to non-zero value if all of string could not be
6448 * shown. If the reserve flag is TRUE, it will reserve at least one
6449 * trailing character, which can be useful when drawing a delimiter.
6450 *
6451 * Returns the number of bytes to output from string to satisfy max_width. */
6452 static size_t
6453 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6454 {
6455 const char *string = *start;
6456 const char *end = strchr(string, '\0');
6457 unsigned char last_bytes = 0;
6458 size_t last_ucwidth = 0;
6460 *width = 0;
6461 *trimmed = 0;
6463 while (string < end) {
6464 int c = *(unsigned char *) string;
6465 unsigned char bytes = utf8_bytes[c];
6466 size_t ucwidth;
6467 unsigned long unicode;
6469 if (string + bytes > end)
6470 break;
6472 /* Change representation to figure out whether
6473 * it is a single- or double-width character. */
6475 unicode = utf8_to_unicode(string, bytes);
6476 /* FIXME: Graceful handling of invalid Unicode character. */
6477 if (!unicode)
6478 break;
6480 ucwidth = unicode_width(unicode);
6481 if (skip > 0) {
6482 skip -= ucwidth <= skip ? ucwidth : skip;
6483 *start += bytes;
6484 }
6485 *width += ucwidth;
6486 if (*width > max_width) {
6487 *trimmed = 1;
6488 *width -= ucwidth;
6489 if (reserve && *width == max_width) {
6490 string -= last_bytes;
6491 *width -= last_ucwidth;
6492 }
6493 break;
6494 }
6496 string += bytes;
6497 last_bytes = ucwidth ? bytes : 0;
6498 last_ucwidth = ucwidth;
6499 }
6501 return string - *start;
6502 }
6505 /*
6506 * Status management
6507 */
6509 /* Whether or not the curses interface has been initialized. */
6510 static bool cursed = FALSE;
6512 /* Terminal hacks and workarounds. */
6513 static bool use_scroll_redrawwin;
6514 static bool use_scroll_status_wclear;
6516 /* The status window is used for polling keystrokes. */
6517 static WINDOW *status_win;
6519 /* Reading from the prompt? */
6520 static bool input_mode = FALSE;
6522 static bool status_empty = FALSE;
6524 /* Update status and title window. */
6525 static void
6526 report(const char *msg, ...)
6527 {
6528 struct view *view = display[current_view];
6530 if (input_mode)
6531 return;
6533 if (!view) {
6534 char buf[SIZEOF_STR];
6535 va_list args;
6537 va_start(args, msg);
6538 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6539 buf[sizeof(buf) - 1] = 0;
6540 buf[sizeof(buf) - 2] = '.';
6541 buf[sizeof(buf) - 3] = '.';
6542 buf[sizeof(buf) - 4] = '.';
6543 }
6544 va_end(args);
6545 die("%s", buf);
6546 }
6548 if (!status_empty || *msg) {
6549 va_list args;
6551 va_start(args, msg);
6553 wmove(status_win, 0, 0);
6554 if (view->has_scrolled && use_scroll_status_wclear)
6555 wclear(status_win);
6556 if (*msg) {
6557 vwprintw(status_win, msg, args);
6558 status_empty = FALSE;
6559 } else {
6560 status_empty = TRUE;
6561 }
6562 wclrtoeol(status_win);
6563 wnoutrefresh(status_win);
6565 va_end(args);
6566 }
6568 update_view_title(view);
6569 }
6571 /* Controls when nodelay should be in effect when polling user input. */
6572 static void
6573 set_nonblocking_input(bool loading)
6574 {
6575 static unsigned int loading_views;
6577 if ((loading == FALSE && loading_views-- == 1) ||
6578 (loading == TRUE && loading_views++ == 0))
6579 nodelay(status_win, loading);
6580 }
6582 static void
6583 init_display(void)
6584 {
6585 const char *term;
6586 int x, y;
6588 /* Initialize the curses library */
6589 if (isatty(STDIN_FILENO)) {
6590 cursed = !!initscr();
6591 opt_tty = stdin;
6592 } else {
6593 /* Leave stdin and stdout alone when acting as a pager. */
6594 opt_tty = fopen("/dev/tty", "r+");
6595 if (!opt_tty)
6596 die("Failed to open /dev/tty");
6597 cursed = !!newterm(NULL, opt_tty, opt_tty);
6598 }
6600 if (!cursed)
6601 die("Failed to initialize curses");
6603 nonl(); /* Disable conversion and detect newlines from input. */
6604 cbreak(); /* Take input chars one at a time, no wait for \n */
6605 noecho(); /* Don't echo input */
6606 leaveok(stdscr, FALSE);
6608 if (has_colors())
6609 init_colors();
6611 getmaxyx(stdscr, y, x);
6612 status_win = newwin(1, 0, y - 1, 0);
6613 if (!status_win)
6614 die("Failed to create status window");
6616 /* Enable keyboard mapping */
6617 keypad(status_win, TRUE);
6618 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6620 TABSIZE = opt_tab_size;
6621 if (opt_line_graphics) {
6622 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6623 }
6625 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6626 if (term && !strcmp(term, "gnome-terminal")) {
6627 /* In the gnome-terminal-emulator, the message from
6628 * scrolling up one line when impossible followed by
6629 * scrolling down one line causes corruption of the
6630 * status line. This is fixed by calling wclear. */
6631 use_scroll_status_wclear = TRUE;
6632 use_scroll_redrawwin = FALSE;
6634 } else if (term && !strcmp(term, "xrvt-xpm")) {
6635 /* No problems with full optimizations in xrvt-(unicode)
6636 * and aterm. */
6637 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6639 } else {
6640 /* When scrolling in (u)xterm the last line in the
6641 * scrolling direction will update slowly. */
6642 use_scroll_redrawwin = TRUE;
6643 use_scroll_status_wclear = FALSE;
6644 }
6645 }
6647 static int
6648 get_input(int prompt_position)
6649 {
6650 struct view *view;
6651 int i, key, cursor_y, cursor_x;
6653 if (prompt_position)
6654 input_mode = TRUE;
6656 while (TRUE) {
6657 foreach_view (view, i) {
6658 update_view(view);
6659 if (view_is_displayed(view) && view->has_scrolled &&
6660 use_scroll_redrawwin)
6661 redrawwin(view->win);
6662 view->has_scrolled = FALSE;
6663 }
6665 /* Update the cursor position. */
6666 if (prompt_position) {
6667 getbegyx(status_win, cursor_y, cursor_x);
6668 cursor_x = prompt_position;
6669 } else {
6670 view = display[current_view];
6671 getbegyx(view->win, cursor_y, cursor_x);
6672 cursor_x = view->width - 1;
6673 cursor_y += view->lineno - view->offset;
6674 }
6675 setsyx(cursor_y, cursor_x);
6677 /* Refresh, accept single keystroke of input */
6678 doupdate();
6679 key = wgetch(status_win);
6681 /* wgetch() with nodelay() enabled returns ERR when
6682 * there's no input. */
6683 if (key == ERR) {
6685 } else if (key == KEY_RESIZE) {
6686 int height, width;
6688 getmaxyx(stdscr, height, width);
6690 wresize(status_win, 1, width);
6691 mvwin(status_win, height - 1, 0);
6692 wnoutrefresh(status_win);
6693 resize_display();
6694 redraw_display(TRUE);
6696 } else {
6697 input_mode = FALSE;
6698 return key;
6699 }
6700 }
6701 }
6703 static char *
6704 prompt_input(const char *prompt, input_handler handler, void *data)
6705 {
6706 enum input_status status = INPUT_OK;
6707 static char buf[SIZEOF_STR];
6708 size_t pos = 0;
6710 buf[pos] = 0;
6712 while (status == INPUT_OK || status == INPUT_SKIP) {
6713 int key;
6715 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6716 wclrtoeol(status_win);
6718 key = get_input(pos + 1);
6719 switch (key) {
6720 case KEY_RETURN:
6721 case KEY_ENTER:
6722 case '\n':
6723 status = pos ? INPUT_STOP : INPUT_CANCEL;
6724 break;
6726 case KEY_BACKSPACE:
6727 if (pos > 0)
6728 buf[--pos] = 0;
6729 else
6730 status = INPUT_CANCEL;
6731 break;
6733 case KEY_ESC:
6734 status = INPUT_CANCEL;
6735 break;
6737 default:
6738 if (pos >= sizeof(buf)) {
6739 report("Input string too long");
6740 return NULL;
6741 }
6743 status = handler(data, buf, key);
6744 if (status == INPUT_OK)
6745 buf[pos++] = (char) key;
6746 }
6747 }
6749 /* Clear the status window */
6750 status_empty = FALSE;
6751 report("");
6753 if (status == INPUT_CANCEL)
6754 return NULL;
6756 buf[pos++] = 0;
6758 return buf;
6759 }
6761 static enum input_status
6762 prompt_yesno_handler(void *data, char *buf, int c)
6763 {
6764 if (c == 'y' || c == 'Y')
6765 return INPUT_STOP;
6766 if (c == 'n' || c == 'N')
6767 return INPUT_CANCEL;
6768 return INPUT_SKIP;
6769 }
6771 static bool
6772 prompt_yesno(const char *prompt)
6773 {
6774 char prompt2[SIZEOF_STR];
6776 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6777 return FALSE;
6779 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6780 }
6782 static enum input_status
6783 read_prompt_handler(void *data, char *buf, int c)
6784 {
6785 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6786 }
6788 static char *
6789 read_prompt(const char *prompt)
6790 {
6791 return prompt_input(prompt, read_prompt_handler, NULL);
6792 }
6794 /*
6795 * Repository properties
6796 */
6798 static struct ref **refs = NULL;
6799 static size_t refs_size = 0;
6801 static struct ref_list **ref_lists = NULL;
6802 static size_t ref_lists_size = 0;
6804 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
6805 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6806 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
6808 static int
6809 compare_refs(const void *ref1_, const void *ref2_)
6810 {
6811 const struct ref *ref1 = *(const struct ref **)ref1_;
6812 const struct ref *ref2 = *(const struct ref **)ref2_;
6814 if (ref1->tag != ref2->tag)
6815 return ref2->tag - ref1->tag;
6816 if (ref1->ltag != ref2->ltag)
6817 return ref2->ltag - ref2->ltag;
6818 if (ref1->head != ref2->head)
6819 return ref2->head - ref1->head;
6820 if (ref1->tracked != ref2->tracked)
6821 return ref2->tracked - ref1->tracked;
6822 if (ref1->remote != ref2->remote)
6823 return ref2->remote - ref1->remote;
6824 return strcmp(ref1->name, ref2->name);
6825 }
6827 static void
6828 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6829 {
6830 size_t i;
6832 for (i = 0; i < refs_size; i++)
6833 if (!visitor(data, refs[i]))
6834 break;
6835 }
6837 static struct ref_list *
6838 get_ref_list(const char *id)
6839 {
6840 struct ref_list *list;
6841 size_t i;
6843 for (i = 0; i < ref_lists_size; i++)
6844 if (!strcmp(id, ref_lists[i]->id))
6845 return ref_lists[i];
6847 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
6848 return NULL;
6849 list = calloc(1, sizeof(*list));
6850 if (!list)
6851 return NULL;
6853 for (i = 0; i < refs_size; i++) {
6854 if (!strcmp(id, refs[i]->id) &&
6855 realloc_refs_list(&list->refs, list->size, 1))
6856 list->refs[list->size++] = refs[i];
6857 }
6859 if (!list->refs) {
6860 free(list);
6861 return NULL;
6862 }
6864 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
6865 ref_lists[ref_lists_size++] = list;
6866 return list;
6867 }
6869 static int
6870 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6871 {
6872 struct ref *ref = NULL;
6873 bool tag = FALSE;
6874 bool ltag = FALSE;
6875 bool remote = FALSE;
6876 bool tracked = FALSE;
6877 bool head = FALSE;
6878 int from = 0, to = refs_size - 1;
6880 if (!prefixcmp(name, "refs/tags/")) {
6881 if (!suffixcmp(name, namelen, "^{}")) {
6882 namelen -= 3;
6883 name[namelen] = 0;
6884 } else {
6885 ltag = TRUE;
6886 }
6888 tag = TRUE;
6889 namelen -= STRING_SIZE("refs/tags/");
6890 name += STRING_SIZE("refs/tags/");
6892 } else if (!prefixcmp(name, "refs/remotes/")) {
6893 remote = TRUE;
6894 namelen -= STRING_SIZE("refs/remotes/");
6895 name += STRING_SIZE("refs/remotes/");
6896 tracked = !strcmp(opt_remote, name);
6898 } else if (!prefixcmp(name, "refs/heads/")) {
6899 namelen -= STRING_SIZE("refs/heads/");
6900 name += STRING_SIZE("refs/heads/");
6901 head = !strncmp(opt_head, name, namelen);
6903 } else if (!strcmp(name, "HEAD")) {
6904 string_ncopy(opt_head_rev, id, idlen);
6905 return OK;
6906 }
6908 /* If we are reloading or it's an annotated tag, replace the
6909 * previous SHA1 with the resolved commit id; relies on the fact
6910 * git-ls-remote lists the commit id of an annotated tag right
6911 * before the commit id it points to. */
6912 while (from <= to) {
6913 size_t pos = (to + from) / 2;
6914 int cmp = strcmp(name, refs[pos]->name);
6916 if (!cmp) {
6917 ref = refs[pos];
6918 break;
6919 }
6921 if (cmp < 0)
6922 to = pos - 1;
6923 else
6924 from = pos + 1;
6925 }
6927 if (!ref) {
6928 if (!realloc_refs(&refs, refs_size, 1))
6929 return ERR;
6930 ref = calloc(1, sizeof(*ref) + namelen);
6931 if (!ref)
6932 return ERR;
6933 memmove(refs + from + 1, refs + from,
6934 (refs_size - from) * sizeof(*refs));
6935 refs[from] = ref;
6936 strncpy(ref->name, name, namelen);
6937 refs_size++;
6938 }
6940 ref->head = head;
6941 ref->tag = tag;
6942 ref->ltag = ltag;
6943 ref->remote = remote;
6944 ref->tracked = tracked;
6945 string_copy_rev(ref->id, id);
6947 return OK;
6948 }
6950 static int
6951 load_refs(void)
6952 {
6953 const char *head_argv[] = {
6954 "git", "symbolic-ref", "HEAD", NULL
6955 };
6956 static const char *ls_remote_argv[SIZEOF_ARG] = {
6957 "git", "ls-remote", opt_git_dir, NULL
6958 };
6959 static bool init = FALSE;
6960 size_t i;
6962 if (!init) {
6963 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6964 init = TRUE;
6965 }
6967 if (!*opt_git_dir)
6968 return OK;
6970 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6971 !prefixcmp(opt_head, "refs/heads/")) {
6972 char *offset = opt_head + STRING_SIZE("refs/heads/");
6974 memmove(opt_head, offset, strlen(offset) + 1);
6975 }
6977 for (i = 0; i < refs_size; i++)
6978 refs[i]->id[0] = 0;
6980 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
6981 return ERR;
6983 /* Update the ref lists to reflect changes. */
6984 for (i = 0; i < ref_lists_size; i++) {
6985 struct ref_list *list = ref_lists[i];
6986 size_t old, new;
6988 for (old = new = 0; old < list->size; old++)
6989 if (!strcmp(list->id, list->refs[old]->id))
6990 list->refs[new++] = list->refs[old];
6991 list->size = new;
6992 }
6994 return OK;
6995 }
6997 static void
6998 set_remote_branch(const char *name, const char *value, size_t valuelen)
6999 {
7000 if (!strcmp(name, ".remote")) {
7001 string_ncopy(opt_remote, value, valuelen);
7003 } else if (*opt_remote && !strcmp(name, ".merge")) {
7004 size_t from = strlen(opt_remote);
7006 if (!prefixcmp(value, "refs/heads/"))
7007 value += STRING_SIZE("refs/heads/");
7009 if (!string_format_from(opt_remote, &from, "/%s", value))
7010 opt_remote[0] = 0;
7011 }
7012 }
7014 static void
7015 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7016 {
7017 const char *argv[SIZEOF_ARG] = { name, "=" };
7018 int argc = 1 + (cmd == option_set_command);
7019 int error = ERR;
7021 if (!argv_from_string(argv, &argc, value))
7022 config_msg = "Too many option arguments";
7023 else
7024 error = cmd(argc, argv);
7026 if (error == ERR)
7027 warn("Option 'tig.%s': %s", name, config_msg);
7028 }
7030 static bool
7031 set_environment_variable(const char *name, const char *value)
7032 {
7033 size_t len = strlen(name) + 1 + strlen(value) + 1;
7034 char *env = malloc(len);
7036 if (env &&
7037 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7038 putenv(env) == 0)
7039 return TRUE;
7040 free(env);
7041 return FALSE;
7042 }
7044 static void
7045 set_work_tree(const char *value)
7046 {
7047 char cwd[SIZEOF_STR];
7049 if (!getcwd(cwd, sizeof(cwd)))
7050 die("Failed to get cwd path: %s", strerror(errno));
7051 if (chdir(opt_git_dir) < 0)
7052 die("Failed to chdir(%s): %s", strerror(errno));
7053 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7054 die("Failed to get git path: %s", strerror(errno));
7055 if (chdir(cwd) < 0)
7056 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7057 if (chdir(value) < 0)
7058 die("Failed to chdir(%s): %s", value, strerror(errno));
7059 if (!getcwd(cwd, sizeof(cwd)))
7060 die("Failed to get cwd path: %s", strerror(errno));
7061 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7062 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7063 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7064 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7065 opt_is_inside_work_tree = TRUE;
7066 }
7068 static int
7069 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7070 {
7071 if (!strcmp(name, "i18n.commitencoding"))
7072 string_ncopy(opt_encoding, value, valuelen);
7074 else if (!strcmp(name, "core.editor"))
7075 string_ncopy(opt_editor, value, valuelen);
7077 else if (!strcmp(name, "core.worktree"))
7078 set_work_tree(value);
7080 else if (!prefixcmp(name, "tig.color."))
7081 set_repo_config_option(name + 10, value, option_color_command);
7083 else if (!prefixcmp(name, "tig.bind."))
7084 set_repo_config_option(name + 9, value, option_bind_command);
7086 else if (!prefixcmp(name, "tig."))
7087 set_repo_config_option(name + 4, value, option_set_command);
7089 else if (*opt_head && !prefixcmp(name, "branch.") &&
7090 !strncmp(name + 7, opt_head, strlen(opt_head)))
7091 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7093 return OK;
7094 }
7096 static int
7097 load_git_config(void)
7098 {
7099 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7101 return run_io_load(config_list_argv, "=", read_repo_config_option);
7102 }
7104 static int
7105 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7106 {
7107 if (!opt_git_dir[0]) {
7108 string_ncopy(opt_git_dir, name, namelen);
7110 } else if (opt_is_inside_work_tree == -1) {
7111 /* This can be 3 different values depending on the
7112 * version of git being used. If git-rev-parse does not
7113 * understand --is-inside-work-tree it will simply echo
7114 * the option else either "true" or "false" is printed.
7115 * Default to true for the unknown case. */
7116 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7118 } else if (*name == '.') {
7119 string_ncopy(opt_cdup, name, namelen);
7121 } else {
7122 string_ncopy(opt_prefix, name, namelen);
7123 }
7125 return OK;
7126 }
7128 static int
7129 load_repo_info(void)
7130 {
7131 const char *rev_parse_argv[] = {
7132 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7133 "--show-cdup", "--show-prefix", NULL
7134 };
7136 return run_io_load(rev_parse_argv, "=", read_repo_info);
7137 }
7140 /*
7141 * Main
7142 */
7144 static const char usage[] =
7145 "tig " TIG_VERSION " (" __DATE__ ")\n"
7146 "\n"
7147 "Usage: tig [options] [revs] [--] [paths]\n"
7148 " or: tig show [options] [revs] [--] [paths]\n"
7149 " or: tig blame [rev] path\n"
7150 " or: tig status\n"
7151 " or: tig < [git command output]\n"
7152 "\n"
7153 "Options:\n"
7154 " -v, --version Show version and exit\n"
7155 " -h, --help Show help message and exit";
7157 static void __NORETURN
7158 quit(int sig)
7159 {
7160 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7161 if (cursed)
7162 endwin();
7163 exit(0);
7164 }
7166 static void __NORETURN
7167 die(const char *err, ...)
7168 {
7169 va_list args;
7171 endwin();
7173 va_start(args, err);
7174 fputs("tig: ", stderr);
7175 vfprintf(stderr, err, args);
7176 fputs("\n", stderr);
7177 va_end(args);
7179 exit(1);
7180 }
7182 static void
7183 warn(const char *msg, ...)
7184 {
7185 va_list args;
7187 va_start(args, msg);
7188 fputs("tig warning: ", stderr);
7189 vfprintf(stderr, msg, args);
7190 fputs("\n", stderr);
7191 va_end(args);
7192 }
7194 static enum request
7195 parse_options(int argc, const char *argv[])
7196 {
7197 enum request request = REQ_VIEW_MAIN;
7198 const char *subcommand;
7199 bool seen_dashdash = FALSE;
7200 /* XXX: This is vulnerable to the user overriding options
7201 * required for the main view parser. */
7202 const char *custom_argv[SIZEOF_ARG] = {
7203 "git", "log", "--no-color", "--pretty=raw", "--parents",
7204 "--topo-order", NULL
7205 };
7206 int i, j = 6;
7208 if (!isatty(STDIN_FILENO)) {
7209 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7210 return REQ_VIEW_PAGER;
7211 }
7213 if (argc <= 1)
7214 return REQ_NONE;
7216 subcommand = argv[1];
7217 if (!strcmp(subcommand, "status")) {
7218 if (argc > 2)
7219 warn("ignoring arguments after `%s'", subcommand);
7220 return REQ_VIEW_STATUS;
7222 } else if (!strcmp(subcommand, "blame")) {
7223 if (argc <= 2 || argc > 4)
7224 die("invalid number of options to blame\n\n%s", usage);
7226 i = 2;
7227 if (argc == 4) {
7228 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7229 i++;
7230 }
7232 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7233 return REQ_VIEW_BLAME;
7235 } else if (!strcmp(subcommand, "show")) {
7236 request = REQ_VIEW_DIFF;
7238 } else {
7239 subcommand = NULL;
7240 }
7242 if (subcommand) {
7243 custom_argv[1] = subcommand;
7244 j = 2;
7245 }
7247 for (i = 1 + !!subcommand; i < argc; i++) {
7248 const char *opt = argv[i];
7250 if (seen_dashdash || !strcmp(opt, "--")) {
7251 seen_dashdash = TRUE;
7253 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7254 printf("tig version %s\n", TIG_VERSION);
7255 quit(0);
7257 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7258 printf("%s\n", usage);
7259 quit(0);
7260 }
7262 custom_argv[j++] = opt;
7263 if (j >= ARRAY_SIZE(custom_argv))
7264 die("command too long");
7265 }
7267 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7268 die("Failed to format arguments");
7270 return request;
7271 }
7273 int
7274 main(int argc, const char *argv[])
7275 {
7276 enum request request = parse_options(argc, argv);
7277 struct view *view;
7278 size_t i;
7280 signal(SIGINT, quit);
7281 signal(SIGPIPE, SIG_IGN);
7283 if (setlocale(LC_ALL, "")) {
7284 char *codeset = nl_langinfo(CODESET);
7286 string_ncopy(opt_codeset, codeset, strlen(codeset));
7287 }
7289 if (load_repo_info() == ERR)
7290 die("Failed to load repo info.");
7292 if (load_options() == ERR)
7293 die("Failed to load user config.");
7295 if (load_git_config() == ERR)
7296 die("Failed to load repo config.");
7298 /* Require a git repository unless when running in pager mode. */
7299 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7300 die("Not a git repository");
7302 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7303 opt_utf8 = FALSE;
7305 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7306 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7307 if (opt_iconv == ICONV_NONE)
7308 die("Failed to initialize character set conversion");
7309 }
7311 if (load_refs() == ERR)
7312 die("Failed to load refs.");
7314 foreach_view (view, i)
7315 argv_from_env(view->ops->argv, view->cmd_env);
7317 init_display();
7319 if (request != REQ_NONE)
7320 open_view(NULL, request, OPEN_PREPARED);
7321 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7323 while (view_driver(display[current_view], request)) {
7324 int key = get_input(0);
7326 view = display[current_view];
7327 request = get_keybinding(view->keymap, key);
7329 /* Some low-level request handling. This keeps access to
7330 * status_win restricted. */
7331 switch (request) {
7332 case REQ_PROMPT:
7333 {
7334 char *cmd = read_prompt(":");
7336 if (cmd && isdigit(*cmd)) {
7337 int lineno = view->lineno + 1;
7339 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7340 select_view_line(view, lineno - 1);
7341 report("");
7342 } else {
7343 report("Unable to parse '%s' as a line number", cmd);
7344 }
7346 } else if (cmd) {
7347 struct view *next = VIEW(REQ_VIEW_PAGER);
7348 const char *argv[SIZEOF_ARG] = { "git" };
7349 int argc = 1;
7351 /* When running random commands, initially show the
7352 * command in the title. However, it maybe later be
7353 * overwritten if a commit line is selected. */
7354 string_ncopy(next->ref, cmd, strlen(cmd));
7356 if (!argv_from_string(argv, &argc, cmd)) {
7357 report("Too many arguments");
7358 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7359 report("Failed to format command");
7360 } else {
7361 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7362 }
7363 }
7365 request = REQ_NONE;
7366 break;
7367 }
7368 case REQ_SEARCH:
7369 case REQ_SEARCH_BACK:
7370 {
7371 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7372 char *search = read_prompt(prompt);
7374 if (search)
7375 string_ncopy(opt_search, search, strlen(search));
7376 else if (*opt_search)
7377 request = request == REQ_SEARCH ?
7378 REQ_FIND_NEXT :
7379 REQ_FIND_PREV;
7380 else
7381 request = REQ_NONE;
7382 break;
7383 }
7384 default:
7385 break;
7386 }
7387 }
7389 quit(0);
7391 return 0;
7392 }