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);
4869 view->p_restore = TRUE;
4871 return TRUE;
4872 }
4874 static bool
4875 branch_grep(struct view *view, struct line *line)
4876 {
4877 struct branch *branch = line->data;
4878 const char *text[] = {
4879 branch->ref->name,
4880 branch->author,
4881 NULL
4882 };
4884 return grep_text(view, text);
4885 }
4887 static void
4888 branch_select(struct view *view, struct line *line)
4889 {
4890 struct branch *branch = line->data;
4892 string_copy_rev(view->ref, branch->ref->id);
4893 string_copy_rev(ref_commit, branch->ref->id);
4894 string_copy_rev(ref_head, branch->ref->id);
4895 }
4897 static struct view_ops branch_ops = {
4898 "branch",
4899 NULL,
4900 branch_open,
4901 branch_read,
4902 branch_draw,
4903 branch_request,
4904 branch_grep,
4905 branch_select,
4906 };
4908 /*
4909 * Status backend
4910 */
4912 struct status {
4913 char status;
4914 struct {
4915 mode_t mode;
4916 char rev[SIZEOF_REV];
4917 char name[SIZEOF_STR];
4918 } old;
4919 struct {
4920 mode_t mode;
4921 char rev[SIZEOF_REV];
4922 char name[SIZEOF_STR];
4923 } new;
4924 };
4926 static char status_onbranch[SIZEOF_STR];
4927 static struct status stage_status;
4928 static enum line_type stage_line_type;
4929 static size_t stage_chunks;
4930 static int *stage_chunk;
4932 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4934 /* This should work even for the "On branch" line. */
4935 static inline bool
4936 status_has_none(struct view *view, struct line *line)
4937 {
4938 return line < view->line + view->lines && !line[1].data;
4939 }
4941 /* Get fields from the diff line:
4942 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4943 */
4944 static inline bool
4945 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4946 {
4947 const char *old_mode = buf + 1;
4948 const char *new_mode = buf + 8;
4949 const char *old_rev = buf + 15;
4950 const char *new_rev = buf + 56;
4951 const char *status = buf + 97;
4953 if (bufsize < 98 ||
4954 old_mode[-1] != ':' ||
4955 new_mode[-1] != ' ' ||
4956 old_rev[-1] != ' ' ||
4957 new_rev[-1] != ' ' ||
4958 status[-1] != ' ')
4959 return FALSE;
4961 file->status = *status;
4963 string_copy_rev(file->old.rev, old_rev);
4964 string_copy_rev(file->new.rev, new_rev);
4966 file->old.mode = strtoul(old_mode, NULL, 8);
4967 file->new.mode = strtoul(new_mode, NULL, 8);
4969 file->old.name[0] = file->new.name[0] = 0;
4971 return TRUE;
4972 }
4974 static bool
4975 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4976 {
4977 struct status *unmerged = NULL;
4978 char *buf;
4979 struct io io = {};
4981 if (!run_io(&io, argv, NULL, IO_RD))
4982 return FALSE;
4984 add_line_data(view, NULL, type);
4986 while ((buf = io_get(&io, 0, TRUE))) {
4987 struct status *file = unmerged;
4989 if (!file) {
4990 file = calloc(1, sizeof(*file));
4991 if (!file || !add_line_data(view, file, type))
4992 goto error_out;
4993 }
4995 /* Parse diff info part. */
4996 if (status) {
4997 file->status = status;
4998 if (status == 'A')
4999 string_copy(file->old.rev, NULL_ID);
5001 } else if (!file->status || file == unmerged) {
5002 if (!status_get_diff(file, buf, strlen(buf)))
5003 goto error_out;
5005 buf = io_get(&io, 0, TRUE);
5006 if (!buf)
5007 break;
5009 /* Collapse all modified entries that follow an
5010 * associated unmerged entry. */
5011 if (unmerged == file) {
5012 unmerged->status = 'U';
5013 unmerged = NULL;
5014 } else if (file->status == 'U') {
5015 unmerged = file;
5016 }
5017 }
5019 /* Grab the old name for rename/copy. */
5020 if (!*file->old.name &&
5021 (file->status == 'R' || file->status == 'C')) {
5022 string_ncopy(file->old.name, buf, strlen(buf));
5024 buf = io_get(&io, 0, TRUE);
5025 if (!buf)
5026 break;
5027 }
5029 /* git-ls-files just delivers a NUL separated list of
5030 * file names similar to the second half of the
5031 * git-diff-* output. */
5032 string_ncopy(file->new.name, buf, strlen(buf));
5033 if (!*file->old.name)
5034 string_copy(file->old.name, file->new.name);
5035 file = NULL;
5036 }
5038 if (io_error(&io)) {
5039 error_out:
5040 done_io(&io);
5041 return FALSE;
5042 }
5044 if (!view->line[view->lines - 1].data)
5045 add_line_data(view, NULL, LINE_STAT_NONE);
5047 done_io(&io);
5048 return TRUE;
5049 }
5051 /* Don't show unmerged entries in the staged section. */
5052 static const char *status_diff_index_argv[] = {
5053 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5054 "--cached", "-M", "HEAD", NULL
5055 };
5057 static const char *status_diff_files_argv[] = {
5058 "git", "diff-files", "-z", NULL
5059 };
5061 static const char *status_list_other_argv[] = {
5062 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5063 };
5065 static const char *status_list_no_head_argv[] = {
5066 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5067 };
5069 static const char *update_index_argv[] = {
5070 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5071 };
5073 /* Restore the previous line number to stay in the context or select a
5074 * line with something that can be updated. */
5075 static void
5076 status_restore(struct view *view)
5077 {
5078 if (view->p_lineno >= view->lines)
5079 view->p_lineno = view->lines - 1;
5080 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5081 view->p_lineno++;
5082 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5083 view->p_lineno--;
5085 /* If the above fails, always skip the "On branch" line. */
5086 if (view->p_lineno < view->lines)
5087 view->lineno = view->p_lineno;
5088 else
5089 view->lineno = 1;
5091 if (view->lineno < view->offset)
5092 view->offset = view->lineno;
5093 else if (view->offset + view->height <= view->lineno)
5094 view->offset = view->lineno - view->height + 1;
5096 view->p_restore = FALSE;
5097 }
5099 static void
5100 status_update_onbranch(void)
5101 {
5102 static const char *paths[][2] = {
5103 { "rebase-apply/rebasing", "Rebasing" },
5104 { "rebase-apply/applying", "Applying mailbox" },
5105 { "rebase-apply/", "Rebasing mailbox" },
5106 { "rebase-merge/interactive", "Interactive rebase" },
5107 { "rebase-merge/", "Rebase merge" },
5108 { "MERGE_HEAD", "Merging" },
5109 { "BISECT_LOG", "Bisecting" },
5110 { "HEAD", "On branch" },
5111 };
5112 char buf[SIZEOF_STR];
5113 struct stat stat;
5114 int i;
5116 if (is_initial_commit()) {
5117 string_copy(status_onbranch, "Initial commit");
5118 return;
5119 }
5121 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5122 char *head = opt_head;
5124 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5125 lstat(buf, &stat) < 0)
5126 continue;
5128 if (!*opt_head) {
5129 struct io io = {};
5131 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5132 io_open(&io, buf) &&
5133 io_read_buf(&io, buf, sizeof(buf))) {
5134 head = buf;
5135 if (!prefixcmp(head, "refs/heads/"))
5136 head += STRING_SIZE("refs/heads/");
5137 }
5138 }
5140 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5141 string_copy(status_onbranch, opt_head);
5142 return;
5143 }
5145 string_copy(status_onbranch, "Not currently on any branch");
5146 }
5148 /* First parse staged info using git-diff-index(1), then parse unstaged
5149 * info using git-diff-files(1), and finally untracked files using
5150 * git-ls-files(1). */
5151 static bool
5152 status_open(struct view *view)
5153 {
5154 reset_view(view);
5156 add_line_data(view, NULL, LINE_STAT_HEAD);
5157 status_update_onbranch();
5159 run_io_bg(update_index_argv);
5161 if (is_initial_commit()) {
5162 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5163 return FALSE;
5164 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5165 return FALSE;
5166 }
5168 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5169 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5170 return FALSE;
5172 /* Restore the exact position or use the specialized restore
5173 * mode? */
5174 if (!view->p_restore)
5175 status_restore(view);
5176 return TRUE;
5177 }
5179 static bool
5180 status_draw(struct view *view, struct line *line, unsigned int lineno)
5181 {
5182 struct status *status = line->data;
5183 enum line_type type;
5184 const char *text;
5186 if (!status) {
5187 switch (line->type) {
5188 case LINE_STAT_STAGED:
5189 type = LINE_STAT_SECTION;
5190 text = "Changes to be committed:";
5191 break;
5193 case LINE_STAT_UNSTAGED:
5194 type = LINE_STAT_SECTION;
5195 text = "Changed but not updated:";
5196 break;
5198 case LINE_STAT_UNTRACKED:
5199 type = LINE_STAT_SECTION;
5200 text = "Untracked files:";
5201 break;
5203 case LINE_STAT_NONE:
5204 type = LINE_DEFAULT;
5205 text = " (no files)";
5206 break;
5208 case LINE_STAT_HEAD:
5209 type = LINE_STAT_HEAD;
5210 text = status_onbranch;
5211 break;
5213 default:
5214 return FALSE;
5215 }
5216 } else {
5217 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5219 buf[0] = status->status;
5220 if (draw_text(view, line->type, buf, TRUE))
5221 return TRUE;
5222 type = LINE_DEFAULT;
5223 text = status->new.name;
5224 }
5226 draw_text(view, type, text, TRUE);
5227 return TRUE;
5228 }
5230 static enum request
5231 status_load_error(struct view *view, struct view *stage, const char *path)
5232 {
5233 if (displayed_views() == 2 || display[current_view] != view)
5234 maximize_view(view);
5235 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5236 return REQ_NONE;
5237 }
5239 static enum request
5240 status_enter(struct view *view, struct line *line)
5241 {
5242 struct status *status = line->data;
5243 const char *oldpath = status ? status->old.name : NULL;
5244 /* Diffs for unmerged entries are empty when passing the new
5245 * path, so leave it empty. */
5246 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5247 const char *info;
5248 enum open_flags split;
5249 struct view *stage = VIEW(REQ_VIEW_STAGE);
5251 if (line->type == LINE_STAT_NONE ||
5252 (!status && line[1].type == LINE_STAT_NONE)) {
5253 report("No file to diff");
5254 return REQ_NONE;
5255 }
5257 switch (line->type) {
5258 case LINE_STAT_STAGED:
5259 if (is_initial_commit()) {
5260 const char *no_head_diff_argv[] = {
5261 "git", "diff", "--no-color", "--patch-with-stat",
5262 "--", "/dev/null", newpath, NULL
5263 };
5265 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5266 return status_load_error(view, stage, newpath);
5267 } else {
5268 const char *index_show_argv[] = {
5269 "git", "diff-index", "--root", "--patch-with-stat",
5270 "-C", "-M", "--cached", "HEAD", "--",
5271 oldpath, newpath, NULL
5272 };
5274 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5275 return status_load_error(view, stage, newpath);
5276 }
5278 if (status)
5279 info = "Staged changes to %s";
5280 else
5281 info = "Staged changes";
5282 break;
5284 case LINE_STAT_UNSTAGED:
5285 {
5286 const char *files_show_argv[] = {
5287 "git", "diff-files", "--root", "--patch-with-stat",
5288 "-C", "-M", "--", oldpath, newpath, NULL
5289 };
5291 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5292 return status_load_error(view, stage, newpath);
5293 if (status)
5294 info = "Unstaged changes to %s";
5295 else
5296 info = "Unstaged changes";
5297 break;
5298 }
5299 case LINE_STAT_UNTRACKED:
5300 if (!newpath) {
5301 report("No file to show");
5302 return REQ_NONE;
5303 }
5305 if (!suffixcmp(status->new.name, -1, "/")) {
5306 report("Cannot display a directory");
5307 return REQ_NONE;
5308 }
5310 if (!prepare_update_file(stage, newpath))
5311 return status_load_error(view, stage, newpath);
5312 info = "Untracked file %s";
5313 break;
5315 case LINE_STAT_HEAD:
5316 return REQ_NONE;
5318 default:
5319 die("line type %d not handled in switch", line->type);
5320 }
5322 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5323 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5324 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5325 if (status) {
5326 stage_status = *status;
5327 } else {
5328 memset(&stage_status, 0, sizeof(stage_status));
5329 }
5331 stage_line_type = line->type;
5332 stage_chunks = 0;
5333 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5334 }
5336 return REQ_NONE;
5337 }
5339 static bool
5340 status_exists(struct status *status, enum line_type type)
5341 {
5342 struct view *view = VIEW(REQ_VIEW_STATUS);
5343 unsigned long lineno;
5345 for (lineno = 0; lineno < view->lines; lineno++) {
5346 struct line *line = &view->line[lineno];
5347 struct status *pos = line->data;
5349 if (line->type != type)
5350 continue;
5351 if (!pos && (!status || !status->status) && line[1].data) {
5352 select_view_line(view, lineno);
5353 return TRUE;
5354 }
5355 if (pos && !strcmp(status->new.name, pos->new.name)) {
5356 select_view_line(view, lineno);
5357 return TRUE;
5358 }
5359 }
5361 return FALSE;
5362 }
5365 static bool
5366 status_update_prepare(struct io *io, enum line_type type)
5367 {
5368 const char *staged_argv[] = {
5369 "git", "update-index", "-z", "--index-info", NULL
5370 };
5371 const char *others_argv[] = {
5372 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5373 };
5375 switch (type) {
5376 case LINE_STAT_STAGED:
5377 return run_io(io, staged_argv, opt_cdup, IO_WR);
5379 case LINE_STAT_UNSTAGED:
5380 return run_io(io, others_argv, opt_cdup, IO_WR);
5382 case LINE_STAT_UNTRACKED:
5383 return run_io(io, others_argv, NULL, IO_WR);
5385 default:
5386 die("line type %d not handled in switch", type);
5387 return FALSE;
5388 }
5389 }
5391 static bool
5392 status_update_write(struct io *io, struct status *status, enum line_type type)
5393 {
5394 char buf[SIZEOF_STR];
5395 size_t bufsize = 0;
5397 switch (type) {
5398 case LINE_STAT_STAGED:
5399 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5400 status->old.mode,
5401 status->old.rev,
5402 status->old.name, 0))
5403 return FALSE;
5404 break;
5406 case LINE_STAT_UNSTAGED:
5407 case LINE_STAT_UNTRACKED:
5408 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5409 return FALSE;
5410 break;
5412 default:
5413 die("line type %d not handled in switch", type);
5414 }
5416 return io_write(io, buf, bufsize);
5417 }
5419 static bool
5420 status_update_file(struct status *status, enum line_type type)
5421 {
5422 struct io io = {};
5423 bool result;
5425 if (!status_update_prepare(&io, type))
5426 return FALSE;
5428 result = status_update_write(&io, status, type);
5429 return done_io(&io) && result;
5430 }
5432 static bool
5433 status_update_files(struct view *view, struct line *line)
5434 {
5435 char buf[sizeof(view->ref)];
5436 struct io io = {};
5437 bool result = TRUE;
5438 struct line *pos = view->line + view->lines;
5439 int files = 0;
5440 int file, done;
5441 int cursor_y, cursor_x;
5443 if (!status_update_prepare(&io, line->type))
5444 return FALSE;
5446 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5447 files++;
5449 string_copy(buf, view->ref);
5450 getsyx(cursor_y, cursor_x);
5451 for (file = 0, done = 5; result && file < files; line++, file++) {
5452 int almost_done = file * 100 / files;
5454 if (almost_done > done) {
5455 done = almost_done;
5456 string_format(view->ref, "updating file %u of %u (%d%% done)",
5457 file, files, done);
5458 update_view_title(view);
5459 setsyx(cursor_y, cursor_x);
5460 doupdate();
5461 }
5462 result = status_update_write(&io, line->data, line->type);
5463 }
5464 string_copy(view->ref, buf);
5466 return done_io(&io) && result;
5467 }
5469 static bool
5470 status_update(struct view *view)
5471 {
5472 struct line *line = &view->line[view->lineno];
5474 assert(view->lines);
5476 if (!line->data) {
5477 /* This should work even for the "On branch" line. */
5478 if (line < view->line + view->lines && !line[1].data) {
5479 report("Nothing to update");
5480 return FALSE;
5481 }
5483 if (!status_update_files(view, line + 1)) {
5484 report("Failed to update file status");
5485 return FALSE;
5486 }
5488 } else if (!status_update_file(line->data, line->type)) {
5489 report("Failed to update file status");
5490 return FALSE;
5491 }
5493 return TRUE;
5494 }
5496 static bool
5497 status_revert(struct status *status, enum line_type type, bool has_none)
5498 {
5499 if (!status || type != LINE_STAT_UNSTAGED) {
5500 if (type == LINE_STAT_STAGED) {
5501 report("Cannot revert changes to staged files");
5502 } else if (type == LINE_STAT_UNTRACKED) {
5503 report("Cannot revert changes to untracked files");
5504 } else if (has_none) {
5505 report("Nothing to revert");
5506 } else {
5507 report("Cannot revert changes to multiple files");
5508 }
5509 return FALSE;
5511 } else {
5512 char mode[10] = "100644";
5513 const char *reset_argv[] = {
5514 "git", "update-index", "--cacheinfo", mode,
5515 status->old.rev, status->old.name, NULL
5516 };
5517 const char *checkout_argv[] = {
5518 "git", "checkout", "--", status->old.name, NULL
5519 };
5521 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5522 return FALSE;
5523 string_format(mode, "%o", status->old.mode);
5524 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5525 run_io_fg(checkout_argv, opt_cdup);
5526 }
5527 }
5529 static enum request
5530 status_request(struct view *view, enum request request, struct line *line)
5531 {
5532 struct status *status = line->data;
5534 switch (request) {
5535 case REQ_STATUS_UPDATE:
5536 if (!status_update(view))
5537 return REQ_NONE;
5538 break;
5540 case REQ_STATUS_REVERT:
5541 if (!status_revert(status, line->type, status_has_none(view, line)))
5542 return REQ_NONE;
5543 break;
5545 case REQ_STATUS_MERGE:
5546 if (!status || status->status != 'U') {
5547 report("Merging only possible for files with unmerged status ('U').");
5548 return REQ_NONE;
5549 }
5550 open_mergetool(status->new.name);
5551 break;
5553 case REQ_EDIT:
5554 if (!status)
5555 return request;
5556 if (status->status == 'D') {
5557 report("File has been deleted.");
5558 return REQ_NONE;
5559 }
5561 open_editor(status->status != '?', status->new.name);
5562 break;
5564 case REQ_VIEW_BLAME:
5565 if (status) {
5566 string_copy(opt_file, status->new.name);
5567 opt_ref[0] = 0;
5568 }
5569 return request;
5571 case REQ_ENTER:
5572 /* After returning the status view has been split to
5573 * show the stage view. No further reloading is
5574 * necessary. */
5575 return status_enter(view, line);
5577 case REQ_REFRESH:
5578 /* Simply reload the view. */
5579 break;
5581 default:
5582 return request;
5583 }
5585 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5587 return REQ_NONE;
5588 }
5590 static void
5591 status_select(struct view *view, struct line *line)
5592 {
5593 struct status *status = line->data;
5594 char file[SIZEOF_STR] = "all files";
5595 const char *text;
5596 const char *key;
5598 if (status && !string_format(file, "'%s'", status->new.name))
5599 return;
5601 if (!status && line[1].type == LINE_STAT_NONE)
5602 line++;
5604 switch (line->type) {
5605 case LINE_STAT_STAGED:
5606 text = "Press %s to unstage %s for commit";
5607 break;
5609 case LINE_STAT_UNSTAGED:
5610 text = "Press %s to stage %s for commit";
5611 break;
5613 case LINE_STAT_UNTRACKED:
5614 text = "Press %s to stage %s for addition";
5615 break;
5617 case LINE_STAT_HEAD:
5618 case LINE_STAT_NONE:
5619 text = "Nothing to update";
5620 break;
5622 default:
5623 die("line type %d not handled in switch", line->type);
5624 }
5626 if (status && status->status == 'U') {
5627 text = "Press %s to resolve conflict in %s";
5628 key = get_key(REQ_STATUS_MERGE);
5630 } else {
5631 key = get_key(REQ_STATUS_UPDATE);
5632 }
5634 string_format(view->ref, text, key, file);
5635 }
5637 static bool
5638 status_grep(struct view *view, struct line *line)
5639 {
5640 struct status *status = line->data;
5642 if (status) {
5643 const char buf[2] = { status->status, 0 };
5644 const char *text[] = { status->new.name, buf, NULL };
5646 return grep_text(view, text);
5647 }
5649 return FALSE;
5650 }
5652 static struct view_ops status_ops = {
5653 "file",
5654 NULL,
5655 status_open,
5656 NULL,
5657 status_draw,
5658 status_request,
5659 status_grep,
5660 status_select,
5661 };
5664 static bool
5665 stage_diff_write(struct io *io, struct line *line, struct line *end)
5666 {
5667 while (line < end) {
5668 if (!io_write(io, line->data, strlen(line->data)) ||
5669 !io_write(io, "\n", 1))
5670 return FALSE;
5671 line++;
5672 if (line->type == LINE_DIFF_CHUNK ||
5673 line->type == LINE_DIFF_HEADER)
5674 break;
5675 }
5677 return TRUE;
5678 }
5680 static struct line *
5681 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5682 {
5683 for (; view->line < line; line--)
5684 if (line->type == type)
5685 return line;
5687 return NULL;
5688 }
5690 static bool
5691 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5692 {
5693 const char *apply_argv[SIZEOF_ARG] = {
5694 "git", "apply", "--whitespace=nowarn", NULL
5695 };
5696 struct line *diff_hdr;
5697 struct io io = {};
5698 int argc = 3;
5700 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5701 if (!diff_hdr)
5702 return FALSE;
5704 if (!revert)
5705 apply_argv[argc++] = "--cached";
5706 if (revert || stage_line_type == LINE_STAT_STAGED)
5707 apply_argv[argc++] = "-R";
5708 apply_argv[argc++] = "-";
5709 apply_argv[argc++] = NULL;
5710 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5711 return FALSE;
5713 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5714 !stage_diff_write(&io, chunk, view->line + view->lines))
5715 chunk = NULL;
5717 done_io(&io);
5718 run_io_bg(update_index_argv);
5720 return chunk ? TRUE : FALSE;
5721 }
5723 static bool
5724 stage_update(struct view *view, struct line *line)
5725 {
5726 struct line *chunk = NULL;
5728 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5729 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5731 if (chunk) {
5732 if (!stage_apply_chunk(view, chunk, FALSE)) {
5733 report("Failed to apply chunk");
5734 return FALSE;
5735 }
5737 } else if (!stage_status.status) {
5738 view = VIEW(REQ_VIEW_STATUS);
5740 for (line = view->line; line < view->line + view->lines; line++)
5741 if (line->type == stage_line_type)
5742 break;
5744 if (!status_update_files(view, line + 1)) {
5745 report("Failed to update files");
5746 return FALSE;
5747 }
5749 } else if (!status_update_file(&stage_status, stage_line_type)) {
5750 report("Failed to update file");
5751 return FALSE;
5752 }
5754 return TRUE;
5755 }
5757 static bool
5758 stage_revert(struct view *view, struct line *line)
5759 {
5760 struct line *chunk = NULL;
5762 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5763 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5765 if (chunk) {
5766 if (!prompt_yesno("Are you sure you want to revert changes?"))
5767 return FALSE;
5769 if (!stage_apply_chunk(view, chunk, TRUE)) {
5770 report("Failed to revert chunk");
5771 return FALSE;
5772 }
5773 return TRUE;
5775 } else {
5776 return status_revert(stage_status.status ? &stage_status : NULL,
5777 stage_line_type, FALSE);
5778 }
5779 }
5782 static void
5783 stage_next(struct view *view, struct line *line)
5784 {
5785 int i;
5787 if (!stage_chunks) {
5788 for (line = view->line; line < view->line + view->lines; line++) {
5789 if (line->type != LINE_DIFF_CHUNK)
5790 continue;
5792 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5793 report("Allocation failure");
5794 return;
5795 }
5797 stage_chunk[stage_chunks++] = line - view->line;
5798 }
5799 }
5801 for (i = 0; i < stage_chunks; i++) {
5802 if (stage_chunk[i] > view->lineno) {
5803 do_scroll_view(view, stage_chunk[i] - view->lineno);
5804 report("Chunk %d of %d", i + 1, stage_chunks);
5805 return;
5806 }
5807 }
5809 report("No next chunk found");
5810 }
5812 static enum request
5813 stage_request(struct view *view, enum request request, struct line *line)
5814 {
5815 switch (request) {
5816 case REQ_STATUS_UPDATE:
5817 if (!stage_update(view, line))
5818 return REQ_NONE;
5819 break;
5821 case REQ_STATUS_REVERT:
5822 if (!stage_revert(view, line))
5823 return REQ_NONE;
5824 break;
5826 case REQ_STAGE_NEXT:
5827 if (stage_line_type == LINE_STAT_UNTRACKED) {
5828 report("File is untracked; press %s to add",
5829 get_key(REQ_STATUS_UPDATE));
5830 return REQ_NONE;
5831 }
5832 stage_next(view, line);
5833 return REQ_NONE;
5835 case REQ_EDIT:
5836 if (!stage_status.new.name[0])
5837 return request;
5838 if (stage_status.status == 'D') {
5839 report("File has been deleted.");
5840 return REQ_NONE;
5841 }
5843 open_editor(stage_status.status != '?', stage_status.new.name);
5844 break;
5846 case REQ_REFRESH:
5847 /* Reload everything ... */
5848 break;
5850 case REQ_VIEW_BLAME:
5851 if (stage_status.new.name[0]) {
5852 string_copy(opt_file, stage_status.new.name);
5853 opt_ref[0] = 0;
5854 }
5855 return request;
5857 case REQ_ENTER:
5858 return pager_request(view, request, line);
5860 default:
5861 return request;
5862 }
5864 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5865 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5867 /* Check whether the staged entry still exists, and close the
5868 * stage view if it doesn't. */
5869 if (!status_exists(&stage_status, stage_line_type)) {
5870 status_restore(VIEW(REQ_VIEW_STATUS));
5871 return REQ_VIEW_CLOSE;
5872 }
5874 if (stage_line_type == LINE_STAT_UNTRACKED) {
5875 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5876 report("Cannot display a directory");
5877 return REQ_NONE;
5878 }
5880 if (!prepare_update_file(view, stage_status.new.name)) {
5881 report("Failed to open file: %s", strerror(errno));
5882 return REQ_NONE;
5883 }
5884 }
5885 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5887 return REQ_NONE;
5888 }
5890 static struct view_ops stage_ops = {
5891 "line",
5892 NULL,
5893 NULL,
5894 pager_read,
5895 pager_draw,
5896 stage_request,
5897 pager_grep,
5898 pager_select,
5899 };
5902 /*
5903 * Revision graph
5904 */
5906 struct commit {
5907 char id[SIZEOF_REV]; /* SHA1 ID. */
5908 char title[128]; /* First line of the commit message. */
5909 const char *author; /* Author of the commit. */
5910 time_t time; /* Date from the author ident. */
5911 struct ref_list *refs; /* Repository references. */
5912 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5913 size_t graph_size; /* The width of the graph array. */
5914 bool has_parents; /* Rewritten --parents seen. */
5915 };
5917 /* Size of rev graph with no "padding" columns */
5918 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5920 struct rev_graph {
5921 struct rev_graph *prev, *next, *parents;
5922 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5923 size_t size;
5924 struct commit *commit;
5925 size_t pos;
5926 unsigned int boundary:1;
5927 };
5929 /* Parents of the commit being visualized. */
5930 static struct rev_graph graph_parents[4];
5932 /* The current stack of revisions on the graph. */
5933 static struct rev_graph graph_stacks[4] = {
5934 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5935 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5936 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5937 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5938 };
5940 static inline bool
5941 graph_parent_is_merge(struct rev_graph *graph)
5942 {
5943 return graph->parents->size > 1;
5944 }
5946 static inline void
5947 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5948 {
5949 struct commit *commit = graph->commit;
5951 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5952 commit->graph[commit->graph_size++] = symbol;
5953 }
5955 static void
5956 clear_rev_graph(struct rev_graph *graph)
5957 {
5958 graph->boundary = 0;
5959 graph->size = graph->pos = 0;
5960 graph->commit = NULL;
5961 memset(graph->parents, 0, sizeof(*graph->parents));
5962 }
5964 static void
5965 done_rev_graph(struct rev_graph *graph)
5966 {
5967 if (graph_parent_is_merge(graph) &&
5968 graph->pos < graph->size - 1 &&
5969 graph->next->size == graph->size + graph->parents->size - 1) {
5970 size_t i = graph->pos + graph->parents->size - 1;
5972 graph->commit->graph_size = i * 2;
5973 while (i < graph->next->size - 1) {
5974 append_to_rev_graph(graph, ' ');
5975 append_to_rev_graph(graph, '\\');
5976 i++;
5977 }
5978 }
5980 clear_rev_graph(graph);
5981 }
5983 static void
5984 push_rev_graph(struct rev_graph *graph, const char *parent)
5985 {
5986 int i;
5988 /* "Collapse" duplicate parents lines.
5989 *
5990 * FIXME: This needs to also update update the drawn graph but
5991 * for now it just serves as a method for pruning graph lines. */
5992 for (i = 0; i < graph->size; i++)
5993 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5994 return;
5996 if (graph->size < SIZEOF_REVITEMS) {
5997 string_copy_rev(graph->rev[graph->size++], parent);
5998 }
5999 }
6001 static chtype
6002 get_rev_graph_symbol(struct rev_graph *graph)
6003 {
6004 chtype symbol;
6006 if (graph->boundary)
6007 symbol = REVGRAPH_BOUND;
6008 else if (graph->parents->size == 0)
6009 symbol = REVGRAPH_INIT;
6010 else if (graph_parent_is_merge(graph))
6011 symbol = REVGRAPH_MERGE;
6012 else if (graph->pos >= graph->size)
6013 symbol = REVGRAPH_BRANCH;
6014 else
6015 symbol = REVGRAPH_COMMIT;
6017 return symbol;
6018 }
6020 static void
6021 draw_rev_graph(struct rev_graph *graph)
6022 {
6023 struct rev_filler {
6024 chtype separator, line;
6025 };
6026 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6027 static struct rev_filler fillers[] = {
6028 { ' ', '|' },
6029 { '`', '.' },
6030 { '\'', ' ' },
6031 { '/', ' ' },
6032 };
6033 chtype symbol = get_rev_graph_symbol(graph);
6034 struct rev_filler *filler;
6035 size_t i;
6037 if (opt_line_graphics)
6038 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6040 filler = &fillers[DEFAULT];
6042 for (i = 0; i < graph->pos; i++) {
6043 append_to_rev_graph(graph, filler->line);
6044 if (graph_parent_is_merge(graph->prev) &&
6045 graph->prev->pos == i)
6046 filler = &fillers[RSHARP];
6048 append_to_rev_graph(graph, filler->separator);
6049 }
6051 /* Place the symbol for this revision. */
6052 append_to_rev_graph(graph, symbol);
6054 if (graph->prev->size > graph->size)
6055 filler = &fillers[RDIAG];
6056 else
6057 filler = &fillers[DEFAULT];
6059 i++;
6061 for (; i < graph->size; i++) {
6062 append_to_rev_graph(graph, filler->separator);
6063 append_to_rev_graph(graph, filler->line);
6064 if (graph_parent_is_merge(graph->prev) &&
6065 i < graph->prev->pos + graph->parents->size)
6066 filler = &fillers[RSHARP];
6067 if (graph->prev->size > graph->size)
6068 filler = &fillers[LDIAG];
6069 }
6071 if (graph->prev->size > graph->size) {
6072 append_to_rev_graph(graph, filler->separator);
6073 if (filler->line != ' ')
6074 append_to_rev_graph(graph, filler->line);
6075 }
6076 }
6078 /* Prepare the next rev graph */
6079 static void
6080 prepare_rev_graph(struct rev_graph *graph)
6081 {
6082 size_t i;
6084 /* First, traverse all lines of revisions up to the active one. */
6085 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6086 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6087 break;
6089 push_rev_graph(graph->next, graph->rev[graph->pos]);
6090 }
6092 /* Interleave the new revision parent(s). */
6093 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6094 push_rev_graph(graph->next, graph->parents->rev[i]);
6096 /* Lastly, put any remaining revisions. */
6097 for (i = graph->pos + 1; i < graph->size; i++)
6098 push_rev_graph(graph->next, graph->rev[i]);
6099 }
6101 static void
6102 update_rev_graph(struct view *view, struct rev_graph *graph)
6103 {
6104 /* If this is the finalizing update ... */
6105 if (graph->commit)
6106 prepare_rev_graph(graph);
6108 /* Graph visualization needs a one rev look-ahead,
6109 * so the first update doesn't visualize anything. */
6110 if (!graph->prev->commit)
6111 return;
6113 if (view->lines > 2)
6114 view->line[view->lines - 3].dirty = 1;
6115 if (view->lines > 1)
6116 view->line[view->lines - 2].dirty = 1;
6117 draw_rev_graph(graph->prev);
6118 done_rev_graph(graph->prev->prev);
6119 }
6122 /*
6123 * Main view backend
6124 */
6126 static const char *main_argv[SIZEOF_ARG] = {
6127 "git", "log", "--no-color", "--pretty=raw", "--parents",
6128 "--topo-order", "%(head)", NULL
6129 };
6131 static bool
6132 main_draw(struct view *view, struct line *line, unsigned int lineno)
6133 {
6134 struct commit *commit = line->data;
6136 if (!commit->author)
6137 return FALSE;
6139 if (opt_date && draw_date(view, &commit->time))
6140 return TRUE;
6142 if (opt_author && draw_author(view, commit->author))
6143 return TRUE;
6145 if (opt_rev_graph && commit->graph_size &&
6146 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6147 return TRUE;
6149 if (opt_show_refs && commit->refs) {
6150 size_t i;
6152 for (i = 0; i < commit->refs->size; i++) {
6153 struct ref *ref = commit->refs->refs[i];
6154 enum line_type type;
6156 if (ref->head)
6157 type = LINE_MAIN_HEAD;
6158 else if (ref->ltag)
6159 type = LINE_MAIN_LOCAL_TAG;
6160 else if (ref->tag)
6161 type = LINE_MAIN_TAG;
6162 else if (ref->tracked)
6163 type = LINE_MAIN_TRACKED;
6164 else if (ref->remote)
6165 type = LINE_MAIN_REMOTE;
6166 else
6167 type = LINE_MAIN_REF;
6169 if (draw_text(view, type, "[", TRUE) ||
6170 draw_text(view, type, ref->name, TRUE) ||
6171 draw_text(view, type, "]", TRUE))
6172 return TRUE;
6174 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6175 return TRUE;
6176 }
6177 }
6179 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6180 return TRUE;
6181 }
6183 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6184 static bool
6185 main_read(struct view *view, char *line)
6186 {
6187 static struct rev_graph *graph = graph_stacks;
6188 enum line_type type;
6189 struct commit *commit;
6191 if (!line) {
6192 int i;
6194 if (!view->lines && !view->parent)
6195 die("No revisions match the given arguments.");
6196 if (view->lines > 0) {
6197 commit = view->line[view->lines - 1].data;
6198 view->line[view->lines - 1].dirty = 1;
6199 if (!commit->author) {
6200 view->lines--;
6201 free(commit);
6202 graph->commit = NULL;
6203 }
6204 }
6205 update_rev_graph(view, graph);
6207 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6208 clear_rev_graph(&graph_stacks[i]);
6209 return TRUE;
6210 }
6212 type = get_line_type(line);
6213 if (type == LINE_COMMIT) {
6214 commit = calloc(1, sizeof(struct commit));
6215 if (!commit)
6216 return FALSE;
6218 line += STRING_SIZE("commit ");
6219 if (*line == '-') {
6220 graph->boundary = 1;
6221 line++;
6222 }
6224 string_copy_rev(commit->id, line);
6225 commit->refs = get_ref_list(commit->id);
6226 graph->commit = commit;
6227 add_line_data(view, commit, LINE_MAIN_COMMIT);
6229 while ((line = strchr(line, ' '))) {
6230 line++;
6231 push_rev_graph(graph->parents, line);
6232 commit->has_parents = TRUE;
6233 }
6234 return TRUE;
6235 }
6237 if (!view->lines)
6238 return TRUE;
6239 commit = view->line[view->lines - 1].data;
6241 switch (type) {
6242 case LINE_PARENT:
6243 if (commit->has_parents)
6244 break;
6245 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6246 break;
6248 case LINE_AUTHOR:
6249 parse_author_line(line + STRING_SIZE("author "),
6250 &commit->author, &commit->time);
6251 update_rev_graph(view, graph);
6252 graph = graph->next;
6253 break;
6255 default:
6256 /* Fill in the commit title if it has not already been set. */
6257 if (commit->title[0])
6258 break;
6260 /* Require titles to start with a non-space character at the
6261 * offset used by git log. */
6262 if (strncmp(line, " ", 4))
6263 break;
6264 line += 4;
6265 /* Well, if the title starts with a whitespace character,
6266 * try to be forgiving. Otherwise we end up with no title. */
6267 while (isspace(*line))
6268 line++;
6269 if (*line == '\0')
6270 break;
6271 /* FIXME: More graceful handling of titles; append "..." to
6272 * shortened titles, etc. */
6274 string_expand(commit->title, sizeof(commit->title), line, 1);
6275 view->line[view->lines - 1].dirty = 1;
6276 }
6278 return TRUE;
6279 }
6281 static enum request
6282 main_request(struct view *view, enum request request, struct line *line)
6283 {
6284 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6286 switch (request) {
6287 case REQ_ENTER:
6288 open_view(view, REQ_VIEW_DIFF, flags);
6289 break;
6290 case REQ_REFRESH:
6291 load_refs();
6292 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6293 break;
6294 default:
6295 return request;
6296 }
6298 return REQ_NONE;
6299 }
6301 static bool
6302 grep_refs(struct ref_list *list, regex_t *regex)
6303 {
6304 regmatch_t pmatch;
6305 size_t i;
6307 if (!opt_show_refs || !list)
6308 return FALSE;
6310 for (i = 0; i < list->size; i++) {
6311 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6312 return TRUE;
6313 }
6315 return FALSE;
6316 }
6318 static bool
6319 main_grep(struct view *view, struct line *line)
6320 {
6321 struct commit *commit = line->data;
6322 const char *text[] = {
6323 commit->title,
6324 opt_author ? commit->author : "",
6325 opt_date ? mkdate(&commit->time) : "",
6326 NULL
6327 };
6329 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6330 }
6332 static void
6333 main_select(struct view *view, struct line *line)
6334 {
6335 struct commit *commit = line->data;
6337 string_copy_rev(view->ref, commit->id);
6338 string_copy_rev(ref_commit, view->ref);
6339 }
6341 static struct view_ops main_ops = {
6342 "commit",
6343 main_argv,
6344 NULL,
6345 main_read,
6346 main_draw,
6347 main_request,
6348 main_grep,
6349 main_select,
6350 };
6353 /*
6354 * Unicode / UTF-8 handling
6355 *
6356 * NOTE: Much of the following code for dealing with Unicode is derived from
6357 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6358 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6359 */
6361 static inline int
6362 unicode_width(unsigned long c)
6363 {
6364 if (c >= 0x1100 &&
6365 (c <= 0x115f /* Hangul Jamo */
6366 || c == 0x2329
6367 || c == 0x232a
6368 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6369 /* CJK ... Yi */
6370 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6371 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6372 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6373 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6374 || (c >= 0xffe0 && c <= 0xffe6)
6375 || (c >= 0x20000 && c <= 0x2fffd)
6376 || (c >= 0x30000 && c <= 0x3fffd)))
6377 return 2;
6379 if (c == '\t')
6380 return opt_tab_size;
6382 return 1;
6383 }
6385 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6386 * Illegal bytes are set one. */
6387 static const unsigned char utf8_bytes[256] = {
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 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,
6394 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,
6395 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,
6396 };
6398 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6399 static inline unsigned long
6400 utf8_to_unicode(const char *string, size_t length)
6401 {
6402 unsigned long unicode;
6404 switch (length) {
6405 case 1:
6406 unicode = string[0];
6407 break;
6408 case 2:
6409 unicode = (string[0] & 0x1f) << 6;
6410 unicode += (string[1] & 0x3f);
6411 break;
6412 case 3:
6413 unicode = (string[0] & 0x0f) << 12;
6414 unicode += ((string[1] & 0x3f) << 6);
6415 unicode += (string[2] & 0x3f);
6416 break;
6417 case 4:
6418 unicode = (string[0] & 0x0f) << 18;
6419 unicode += ((string[1] & 0x3f) << 12);
6420 unicode += ((string[2] & 0x3f) << 6);
6421 unicode += (string[3] & 0x3f);
6422 break;
6423 case 5:
6424 unicode = (string[0] & 0x0f) << 24;
6425 unicode += ((string[1] & 0x3f) << 18);
6426 unicode += ((string[2] & 0x3f) << 12);
6427 unicode += ((string[3] & 0x3f) << 6);
6428 unicode += (string[4] & 0x3f);
6429 break;
6430 case 6:
6431 unicode = (string[0] & 0x01) << 30;
6432 unicode += ((string[1] & 0x3f) << 24);
6433 unicode += ((string[2] & 0x3f) << 18);
6434 unicode += ((string[3] & 0x3f) << 12);
6435 unicode += ((string[4] & 0x3f) << 6);
6436 unicode += (string[5] & 0x3f);
6437 break;
6438 default:
6439 die("Invalid Unicode length");
6440 }
6442 /* Invalid characters could return the special 0xfffd value but NUL
6443 * should be just as good. */
6444 return unicode > 0xffff ? 0 : unicode;
6445 }
6447 /* Calculates how much of string can be shown within the given maximum width
6448 * and sets trimmed parameter to non-zero value if all of string could not be
6449 * shown. If the reserve flag is TRUE, it will reserve at least one
6450 * trailing character, which can be useful when drawing a delimiter.
6451 *
6452 * Returns the number of bytes to output from string to satisfy max_width. */
6453 static size_t
6454 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6455 {
6456 const char *string = *start;
6457 const char *end = strchr(string, '\0');
6458 unsigned char last_bytes = 0;
6459 size_t last_ucwidth = 0;
6461 *width = 0;
6462 *trimmed = 0;
6464 while (string < end) {
6465 int c = *(unsigned char *) string;
6466 unsigned char bytes = utf8_bytes[c];
6467 size_t ucwidth;
6468 unsigned long unicode;
6470 if (string + bytes > end)
6471 break;
6473 /* Change representation to figure out whether
6474 * it is a single- or double-width character. */
6476 unicode = utf8_to_unicode(string, bytes);
6477 /* FIXME: Graceful handling of invalid Unicode character. */
6478 if (!unicode)
6479 break;
6481 ucwidth = unicode_width(unicode);
6482 if (skip > 0) {
6483 skip -= ucwidth <= skip ? ucwidth : skip;
6484 *start += bytes;
6485 }
6486 *width += ucwidth;
6487 if (*width > max_width) {
6488 *trimmed = 1;
6489 *width -= ucwidth;
6490 if (reserve && *width == max_width) {
6491 string -= last_bytes;
6492 *width -= last_ucwidth;
6493 }
6494 break;
6495 }
6497 string += bytes;
6498 last_bytes = ucwidth ? bytes : 0;
6499 last_ucwidth = ucwidth;
6500 }
6502 return string - *start;
6503 }
6506 /*
6507 * Status management
6508 */
6510 /* Whether or not the curses interface has been initialized. */
6511 static bool cursed = FALSE;
6513 /* Terminal hacks and workarounds. */
6514 static bool use_scroll_redrawwin;
6515 static bool use_scroll_status_wclear;
6517 /* The status window is used for polling keystrokes. */
6518 static WINDOW *status_win;
6520 /* Reading from the prompt? */
6521 static bool input_mode = FALSE;
6523 static bool status_empty = FALSE;
6525 /* Update status and title window. */
6526 static void
6527 report(const char *msg, ...)
6528 {
6529 struct view *view = display[current_view];
6531 if (input_mode)
6532 return;
6534 if (!view) {
6535 char buf[SIZEOF_STR];
6536 va_list args;
6538 va_start(args, msg);
6539 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6540 buf[sizeof(buf) - 1] = 0;
6541 buf[sizeof(buf) - 2] = '.';
6542 buf[sizeof(buf) - 3] = '.';
6543 buf[sizeof(buf) - 4] = '.';
6544 }
6545 va_end(args);
6546 die("%s", buf);
6547 }
6549 if (!status_empty || *msg) {
6550 va_list args;
6552 va_start(args, msg);
6554 wmove(status_win, 0, 0);
6555 if (view->has_scrolled && use_scroll_status_wclear)
6556 wclear(status_win);
6557 if (*msg) {
6558 vwprintw(status_win, msg, args);
6559 status_empty = FALSE;
6560 } else {
6561 status_empty = TRUE;
6562 }
6563 wclrtoeol(status_win);
6564 wnoutrefresh(status_win);
6566 va_end(args);
6567 }
6569 update_view_title(view);
6570 }
6572 /* Controls when nodelay should be in effect when polling user input. */
6573 static void
6574 set_nonblocking_input(bool loading)
6575 {
6576 static unsigned int loading_views;
6578 if ((loading == FALSE && loading_views-- == 1) ||
6579 (loading == TRUE && loading_views++ == 0))
6580 nodelay(status_win, loading);
6581 }
6583 static void
6584 init_display(void)
6585 {
6586 const char *term;
6587 int x, y;
6589 /* Initialize the curses library */
6590 if (isatty(STDIN_FILENO)) {
6591 cursed = !!initscr();
6592 opt_tty = stdin;
6593 } else {
6594 /* Leave stdin and stdout alone when acting as a pager. */
6595 opt_tty = fopen("/dev/tty", "r+");
6596 if (!opt_tty)
6597 die("Failed to open /dev/tty");
6598 cursed = !!newterm(NULL, opt_tty, opt_tty);
6599 }
6601 if (!cursed)
6602 die("Failed to initialize curses");
6604 nonl(); /* Disable conversion and detect newlines from input. */
6605 cbreak(); /* Take input chars one at a time, no wait for \n */
6606 noecho(); /* Don't echo input */
6607 leaveok(stdscr, FALSE);
6609 if (has_colors())
6610 init_colors();
6612 getmaxyx(stdscr, y, x);
6613 status_win = newwin(1, 0, y - 1, 0);
6614 if (!status_win)
6615 die("Failed to create status window");
6617 /* Enable keyboard mapping */
6618 keypad(status_win, TRUE);
6619 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6621 TABSIZE = opt_tab_size;
6622 if (opt_line_graphics) {
6623 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6624 }
6626 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6627 if (term && !strcmp(term, "gnome-terminal")) {
6628 /* In the gnome-terminal-emulator, the message from
6629 * scrolling up one line when impossible followed by
6630 * scrolling down one line causes corruption of the
6631 * status line. This is fixed by calling wclear. */
6632 use_scroll_status_wclear = TRUE;
6633 use_scroll_redrawwin = FALSE;
6635 } else if (term && !strcmp(term, "xrvt-xpm")) {
6636 /* No problems with full optimizations in xrvt-(unicode)
6637 * and aterm. */
6638 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6640 } else {
6641 /* When scrolling in (u)xterm the last line in the
6642 * scrolling direction will update slowly. */
6643 use_scroll_redrawwin = TRUE;
6644 use_scroll_status_wclear = FALSE;
6645 }
6646 }
6648 static int
6649 get_input(int prompt_position)
6650 {
6651 struct view *view;
6652 int i, key, cursor_y, cursor_x;
6654 if (prompt_position)
6655 input_mode = TRUE;
6657 while (TRUE) {
6658 foreach_view (view, i) {
6659 update_view(view);
6660 if (view_is_displayed(view) && view->has_scrolled &&
6661 use_scroll_redrawwin)
6662 redrawwin(view->win);
6663 view->has_scrolled = FALSE;
6664 }
6666 /* Update the cursor position. */
6667 if (prompt_position) {
6668 getbegyx(status_win, cursor_y, cursor_x);
6669 cursor_x = prompt_position;
6670 } else {
6671 view = display[current_view];
6672 getbegyx(view->win, cursor_y, cursor_x);
6673 cursor_x = view->width - 1;
6674 cursor_y += view->lineno - view->offset;
6675 }
6676 setsyx(cursor_y, cursor_x);
6678 /* Refresh, accept single keystroke of input */
6679 doupdate();
6680 key = wgetch(status_win);
6682 /* wgetch() with nodelay() enabled returns ERR when
6683 * there's no input. */
6684 if (key == ERR) {
6686 } else if (key == KEY_RESIZE) {
6687 int height, width;
6689 getmaxyx(stdscr, height, width);
6691 wresize(status_win, 1, width);
6692 mvwin(status_win, height - 1, 0);
6693 wnoutrefresh(status_win);
6694 resize_display();
6695 redraw_display(TRUE);
6697 } else {
6698 input_mode = FALSE;
6699 return key;
6700 }
6701 }
6702 }
6704 static char *
6705 prompt_input(const char *prompt, input_handler handler, void *data)
6706 {
6707 enum input_status status = INPUT_OK;
6708 static char buf[SIZEOF_STR];
6709 size_t pos = 0;
6711 buf[pos] = 0;
6713 while (status == INPUT_OK || status == INPUT_SKIP) {
6714 int key;
6716 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6717 wclrtoeol(status_win);
6719 key = get_input(pos + 1);
6720 switch (key) {
6721 case KEY_RETURN:
6722 case KEY_ENTER:
6723 case '\n':
6724 status = pos ? INPUT_STOP : INPUT_CANCEL;
6725 break;
6727 case KEY_BACKSPACE:
6728 if (pos > 0)
6729 buf[--pos] = 0;
6730 else
6731 status = INPUT_CANCEL;
6732 break;
6734 case KEY_ESC:
6735 status = INPUT_CANCEL;
6736 break;
6738 default:
6739 if (pos >= sizeof(buf)) {
6740 report("Input string too long");
6741 return NULL;
6742 }
6744 status = handler(data, buf, key);
6745 if (status == INPUT_OK)
6746 buf[pos++] = (char) key;
6747 }
6748 }
6750 /* Clear the status window */
6751 status_empty = FALSE;
6752 report("");
6754 if (status == INPUT_CANCEL)
6755 return NULL;
6757 buf[pos++] = 0;
6759 return buf;
6760 }
6762 static enum input_status
6763 prompt_yesno_handler(void *data, char *buf, int c)
6764 {
6765 if (c == 'y' || c == 'Y')
6766 return INPUT_STOP;
6767 if (c == 'n' || c == 'N')
6768 return INPUT_CANCEL;
6769 return INPUT_SKIP;
6770 }
6772 static bool
6773 prompt_yesno(const char *prompt)
6774 {
6775 char prompt2[SIZEOF_STR];
6777 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6778 return FALSE;
6780 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6781 }
6783 static enum input_status
6784 read_prompt_handler(void *data, char *buf, int c)
6785 {
6786 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6787 }
6789 static char *
6790 read_prompt(const char *prompt)
6791 {
6792 return prompt_input(prompt, read_prompt_handler, NULL);
6793 }
6795 /*
6796 * Repository properties
6797 */
6799 static struct ref **refs = NULL;
6800 static size_t refs_size = 0;
6802 static struct ref_list **ref_lists = NULL;
6803 static size_t ref_lists_size = 0;
6805 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
6806 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6807 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
6809 static int
6810 compare_refs(const void *ref1_, const void *ref2_)
6811 {
6812 const struct ref *ref1 = *(const struct ref **)ref1_;
6813 const struct ref *ref2 = *(const struct ref **)ref2_;
6815 if (ref1->tag != ref2->tag)
6816 return ref2->tag - ref1->tag;
6817 if (ref1->ltag != ref2->ltag)
6818 return ref2->ltag - ref2->ltag;
6819 if (ref1->head != ref2->head)
6820 return ref2->head - ref1->head;
6821 if (ref1->tracked != ref2->tracked)
6822 return ref2->tracked - ref1->tracked;
6823 if (ref1->remote != ref2->remote)
6824 return ref2->remote - ref1->remote;
6825 return strcmp(ref1->name, ref2->name);
6826 }
6828 static void
6829 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6830 {
6831 size_t i;
6833 for (i = 0; i < refs_size; i++)
6834 if (!visitor(data, refs[i]))
6835 break;
6836 }
6838 static struct ref_list *
6839 get_ref_list(const char *id)
6840 {
6841 struct ref_list *list;
6842 size_t i;
6844 for (i = 0; i < ref_lists_size; i++)
6845 if (!strcmp(id, ref_lists[i]->id))
6846 return ref_lists[i];
6848 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
6849 return NULL;
6850 list = calloc(1, sizeof(*list));
6851 if (!list)
6852 return NULL;
6854 for (i = 0; i < refs_size; i++) {
6855 if (!strcmp(id, refs[i]->id) &&
6856 realloc_refs_list(&list->refs, list->size, 1))
6857 list->refs[list->size++] = refs[i];
6858 }
6860 if (!list->refs) {
6861 free(list);
6862 return NULL;
6863 }
6865 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
6866 ref_lists[ref_lists_size++] = list;
6867 return list;
6868 }
6870 static int
6871 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6872 {
6873 struct ref *ref = NULL;
6874 bool tag = FALSE;
6875 bool ltag = FALSE;
6876 bool remote = FALSE;
6877 bool tracked = FALSE;
6878 bool head = FALSE;
6879 int from = 0, to = refs_size - 1;
6881 if (!prefixcmp(name, "refs/tags/")) {
6882 if (!suffixcmp(name, namelen, "^{}")) {
6883 namelen -= 3;
6884 name[namelen] = 0;
6885 } else {
6886 ltag = TRUE;
6887 }
6889 tag = TRUE;
6890 namelen -= STRING_SIZE("refs/tags/");
6891 name += STRING_SIZE("refs/tags/");
6893 } else if (!prefixcmp(name, "refs/remotes/")) {
6894 remote = TRUE;
6895 namelen -= STRING_SIZE("refs/remotes/");
6896 name += STRING_SIZE("refs/remotes/");
6897 tracked = !strcmp(opt_remote, name);
6899 } else if (!prefixcmp(name, "refs/heads/")) {
6900 namelen -= STRING_SIZE("refs/heads/");
6901 name += STRING_SIZE("refs/heads/");
6902 head = !strncmp(opt_head, name, namelen);
6904 } else if (!strcmp(name, "HEAD")) {
6905 string_ncopy(opt_head_rev, id, idlen);
6906 return OK;
6907 }
6909 /* If we are reloading or it's an annotated tag, replace the
6910 * previous SHA1 with the resolved commit id; relies on the fact
6911 * git-ls-remote lists the commit id of an annotated tag right
6912 * before the commit id it points to. */
6913 while (from <= to) {
6914 size_t pos = (to + from) / 2;
6915 int cmp = strcmp(name, refs[pos]->name);
6917 if (!cmp) {
6918 ref = refs[pos];
6919 break;
6920 }
6922 if (cmp < 0)
6923 to = pos - 1;
6924 else
6925 from = pos + 1;
6926 }
6928 if (!ref) {
6929 if (!realloc_refs(&refs, refs_size, 1))
6930 return ERR;
6931 ref = calloc(1, sizeof(*ref) + namelen);
6932 if (!ref)
6933 return ERR;
6934 memmove(refs + from + 1, refs + from,
6935 (refs_size - from) * sizeof(*refs));
6936 refs[from] = ref;
6937 strncpy(ref->name, name, namelen);
6938 refs_size++;
6939 }
6941 ref->head = head;
6942 ref->tag = tag;
6943 ref->ltag = ltag;
6944 ref->remote = remote;
6945 ref->tracked = tracked;
6946 string_copy_rev(ref->id, id);
6948 return OK;
6949 }
6951 static int
6952 load_refs(void)
6953 {
6954 const char *head_argv[] = {
6955 "git", "symbolic-ref", "HEAD", NULL
6956 };
6957 static const char *ls_remote_argv[SIZEOF_ARG] = {
6958 "git", "ls-remote", opt_git_dir, NULL
6959 };
6960 static bool init = FALSE;
6961 size_t i;
6963 if (!init) {
6964 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6965 init = TRUE;
6966 }
6968 if (!*opt_git_dir)
6969 return OK;
6971 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6972 !prefixcmp(opt_head, "refs/heads/")) {
6973 char *offset = opt_head + STRING_SIZE("refs/heads/");
6975 memmove(opt_head, offset, strlen(offset) + 1);
6976 }
6978 for (i = 0; i < refs_size; i++)
6979 refs[i]->id[0] = 0;
6981 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
6982 return ERR;
6984 /* Update the ref lists to reflect changes. */
6985 for (i = 0; i < ref_lists_size; i++) {
6986 struct ref_list *list = ref_lists[i];
6987 size_t old, new;
6989 for (old = new = 0; old < list->size; old++)
6990 if (!strcmp(list->id, list->refs[old]->id))
6991 list->refs[new++] = list->refs[old];
6992 list->size = new;
6993 }
6995 return OK;
6996 }
6998 static void
6999 set_remote_branch(const char *name, const char *value, size_t valuelen)
7000 {
7001 if (!strcmp(name, ".remote")) {
7002 string_ncopy(opt_remote, value, valuelen);
7004 } else if (*opt_remote && !strcmp(name, ".merge")) {
7005 size_t from = strlen(opt_remote);
7007 if (!prefixcmp(value, "refs/heads/"))
7008 value += STRING_SIZE("refs/heads/");
7010 if (!string_format_from(opt_remote, &from, "/%s", value))
7011 opt_remote[0] = 0;
7012 }
7013 }
7015 static void
7016 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7017 {
7018 const char *argv[SIZEOF_ARG] = { name, "=" };
7019 int argc = 1 + (cmd == option_set_command);
7020 int error = ERR;
7022 if (!argv_from_string(argv, &argc, value))
7023 config_msg = "Too many option arguments";
7024 else
7025 error = cmd(argc, argv);
7027 if (error == ERR)
7028 warn("Option 'tig.%s': %s", name, config_msg);
7029 }
7031 static bool
7032 set_environment_variable(const char *name, const char *value)
7033 {
7034 size_t len = strlen(name) + 1 + strlen(value) + 1;
7035 char *env = malloc(len);
7037 if (env &&
7038 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7039 putenv(env) == 0)
7040 return TRUE;
7041 free(env);
7042 return FALSE;
7043 }
7045 static void
7046 set_work_tree(const char *value)
7047 {
7048 char cwd[SIZEOF_STR];
7050 if (!getcwd(cwd, sizeof(cwd)))
7051 die("Failed to get cwd path: %s", strerror(errno));
7052 if (chdir(opt_git_dir) < 0)
7053 die("Failed to chdir(%s): %s", strerror(errno));
7054 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7055 die("Failed to get git path: %s", strerror(errno));
7056 if (chdir(cwd) < 0)
7057 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7058 if (chdir(value) < 0)
7059 die("Failed to chdir(%s): %s", value, strerror(errno));
7060 if (!getcwd(cwd, sizeof(cwd)))
7061 die("Failed to get cwd path: %s", strerror(errno));
7062 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7063 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7064 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7065 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7066 opt_is_inside_work_tree = TRUE;
7067 }
7069 static int
7070 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7071 {
7072 if (!strcmp(name, "i18n.commitencoding"))
7073 string_ncopy(opt_encoding, value, valuelen);
7075 else if (!strcmp(name, "core.editor"))
7076 string_ncopy(opt_editor, value, valuelen);
7078 else if (!strcmp(name, "core.worktree"))
7079 set_work_tree(value);
7081 else if (!prefixcmp(name, "tig.color."))
7082 set_repo_config_option(name + 10, value, option_color_command);
7084 else if (!prefixcmp(name, "tig.bind."))
7085 set_repo_config_option(name + 9, value, option_bind_command);
7087 else if (!prefixcmp(name, "tig."))
7088 set_repo_config_option(name + 4, value, option_set_command);
7090 else if (*opt_head && !prefixcmp(name, "branch.") &&
7091 !strncmp(name + 7, opt_head, strlen(opt_head)))
7092 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7094 return OK;
7095 }
7097 static int
7098 load_git_config(void)
7099 {
7100 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7102 return run_io_load(config_list_argv, "=", read_repo_config_option);
7103 }
7105 static int
7106 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7107 {
7108 if (!opt_git_dir[0]) {
7109 string_ncopy(opt_git_dir, name, namelen);
7111 } else if (opt_is_inside_work_tree == -1) {
7112 /* This can be 3 different values depending on the
7113 * version of git being used. If git-rev-parse does not
7114 * understand --is-inside-work-tree it will simply echo
7115 * the option else either "true" or "false" is printed.
7116 * Default to true for the unknown case. */
7117 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7119 } else if (*name == '.') {
7120 string_ncopy(opt_cdup, name, namelen);
7122 } else {
7123 string_ncopy(opt_prefix, name, namelen);
7124 }
7126 return OK;
7127 }
7129 static int
7130 load_repo_info(void)
7131 {
7132 const char *rev_parse_argv[] = {
7133 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7134 "--show-cdup", "--show-prefix", NULL
7135 };
7137 return run_io_load(rev_parse_argv, "=", read_repo_info);
7138 }
7141 /*
7142 * Main
7143 */
7145 static const char usage[] =
7146 "tig " TIG_VERSION " (" __DATE__ ")\n"
7147 "\n"
7148 "Usage: tig [options] [revs] [--] [paths]\n"
7149 " or: tig show [options] [revs] [--] [paths]\n"
7150 " or: tig blame [rev] path\n"
7151 " or: tig status\n"
7152 " or: tig < [git command output]\n"
7153 "\n"
7154 "Options:\n"
7155 " -v, --version Show version and exit\n"
7156 " -h, --help Show help message and exit";
7158 static void __NORETURN
7159 quit(int sig)
7160 {
7161 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7162 if (cursed)
7163 endwin();
7164 exit(0);
7165 }
7167 static void __NORETURN
7168 die(const char *err, ...)
7169 {
7170 va_list args;
7172 endwin();
7174 va_start(args, err);
7175 fputs("tig: ", stderr);
7176 vfprintf(stderr, err, args);
7177 fputs("\n", stderr);
7178 va_end(args);
7180 exit(1);
7181 }
7183 static void
7184 warn(const char *msg, ...)
7185 {
7186 va_list args;
7188 va_start(args, msg);
7189 fputs("tig warning: ", stderr);
7190 vfprintf(stderr, msg, args);
7191 fputs("\n", stderr);
7192 va_end(args);
7193 }
7195 static enum request
7196 parse_options(int argc, const char *argv[])
7197 {
7198 enum request request = REQ_VIEW_MAIN;
7199 const char *subcommand;
7200 bool seen_dashdash = FALSE;
7201 /* XXX: This is vulnerable to the user overriding options
7202 * required for the main view parser. */
7203 const char *custom_argv[SIZEOF_ARG] = {
7204 "git", "log", "--no-color", "--pretty=raw", "--parents",
7205 "--topo-order", NULL
7206 };
7207 int i, j = 6;
7209 if (!isatty(STDIN_FILENO)) {
7210 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7211 return REQ_VIEW_PAGER;
7212 }
7214 if (argc <= 1)
7215 return REQ_NONE;
7217 subcommand = argv[1];
7218 if (!strcmp(subcommand, "status")) {
7219 if (argc > 2)
7220 warn("ignoring arguments after `%s'", subcommand);
7221 return REQ_VIEW_STATUS;
7223 } else if (!strcmp(subcommand, "blame")) {
7224 if (argc <= 2 || argc > 4)
7225 die("invalid number of options to blame\n\n%s", usage);
7227 i = 2;
7228 if (argc == 4) {
7229 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7230 i++;
7231 }
7233 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7234 return REQ_VIEW_BLAME;
7236 } else if (!strcmp(subcommand, "show")) {
7237 request = REQ_VIEW_DIFF;
7239 } else {
7240 subcommand = NULL;
7241 }
7243 if (subcommand) {
7244 custom_argv[1] = subcommand;
7245 j = 2;
7246 }
7248 for (i = 1 + !!subcommand; i < argc; i++) {
7249 const char *opt = argv[i];
7251 if (seen_dashdash || !strcmp(opt, "--")) {
7252 seen_dashdash = TRUE;
7254 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7255 printf("tig version %s\n", TIG_VERSION);
7256 quit(0);
7258 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7259 printf("%s\n", usage);
7260 quit(0);
7261 }
7263 custom_argv[j++] = opt;
7264 if (j >= ARRAY_SIZE(custom_argv))
7265 die("command too long");
7266 }
7268 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7269 die("Failed to format arguments");
7271 return request;
7272 }
7274 int
7275 main(int argc, const char *argv[])
7276 {
7277 enum request request = parse_options(argc, argv);
7278 struct view *view;
7279 size_t i;
7281 signal(SIGINT, quit);
7282 signal(SIGPIPE, SIG_IGN);
7284 if (setlocale(LC_ALL, "")) {
7285 char *codeset = nl_langinfo(CODESET);
7287 string_ncopy(opt_codeset, codeset, strlen(codeset));
7288 }
7290 if (load_repo_info() == ERR)
7291 die("Failed to load repo info.");
7293 if (load_options() == ERR)
7294 die("Failed to load user config.");
7296 if (load_git_config() == ERR)
7297 die("Failed to load repo config.");
7299 /* Require a git repository unless when running in pager mode. */
7300 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7301 die("Not a git repository");
7303 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7304 opt_utf8 = FALSE;
7306 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7307 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7308 if (opt_iconv == ICONV_NONE)
7309 die("Failed to initialize character set conversion");
7310 }
7312 if (load_refs() == ERR)
7313 die("Failed to load refs.");
7315 foreach_view (view, i)
7316 argv_from_env(view->ops->argv, view->cmd_env);
7318 init_display();
7320 if (request != REQ_NONE)
7321 open_view(NULL, request, OPEN_PREPARED);
7322 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7324 while (view_driver(display[current_view], request)) {
7325 int key = get_input(0);
7327 view = display[current_view];
7328 request = get_keybinding(view->keymap, key);
7330 /* Some low-level request handling. This keeps access to
7331 * status_win restricted. */
7332 switch (request) {
7333 case REQ_PROMPT:
7334 {
7335 char *cmd = read_prompt(":");
7337 if (cmd && isdigit(*cmd)) {
7338 int lineno = view->lineno + 1;
7340 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7341 select_view_line(view, lineno - 1);
7342 report("");
7343 } else {
7344 report("Unable to parse '%s' as a line number", cmd);
7345 }
7347 } else if (cmd) {
7348 struct view *next = VIEW(REQ_VIEW_PAGER);
7349 const char *argv[SIZEOF_ARG] = { "git" };
7350 int argc = 1;
7352 /* When running random commands, initially show the
7353 * command in the title. However, it maybe later be
7354 * overwritten if a commit line is selected. */
7355 string_ncopy(next->ref, cmd, strlen(cmd));
7357 if (!argv_from_string(argv, &argc, cmd)) {
7358 report("Too many arguments");
7359 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7360 report("Failed to format command");
7361 } else {
7362 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7363 }
7364 }
7366 request = REQ_NONE;
7367 break;
7368 }
7369 case REQ_SEARCH:
7370 case REQ_SEARCH_BACK:
7371 {
7372 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7373 char *search = read_prompt(prompt);
7375 if (search)
7376 string_ncopy(opt_search, search, strlen(search));
7377 else if (*opt_search)
7378 request = request == REQ_SEARCH ?
7379 REQ_FIND_NEXT :
7380 REQ_FIND_PREV;
7381 else
7382 request = REQ_NONE;
7383 break;
7384 }
7385 default:
7386 break;
7387 }
7388 }
7390 quit(0);
7392 return 0;
7393 }