f72932144b4d893db73720d3946da34d15b97ee7
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, int *width, size_t max_width, int *trimmed, bool reserve);
72 static bool prompt_yesno(const char *prompt);
73 static int load_refs(void);
75 #define ABS(x) ((x) >= 0 ? (x) : -(x))
76 #define MIN(x, y) ((x) < (y) ? (x) : (y))
78 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x) (sizeof(x) - 1)
81 #define SIZEOF_STR 1024 /* Default string size. */
82 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG 32 /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT 'I'
89 #define REVGRAPH_MERGE 'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND '^'
94 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT (-1)
99 #define ICONV_NONE ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT "%Y-%m-%d %H:%M"
106 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
108 #define AUTHOR_COLS 20
109 #define ID_COLS 8
111 /* The default interval between line numbers. */
112 #define NUMBER_INTERVAL 5
114 #define TAB_SIZE 8
116 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
118 #define NULL_ID "0000000000000000000000000000000000000000"
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 *name; /* Ref name; tag or head names are shortened. */
132 char id[SIZEOF_REV]; /* Commit SHA1 ID */
133 unsigned int head:1; /* Is it the current HEAD? */
134 unsigned int tag:1; /* Is it a tag? */
135 unsigned int ltag:1; /* If so, is the tag local? */
136 unsigned int remote:1; /* Is it a remote ref? */
137 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138 unsigned int next:1; /* For ref lists: are there more refs? */
139 };
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 struct int_map {
152 const char *name;
153 int namelen;
154 int value;
155 };
157 static int
158 set_from_int_map(struct int_map *map, size_t map_size,
159 int *value, const char *name, int namelen)
160 {
162 int i;
164 for (i = 0; i < map_size; i++)
165 if (namelen == map[i].namelen &&
166 !strncasecmp(name, map[i].name, namelen)) {
167 *value = map[i].value;
168 return OK;
169 }
171 return ERR;
172 }
175 /*
176 * String helpers
177 */
179 static inline void
180 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
181 {
182 if (srclen > dstlen - 1)
183 srclen = dstlen - 1;
185 strncpy(dst, src, srclen);
186 dst[srclen] = 0;
187 }
189 /* Shorthands for safely copying into a fixed buffer. */
191 #define string_copy(dst, src) \
192 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
194 #define string_ncopy(dst, src, srclen) \
195 string_ncopy_do(dst, sizeof(dst), src, srclen)
197 #define string_copy_rev(dst, src) \
198 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
200 #define string_add(dst, from, src) \
201 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
203 static char *
204 chomp_string(char *name)
205 {
206 int namelen;
208 while (isspace(*name))
209 name++;
211 namelen = strlen(name) - 1;
212 while (namelen > 0 && isspace(name[namelen]))
213 name[namelen--] = 0;
215 return name;
216 }
218 static bool
219 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
220 {
221 va_list args;
222 size_t pos = bufpos ? *bufpos : 0;
224 va_start(args, fmt);
225 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
226 va_end(args);
228 if (bufpos)
229 *bufpos = pos;
231 return pos >= bufsize ? FALSE : TRUE;
232 }
234 #define string_format(buf, fmt, args...) \
235 string_nformat(buf, sizeof(buf), NULL, fmt, args)
237 #define string_format_from(buf, from, fmt, args...) \
238 string_nformat(buf, sizeof(buf), from, fmt, args)
240 static int
241 string_enum_compare(const char *str1, const char *str2, int len)
242 {
243 size_t i;
245 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
247 /* Diff-Header == DIFF_HEADER */
248 for (i = 0; i < len; i++) {
249 if (toupper(str1[i]) == toupper(str2[i]))
250 continue;
252 if (string_enum_sep(str1[i]) &&
253 string_enum_sep(str2[i]))
254 continue;
256 return str1[i] - str2[i];
257 }
259 return 0;
260 }
262 #define prefixcmp(str1, str2) \
263 strncmp(str1, str2, STRING_SIZE(str2))
265 static inline int
266 suffixcmp(const char *str, int slen, const char *suffix)
267 {
268 size_t len = slen >= 0 ? slen : strlen(str);
269 size_t suffixlen = strlen(suffix);
271 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
272 }
275 static bool
276 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
277 {
278 int valuelen;
280 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
281 bool advance = cmd[valuelen] != 0;
283 cmd[valuelen] = 0;
284 argv[(*argc)++] = chomp_string(cmd);
285 cmd += valuelen + advance;
286 }
288 if (*argc < SIZEOF_ARG)
289 argv[*argc] = NULL;
290 return *argc < SIZEOF_ARG;
291 }
293 static void
294 argv_from_env(const char **argv, const char *name)
295 {
296 char *env = argv ? getenv(name) : NULL;
297 int argc = 0;
299 if (env && *env)
300 env = strdup(env);
301 if (env && !argv_from_string(argv, &argc, env))
302 die("Too many arguments in the `%s` environment variable", name);
303 }
306 /*
307 * Executing external commands.
308 */
310 enum io_type {
311 IO_FD, /* File descriptor based IO. */
312 IO_BG, /* Execute command in the background. */
313 IO_FG, /* Execute command with same std{in,out,err}. */
314 IO_RD, /* Read only fork+exec IO. */
315 IO_WR, /* Write only fork+exec IO. */
316 IO_AP, /* Append fork+exec output to file. */
317 };
319 struct io {
320 enum io_type type; /* The requested type of pipe. */
321 const char *dir; /* Directory from which to execute. */
322 pid_t pid; /* Pipe for reading or writing. */
323 int pipe; /* Pipe end for reading or writing. */
324 int error; /* Error status. */
325 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
326 char *buf; /* Read buffer. */
327 size_t bufalloc; /* Allocated buffer size. */
328 size_t bufsize; /* Buffer content size. */
329 char *bufpos; /* Current buffer position. */
330 unsigned int eof:1; /* Has end of file been reached. */
331 };
333 static void
334 reset_io(struct io *io)
335 {
336 io->pipe = -1;
337 io->pid = 0;
338 io->buf = io->bufpos = NULL;
339 io->bufalloc = io->bufsize = 0;
340 io->error = 0;
341 io->eof = 0;
342 }
344 static void
345 init_io(struct io *io, const char *dir, enum io_type type)
346 {
347 reset_io(io);
348 io->type = type;
349 io->dir = dir;
350 }
352 static bool
353 init_io_rd(struct io *io, const char *argv[], const char *dir,
354 enum format_flags flags)
355 {
356 init_io(io, dir, IO_RD);
357 return format_argv(io->argv, argv, flags);
358 }
360 static bool
361 io_open(struct io *io, const char *name)
362 {
363 init_io(io, NULL, IO_FD);
364 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
365 return io->pipe != -1;
366 }
368 static bool
369 kill_io(struct io *io)
370 {
371 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
372 }
374 static bool
375 done_io(struct io *io)
376 {
377 pid_t pid = io->pid;
379 if (io->pipe != -1)
380 close(io->pipe);
381 free(io->buf);
382 reset_io(io);
384 while (pid > 0) {
385 int status;
386 pid_t waiting = waitpid(pid, &status, 0);
388 if (waiting < 0) {
389 if (errno == EINTR)
390 continue;
391 report("waitpid failed (%s)", strerror(errno));
392 return FALSE;
393 }
395 return waiting == pid &&
396 !WIFSIGNALED(status) &&
397 WIFEXITED(status) &&
398 !WEXITSTATUS(status);
399 }
401 return TRUE;
402 }
404 static bool
405 start_io(struct io *io)
406 {
407 int pipefds[2] = { -1, -1 };
409 if (io->type == IO_FD)
410 return TRUE;
412 if ((io->type == IO_RD || io->type == IO_WR) &&
413 pipe(pipefds) < 0)
414 return FALSE;
415 else if (io->type == IO_AP)
416 pipefds[1] = io->pipe;
418 if ((io->pid = fork())) {
419 if (pipefds[!(io->type == IO_WR)] != -1)
420 close(pipefds[!(io->type == IO_WR)]);
421 if (io->pid != -1) {
422 io->pipe = pipefds[!!(io->type == IO_WR)];
423 return TRUE;
424 }
426 } else {
427 if (io->type != IO_FG) {
428 int devnull = open("/dev/null", O_RDWR);
429 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
430 int writefd = (io->type == IO_RD || io->type == IO_AP)
431 ? pipefds[1] : devnull;
433 dup2(readfd, STDIN_FILENO);
434 dup2(writefd, STDOUT_FILENO);
435 dup2(devnull, STDERR_FILENO);
437 close(devnull);
438 if (pipefds[0] != -1)
439 close(pipefds[0]);
440 if (pipefds[1] != -1)
441 close(pipefds[1]);
442 }
444 if (io->dir && *io->dir && chdir(io->dir) == -1)
445 die("Failed to change directory: %s", strerror(errno));
447 execvp(io->argv[0], (char *const*) io->argv);
448 die("Failed to execute program: %s", strerror(errno));
449 }
451 if (pipefds[!!(io->type == IO_WR)] != -1)
452 close(pipefds[!!(io->type == IO_WR)]);
453 return FALSE;
454 }
456 static bool
457 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
458 {
459 init_io(io, dir, type);
460 if (!format_argv(io->argv, argv, FORMAT_NONE))
461 return FALSE;
462 return start_io(io);
463 }
465 static int
466 run_io_do(struct io *io)
467 {
468 return start_io(io) && done_io(io);
469 }
471 static int
472 run_io_bg(const char **argv)
473 {
474 struct io io = {};
476 init_io(&io, NULL, IO_BG);
477 if (!format_argv(io.argv, argv, FORMAT_NONE))
478 return FALSE;
479 return run_io_do(&io);
480 }
482 static bool
483 run_io_fg(const char **argv, const char *dir)
484 {
485 struct io io = {};
487 init_io(&io, dir, IO_FG);
488 if (!format_argv(io.argv, argv, FORMAT_NONE))
489 return FALSE;
490 return run_io_do(&io);
491 }
493 static bool
494 run_io_append(const char **argv, enum format_flags flags, int fd)
495 {
496 struct io io = {};
498 init_io(&io, NULL, IO_AP);
499 io.pipe = fd;
500 if (format_argv(io.argv, argv, flags))
501 return run_io_do(&io);
502 close(fd);
503 return FALSE;
504 }
506 static bool
507 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
508 {
509 return init_io_rd(io, argv, NULL, flags) && start_io(io);
510 }
512 static bool
513 io_eof(struct io *io)
514 {
515 return io->eof;
516 }
518 static int
519 io_error(struct io *io)
520 {
521 return io->error;
522 }
524 static bool
525 io_strerror(struct io *io)
526 {
527 return strerror(io->error);
528 }
530 static bool
531 io_can_read(struct io *io)
532 {
533 struct timeval tv = { 0, 500 };
534 fd_set fds;
536 FD_ZERO(&fds);
537 FD_SET(io->pipe, &fds);
539 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
540 }
542 static ssize_t
543 io_read(struct io *io, void *buf, size_t bufsize)
544 {
545 do {
546 ssize_t readsize = read(io->pipe, buf, bufsize);
548 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
549 continue;
550 else if (readsize == -1)
551 io->error = errno;
552 else if (readsize == 0)
553 io->eof = 1;
554 return readsize;
555 } while (1);
556 }
558 static char *
559 io_get(struct io *io, int c, bool can_read)
560 {
561 char *eol;
562 ssize_t readsize;
564 if (!io->buf) {
565 io->buf = io->bufpos = malloc(BUFSIZ);
566 if (!io->buf)
567 return NULL;
568 io->bufalloc = BUFSIZ;
569 io->bufsize = 0;
570 }
572 while (TRUE) {
573 if (io->bufsize > 0) {
574 eol = memchr(io->bufpos, c, io->bufsize);
575 if (eol) {
576 char *line = io->bufpos;
578 *eol = 0;
579 io->bufpos = eol + 1;
580 io->bufsize -= io->bufpos - line;
581 return line;
582 }
583 }
585 if (io_eof(io)) {
586 if (io->bufsize) {
587 io->bufpos[io->bufsize] = 0;
588 io->bufsize = 0;
589 return io->bufpos;
590 }
591 return NULL;
592 }
594 if (!can_read)
595 return NULL;
597 if (io->bufsize > 0 && io->bufpos > io->buf)
598 memmove(io->buf, io->bufpos, io->bufsize);
600 io->bufpos = io->buf;
601 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
602 if (io_error(io))
603 return NULL;
604 io->bufsize += readsize;
605 }
606 }
608 static bool
609 io_write(struct io *io, const void *buf, size_t bufsize)
610 {
611 size_t written = 0;
613 while (!io_error(io) && written < bufsize) {
614 ssize_t size;
616 size = write(io->pipe, buf + written, bufsize - written);
617 if (size < 0 && (errno == EAGAIN || errno == EINTR))
618 continue;
619 else if (size == -1)
620 io->error = errno;
621 else
622 written += size;
623 }
625 return written == bufsize;
626 }
628 static bool
629 run_io_buf(const char **argv, char buf[], size_t bufsize)
630 {
631 struct io io = {};
632 bool error;
634 if (!run_io_rd(&io, argv, FORMAT_NONE))
635 return FALSE;
637 io.buf = io.bufpos = buf;
638 io.bufalloc = bufsize;
639 error = !io_get(&io, '\n', TRUE) && io_error(&io);
640 io.buf = NULL;
642 return done_io(&io) || error;
643 }
645 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
647 /*
648 * User requests
649 */
651 #define REQ_INFO \
652 /* XXX: Keep the view request first and in sync with views[]. */ \
653 REQ_GROUP("View switching") \
654 REQ_(VIEW_MAIN, "Show main view"), \
655 REQ_(VIEW_DIFF, "Show diff view"), \
656 REQ_(VIEW_LOG, "Show log view"), \
657 REQ_(VIEW_TREE, "Show tree view"), \
658 REQ_(VIEW_BLOB, "Show blob view"), \
659 REQ_(VIEW_BLAME, "Show blame view"), \
660 REQ_(VIEW_HELP, "Show help page"), \
661 REQ_(VIEW_PAGER, "Show pager view"), \
662 REQ_(VIEW_STATUS, "Show status view"), \
663 REQ_(VIEW_STAGE, "Show stage view"), \
664 \
665 REQ_GROUP("View manipulation") \
666 REQ_(ENTER, "Enter current line and scroll"), \
667 REQ_(NEXT, "Move to next"), \
668 REQ_(PREVIOUS, "Move to previous"), \
669 REQ_(PARENT, "Move to parent"), \
670 REQ_(VIEW_NEXT, "Move focus to next view"), \
671 REQ_(REFRESH, "Reload and refresh"), \
672 REQ_(MAXIMIZE, "Maximize the current view"), \
673 REQ_(VIEW_CLOSE, "Close the current view"), \
674 REQ_(QUIT, "Close all views and quit"), \
675 \
676 REQ_GROUP("View specific requests") \
677 REQ_(STATUS_UPDATE, "Update file status"), \
678 REQ_(STATUS_REVERT, "Revert file changes"), \
679 REQ_(STATUS_MERGE, "Merge file using external tool"), \
680 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
681 \
682 REQ_GROUP("Cursor navigation") \
683 REQ_(MOVE_UP, "Move cursor one line up"), \
684 REQ_(MOVE_DOWN, "Move cursor one line down"), \
685 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
686 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
687 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
688 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
689 \
690 REQ_GROUP("Scrolling") \
691 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
692 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
693 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
694 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
695 \
696 REQ_GROUP("Searching") \
697 REQ_(SEARCH, "Search the view"), \
698 REQ_(SEARCH_BACK, "Search backwards in the view"), \
699 REQ_(FIND_NEXT, "Find next search match"), \
700 REQ_(FIND_PREV, "Find previous search match"), \
701 \
702 REQ_GROUP("Option manipulation") \
703 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
704 REQ_(TOGGLE_DATE, "Toggle date display"), \
705 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
706 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
707 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
708 \
709 REQ_GROUP("Misc") \
710 REQ_(PROMPT, "Bring up the prompt"), \
711 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
712 REQ_(SHOW_VERSION, "Show version information"), \
713 REQ_(STOP_LOADING, "Stop all loading views"), \
714 REQ_(EDIT, "Open in editor"), \
715 REQ_(NONE, "Do nothing")
718 /* User action requests. */
719 enum request {
720 #define REQ_GROUP(help)
721 #define REQ_(req, help) REQ_##req
723 /* Offset all requests to avoid conflicts with ncurses getch values. */
724 REQ_OFFSET = KEY_MAX + 1,
725 REQ_INFO
727 #undef REQ_GROUP
728 #undef REQ_
729 };
731 struct request_info {
732 enum request request;
733 const char *name;
734 int namelen;
735 const char *help;
736 };
738 static struct request_info req_info[] = {
739 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
740 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
741 REQ_INFO
742 #undef REQ_GROUP
743 #undef REQ_
744 };
746 static enum request
747 get_request(const char *name)
748 {
749 int namelen = strlen(name);
750 int i;
752 for (i = 0; i < ARRAY_SIZE(req_info); i++)
753 if (req_info[i].namelen == namelen &&
754 !string_enum_compare(req_info[i].name, name, namelen))
755 return req_info[i].request;
757 return REQ_NONE;
758 }
761 /*
762 * Options
763 */
765 static const char usage[] =
766 "tig " TIG_VERSION " (" __DATE__ ")\n"
767 "\n"
768 "Usage: tig [options] [revs] [--] [paths]\n"
769 " or: tig show [options] [revs] [--] [paths]\n"
770 " or: tig blame [rev] path\n"
771 " or: tig status\n"
772 " or: tig < [git command output]\n"
773 "\n"
774 "Options:\n"
775 " -v, --version Show version and exit\n"
776 " -h, --help Show help message and exit";
778 /* Option and state variables. */
779 static bool opt_date = TRUE;
780 static bool opt_author = TRUE;
781 static bool opt_line_number = FALSE;
782 static bool opt_line_graphics = TRUE;
783 static bool opt_rev_graph = FALSE;
784 static bool opt_show_refs = TRUE;
785 static int opt_num_interval = NUMBER_INTERVAL;
786 static int opt_tab_size = TAB_SIZE;
787 static int opt_author_cols = AUTHOR_COLS-1;
788 static char opt_path[SIZEOF_STR] = "";
789 static char opt_file[SIZEOF_STR] = "";
790 static char opt_ref[SIZEOF_REF] = "";
791 static char opt_head[SIZEOF_REF] = "";
792 static char opt_head_rev[SIZEOF_REV] = "";
793 static char opt_remote[SIZEOF_REF] = "";
794 static char opt_encoding[20] = "UTF-8";
795 static bool opt_utf8 = TRUE;
796 static char opt_codeset[20] = "UTF-8";
797 static iconv_t opt_iconv = ICONV_NONE;
798 static char opt_search[SIZEOF_STR] = "";
799 static char opt_cdup[SIZEOF_STR] = "";
800 static char opt_prefix[SIZEOF_STR] = "";
801 static char opt_git_dir[SIZEOF_STR] = "";
802 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
803 static char opt_editor[SIZEOF_STR] = "";
804 static FILE *opt_tty = NULL;
806 #define is_initial_commit() (!*opt_head_rev)
807 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
809 static enum request
810 parse_options(int argc, const char *argv[], const char ***run_argv)
811 {
812 enum request request = REQ_VIEW_MAIN;
813 const char *subcommand;
814 bool seen_dashdash = FALSE;
815 /* XXX: This is vulnerable to the user overriding options
816 * required for the main view parser. */
817 static const char *custom_argv[SIZEOF_ARG] = {
818 "git", "log", "--no-color", "--pretty=raw", "--parents",
819 "--topo-order", NULL
820 };
821 int i, j = 6;
823 if (!isatty(STDIN_FILENO))
824 return REQ_VIEW_PAGER;
826 if (argc <= 1)
827 return REQ_VIEW_MAIN;
829 subcommand = argv[1];
830 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
831 if (!strcmp(subcommand, "-S"))
832 warn("`-S' has been deprecated; use `tig status' instead");
833 if (argc > 2)
834 warn("ignoring arguments after `%s'", subcommand);
835 return REQ_VIEW_STATUS;
837 } else if (!strcmp(subcommand, "blame")) {
838 if (argc <= 2 || argc > 4)
839 die("invalid number of options to blame\n\n%s", usage);
841 i = 2;
842 if (argc == 4) {
843 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
844 i++;
845 }
847 string_ncopy(opt_file, argv[i], strlen(argv[i]));
848 return REQ_VIEW_BLAME;
850 } else if (!strcmp(subcommand, "show")) {
851 request = REQ_VIEW_DIFF;
853 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
854 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
855 warn("`tig %s' has been deprecated", subcommand);
857 } else {
858 subcommand = NULL;
859 }
861 if (subcommand) {
862 custom_argv[1] = subcommand;
863 j = 2;
864 }
866 for (i = 1 + !!subcommand; i < argc; i++) {
867 const char *opt = argv[i];
869 if (seen_dashdash || !strcmp(opt, "--")) {
870 seen_dashdash = TRUE;
872 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
873 printf("tig version %s\n", TIG_VERSION);
874 return REQ_NONE;
876 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
877 printf("%s\n", usage);
878 return REQ_NONE;
879 }
881 custom_argv[j++] = opt;
882 if (j >= ARRAY_SIZE(custom_argv))
883 die("command too long");
884 }
886 custom_argv[j] = NULL;
887 *run_argv = custom_argv;
889 return request;
890 }
893 /*
894 * Line-oriented content detection.
895 */
897 #define LINE_INFO \
898 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
899 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
900 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
901 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
902 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
903 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
904 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
905 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
906 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
907 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
908 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
910 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
911 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
912 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
913 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
914 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
915 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
916 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
917 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
918 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
919 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
920 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
921 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
922 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
923 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
924 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
925 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
926 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
927 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
928 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
929 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
930 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
931 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
932 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
933 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
934 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
935 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
936 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
937 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
938 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
939 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
940 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
941 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
942 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
943 LINE(TREE_PARENT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
944 LINE(TREE_MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
945 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
946 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
947 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
948 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
949 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
950 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
951 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
952 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
953 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
955 enum line_type {
956 #define LINE(type, line, fg, bg, attr) \
957 LINE_##type
958 LINE_INFO,
959 LINE_NONE
960 #undef LINE
961 };
963 struct line_info {
964 const char *name; /* Option name. */
965 int namelen; /* Size of option name. */
966 const char *line; /* The start of line to match. */
967 int linelen; /* Size of string to match. */
968 int fg, bg, attr; /* Color and text attributes for the lines. */
969 };
971 static struct line_info line_info[] = {
972 #define LINE(type, line, fg, bg, attr) \
973 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
974 LINE_INFO
975 #undef LINE
976 };
978 static enum line_type
979 get_line_type(const char *line)
980 {
981 int linelen = strlen(line);
982 enum line_type type;
984 for (type = 0; type < ARRAY_SIZE(line_info); type++)
985 /* Case insensitive search matches Signed-off-by lines better. */
986 if (linelen >= line_info[type].linelen &&
987 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
988 return type;
990 return LINE_DEFAULT;
991 }
993 static inline int
994 get_line_attr(enum line_type type)
995 {
996 assert(type < ARRAY_SIZE(line_info));
997 return COLOR_PAIR(type) | line_info[type].attr;
998 }
1000 static struct line_info *
1001 get_line_info(const char *name)
1002 {
1003 size_t namelen = strlen(name);
1004 enum line_type type;
1006 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1007 if (namelen == line_info[type].namelen &&
1008 !string_enum_compare(line_info[type].name, name, namelen))
1009 return &line_info[type];
1011 return NULL;
1012 }
1014 static void
1015 init_colors(void)
1016 {
1017 int default_bg = line_info[LINE_DEFAULT].bg;
1018 int default_fg = line_info[LINE_DEFAULT].fg;
1019 enum line_type type;
1021 start_color();
1023 if (assume_default_colors(default_fg, default_bg) == ERR) {
1024 default_bg = COLOR_BLACK;
1025 default_fg = COLOR_WHITE;
1026 }
1028 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1029 struct line_info *info = &line_info[type];
1030 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1031 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1033 init_pair(type, fg, bg);
1034 }
1035 }
1037 struct line {
1038 enum line_type type;
1040 /* State flags */
1041 unsigned int selected:1;
1042 unsigned int dirty:1;
1043 unsigned int cleareol:1;
1045 void *data; /* User data */
1046 };
1049 /*
1050 * Keys
1051 */
1053 struct keybinding {
1054 int alias;
1055 enum request request;
1056 };
1058 static struct keybinding default_keybindings[] = {
1059 /* View switching */
1060 { 'm', REQ_VIEW_MAIN },
1061 { 'd', REQ_VIEW_DIFF },
1062 { 'l', REQ_VIEW_LOG },
1063 { 't', REQ_VIEW_TREE },
1064 { 'f', REQ_VIEW_BLOB },
1065 { 'B', REQ_VIEW_BLAME },
1066 { 'p', REQ_VIEW_PAGER },
1067 { 'h', REQ_VIEW_HELP },
1068 { 'S', REQ_VIEW_STATUS },
1069 { 'c', REQ_VIEW_STAGE },
1071 /* View manipulation */
1072 { 'q', REQ_VIEW_CLOSE },
1073 { KEY_TAB, REQ_VIEW_NEXT },
1074 { KEY_RETURN, REQ_ENTER },
1075 { KEY_UP, REQ_PREVIOUS },
1076 { KEY_DOWN, REQ_NEXT },
1077 { 'R', REQ_REFRESH },
1078 { KEY_F(5), REQ_REFRESH },
1079 { 'O', REQ_MAXIMIZE },
1081 /* Cursor navigation */
1082 { 'k', REQ_MOVE_UP },
1083 { 'j', REQ_MOVE_DOWN },
1084 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1085 { KEY_END, REQ_MOVE_LAST_LINE },
1086 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1087 { ' ', REQ_MOVE_PAGE_DOWN },
1088 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1089 { 'b', REQ_MOVE_PAGE_UP },
1090 { '-', REQ_MOVE_PAGE_UP },
1092 /* Scrolling */
1093 { KEY_IC, REQ_SCROLL_LINE_UP },
1094 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1095 { 'w', REQ_SCROLL_PAGE_UP },
1096 { 's', REQ_SCROLL_PAGE_DOWN },
1098 /* Searching */
1099 { '/', REQ_SEARCH },
1100 { '?', REQ_SEARCH_BACK },
1101 { 'n', REQ_FIND_NEXT },
1102 { 'N', REQ_FIND_PREV },
1104 /* Misc */
1105 { 'Q', REQ_QUIT },
1106 { 'z', REQ_STOP_LOADING },
1107 { 'v', REQ_SHOW_VERSION },
1108 { 'r', REQ_SCREEN_REDRAW },
1109 { '.', REQ_TOGGLE_LINENO },
1110 { 'D', REQ_TOGGLE_DATE },
1111 { 'A', REQ_TOGGLE_AUTHOR },
1112 { 'g', REQ_TOGGLE_REV_GRAPH },
1113 { 'F', REQ_TOGGLE_REFS },
1114 { ':', REQ_PROMPT },
1115 { 'u', REQ_STATUS_UPDATE },
1116 { '!', REQ_STATUS_REVERT },
1117 { 'M', REQ_STATUS_MERGE },
1118 { '@', REQ_STAGE_NEXT },
1119 { ',', REQ_PARENT },
1120 { 'e', REQ_EDIT },
1121 };
1123 #define KEYMAP_INFO \
1124 KEYMAP_(GENERIC), \
1125 KEYMAP_(MAIN), \
1126 KEYMAP_(DIFF), \
1127 KEYMAP_(LOG), \
1128 KEYMAP_(TREE), \
1129 KEYMAP_(BLOB), \
1130 KEYMAP_(BLAME), \
1131 KEYMAP_(PAGER), \
1132 KEYMAP_(HELP), \
1133 KEYMAP_(STATUS), \
1134 KEYMAP_(STAGE)
1136 enum keymap {
1137 #define KEYMAP_(name) KEYMAP_##name
1138 KEYMAP_INFO
1139 #undef KEYMAP_
1140 };
1142 static struct int_map keymap_table[] = {
1143 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1144 KEYMAP_INFO
1145 #undef KEYMAP_
1146 };
1148 #define set_keymap(map, name) \
1149 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1151 struct keybinding_table {
1152 struct keybinding *data;
1153 size_t size;
1154 };
1156 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1158 static void
1159 add_keybinding(enum keymap keymap, enum request request, int key)
1160 {
1161 struct keybinding_table *table = &keybindings[keymap];
1163 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1164 if (!table->data)
1165 die("Failed to allocate keybinding");
1166 table->data[table->size].alias = key;
1167 table->data[table->size++].request = request;
1168 }
1170 /* Looks for a key binding first in the given map, then in the generic map, and
1171 * lastly in the default keybindings. */
1172 static enum request
1173 get_keybinding(enum keymap keymap, int key)
1174 {
1175 size_t i;
1177 for (i = 0; i < keybindings[keymap].size; i++)
1178 if (keybindings[keymap].data[i].alias == key)
1179 return keybindings[keymap].data[i].request;
1181 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1182 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1183 return keybindings[KEYMAP_GENERIC].data[i].request;
1185 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1186 if (default_keybindings[i].alias == key)
1187 return default_keybindings[i].request;
1189 return (enum request) key;
1190 }
1193 struct key {
1194 const char *name;
1195 int value;
1196 };
1198 static struct key key_table[] = {
1199 { "Enter", KEY_RETURN },
1200 { "Space", ' ' },
1201 { "Backspace", KEY_BACKSPACE },
1202 { "Tab", KEY_TAB },
1203 { "Escape", KEY_ESC },
1204 { "Left", KEY_LEFT },
1205 { "Right", KEY_RIGHT },
1206 { "Up", KEY_UP },
1207 { "Down", KEY_DOWN },
1208 { "Insert", KEY_IC },
1209 { "Delete", KEY_DC },
1210 { "Hash", '#' },
1211 { "Home", KEY_HOME },
1212 { "End", KEY_END },
1213 { "PageUp", KEY_PPAGE },
1214 { "PageDown", KEY_NPAGE },
1215 { "F1", KEY_F(1) },
1216 { "F2", KEY_F(2) },
1217 { "F3", KEY_F(3) },
1218 { "F4", KEY_F(4) },
1219 { "F5", KEY_F(5) },
1220 { "F6", KEY_F(6) },
1221 { "F7", KEY_F(7) },
1222 { "F8", KEY_F(8) },
1223 { "F9", KEY_F(9) },
1224 { "F10", KEY_F(10) },
1225 { "F11", KEY_F(11) },
1226 { "F12", KEY_F(12) },
1227 };
1229 static int
1230 get_key_value(const char *name)
1231 {
1232 int i;
1234 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1235 if (!strcasecmp(key_table[i].name, name))
1236 return key_table[i].value;
1238 if (strlen(name) == 1 && isprint(*name))
1239 return (int) *name;
1241 return ERR;
1242 }
1244 static const char *
1245 get_key_name(int key_value)
1246 {
1247 static char key_char[] = "'X'";
1248 const char *seq = NULL;
1249 int key;
1251 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1252 if (key_table[key].value == key_value)
1253 seq = key_table[key].name;
1255 if (seq == NULL &&
1256 key_value < 127 &&
1257 isprint(key_value)) {
1258 key_char[1] = (char) key_value;
1259 seq = key_char;
1260 }
1262 return seq ? seq : "(no key)";
1263 }
1265 static const char *
1266 get_key(enum request request)
1267 {
1268 static char buf[BUFSIZ];
1269 size_t pos = 0;
1270 char *sep = "";
1271 int i;
1273 buf[pos] = 0;
1275 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1276 struct keybinding *keybinding = &default_keybindings[i];
1278 if (keybinding->request != request)
1279 continue;
1281 if (!string_format_from(buf, &pos, "%s%s", sep,
1282 get_key_name(keybinding->alias)))
1283 return "Too many keybindings!";
1284 sep = ", ";
1285 }
1287 return buf;
1288 }
1290 struct run_request {
1291 enum keymap keymap;
1292 int key;
1293 const char *argv[SIZEOF_ARG];
1294 };
1296 static struct run_request *run_request;
1297 static size_t run_requests;
1299 static enum request
1300 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1301 {
1302 struct run_request *req;
1304 if (argc >= ARRAY_SIZE(req->argv) - 1)
1305 return REQ_NONE;
1307 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1308 if (!req)
1309 return REQ_NONE;
1311 run_request = req;
1312 req = &run_request[run_requests];
1313 req->keymap = keymap;
1314 req->key = key;
1315 req->argv[0] = NULL;
1317 if (!format_argv(req->argv, argv, FORMAT_NONE))
1318 return REQ_NONE;
1320 return REQ_NONE + ++run_requests;
1321 }
1323 static struct run_request *
1324 get_run_request(enum request request)
1325 {
1326 if (request <= REQ_NONE)
1327 return NULL;
1328 return &run_request[request - REQ_NONE - 1];
1329 }
1331 static void
1332 add_builtin_run_requests(void)
1333 {
1334 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1335 const char *gc[] = { "git", "gc", NULL };
1336 struct {
1337 enum keymap keymap;
1338 int key;
1339 int argc;
1340 const char **argv;
1341 } reqs[] = {
1342 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1343 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1344 };
1345 int i;
1347 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1348 enum request req;
1350 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1351 if (req != REQ_NONE)
1352 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1353 }
1354 }
1356 /*
1357 * User config file handling.
1358 */
1360 static struct int_map color_map[] = {
1361 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1362 COLOR_MAP(DEFAULT),
1363 COLOR_MAP(BLACK),
1364 COLOR_MAP(BLUE),
1365 COLOR_MAP(CYAN),
1366 COLOR_MAP(GREEN),
1367 COLOR_MAP(MAGENTA),
1368 COLOR_MAP(RED),
1369 COLOR_MAP(WHITE),
1370 COLOR_MAP(YELLOW),
1371 };
1373 #define set_color(color, name) \
1374 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1376 static struct int_map attr_map[] = {
1377 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1378 ATTR_MAP(NORMAL),
1379 ATTR_MAP(BLINK),
1380 ATTR_MAP(BOLD),
1381 ATTR_MAP(DIM),
1382 ATTR_MAP(REVERSE),
1383 ATTR_MAP(STANDOUT),
1384 ATTR_MAP(UNDERLINE),
1385 };
1387 #define set_attribute(attr, name) \
1388 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1390 static int config_lineno;
1391 static bool config_errors;
1392 static const char *config_msg;
1394 /* Wants: object fgcolor bgcolor [attr] */
1395 static int
1396 option_color_command(int argc, const char *argv[])
1397 {
1398 struct line_info *info;
1400 if (argc != 3 && argc != 4) {
1401 config_msg = "Wrong number of arguments given to color command";
1402 return ERR;
1403 }
1405 info = get_line_info(argv[0]);
1406 if (!info) {
1407 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1408 info = get_line_info("delimiter");
1410 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1411 info = get_line_info("date");
1413 } else {
1414 config_msg = "Unknown color name";
1415 return ERR;
1416 }
1417 }
1419 if (set_color(&info->fg, argv[1]) == ERR ||
1420 set_color(&info->bg, argv[2]) == ERR) {
1421 config_msg = "Unknown color";
1422 return ERR;
1423 }
1425 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1426 config_msg = "Unknown attribute";
1427 return ERR;
1428 }
1430 return OK;
1431 }
1433 static bool parse_bool(const char *s)
1434 {
1435 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1436 !strcmp(s, "yes")) ? TRUE : FALSE;
1437 }
1439 static int
1440 parse_int(const char *s, int default_value, int min, int max)
1441 {
1442 int value = atoi(s);
1444 return (value < min || value > max) ? default_value : value;
1445 }
1447 /* Wants: name = value */
1448 static int
1449 option_set_command(int argc, const char *argv[])
1450 {
1451 if (argc != 3) {
1452 config_msg = "Wrong number of arguments given to set command";
1453 return ERR;
1454 }
1456 if (strcmp(argv[1], "=")) {
1457 config_msg = "No value assigned";
1458 return ERR;
1459 }
1461 if (!strcmp(argv[0], "show-author")) {
1462 opt_author = parse_bool(argv[2]);
1463 return OK;
1464 }
1466 if (!strcmp(argv[0], "show-date")) {
1467 opt_date = parse_bool(argv[2]);
1468 return OK;
1469 }
1471 if (!strcmp(argv[0], "show-rev-graph")) {
1472 opt_rev_graph = parse_bool(argv[2]);
1473 return OK;
1474 }
1476 if (!strcmp(argv[0], "show-refs")) {
1477 opt_show_refs = parse_bool(argv[2]);
1478 return OK;
1479 }
1481 if (!strcmp(argv[0], "show-line-numbers")) {
1482 opt_line_number = parse_bool(argv[2]);
1483 return OK;
1484 }
1486 if (!strcmp(argv[0], "line-graphics")) {
1487 opt_line_graphics = parse_bool(argv[2]);
1488 return OK;
1489 }
1491 if (!strcmp(argv[0], "line-number-interval")) {
1492 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1493 return OK;
1494 }
1496 if (!strcmp(argv[0], "author-width")) {
1497 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1498 return OK;
1499 }
1501 if (!strcmp(argv[0], "tab-size")) {
1502 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1503 return OK;
1504 }
1506 if (!strcmp(argv[0], "commit-encoding")) {
1507 const char *arg = argv[2];
1508 int arglen = strlen(arg);
1510 switch (arg[0]) {
1511 case '"':
1512 case '\'':
1513 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1514 config_msg = "Unmatched quotation";
1515 return ERR;
1516 }
1517 arg += 1; arglen -= 2;
1518 default:
1519 string_ncopy(opt_encoding, arg, strlen(arg));
1520 return OK;
1521 }
1522 }
1524 config_msg = "Unknown variable name";
1525 return ERR;
1526 }
1528 /* Wants: mode request key */
1529 static int
1530 option_bind_command(int argc, const char *argv[])
1531 {
1532 enum request request;
1533 int keymap;
1534 int key;
1536 if (argc < 3) {
1537 config_msg = "Wrong number of arguments given to bind command";
1538 return ERR;
1539 }
1541 if (set_keymap(&keymap, argv[0]) == ERR) {
1542 config_msg = "Unknown key map";
1543 return ERR;
1544 }
1546 key = get_key_value(argv[1]);
1547 if (key == ERR) {
1548 config_msg = "Unknown key";
1549 return ERR;
1550 }
1552 request = get_request(argv[2]);
1553 if (request == REQ_NONE) {
1554 struct {
1555 const char *name;
1556 enum request request;
1557 } obsolete[] = {
1558 { "cherry-pick", REQ_NONE },
1559 { "screen-resize", REQ_NONE },
1560 { "tree-parent", REQ_PARENT },
1561 };
1562 size_t namelen = strlen(argv[2]);
1563 int i;
1565 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1566 if (namelen != strlen(obsolete[i].name) ||
1567 string_enum_compare(obsolete[i].name, argv[2], namelen))
1568 continue;
1569 if (obsolete[i].request != REQ_NONE)
1570 add_keybinding(keymap, obsolete[i].request, key);
1571 config_msg = "Obsolete request name";
1572 return ERR;
1573 }
1574 }
1575 if (request == REQ_NONE && *argv[2]++ == '!')
1576 request = add_run_request(keymap, key, argc - 2, argv + 2);
1577 if (request == REQ_NONE) {
1578 config_msg = "Unknown request name";
1579 return ERR;
1580 }
1582 add_keybinding(keymap, request, key);
1584 return OK;
1585 }
1587 static int
1588 set_option(const char *opt, char *value)
1589 {
1590 const char *argv[SIZEOF_ARG];
1591 int argc = 0;
1593 if (!argv_from_string(argv, &argc, value)) {
1594 config_msg = "Too many option arguments";
1595 return ERR;
1596 }
1598 if (!strcmp(opt, "color"))
1599 return option_color_command(argc, argv);
1601 if (!strcmp(opt, "set"))
1602 return option_set_command(argc, argv);
1604 if (!strcmp(opt, "bind"))
1605 return option_bind_command(argc, argv);
1607 config_msg = "Unknown option command";
1608 return ERR;
1609 }
1611 static int
1612 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1613 {
1614 int status = OK;
1616 config_lineno++;
1617 config_msg = "Internal error";
1619 /* Check for comment markers, since read_properties() will
1620 * only ensure opt and value are split at first " \t". */
1621 optlen = strcspn(opt, "#");
1622 if (optlen == 0)
1623 return OK;
1625 if (opt[optlen] != 0) {
1626 config_msg = "No option value";
1627 status = ERR;
1629 } else {
1630 /* Look for comment endings in the value. */
1631 size_t len = strcspn(value, "#");
1633 if (len < valuelen) {
1634 valuelen = len;
1635 value[valuelen] = 0;
1636 }
1638 status = set_option(opt, value);
1639 }
1641 if (status == ERR) {
1642 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1643 config_lineno, (int) optlen, opt, config_msg);
1644 config_errors = TRUE;
1645 }
1647 /* Always keep going if errors are encountered. */
1648 return OK;
1649 }
1651 static void
1652 load_option_file(const char *path)
1653 {
1654 struct io io = {};
1656 /* It's ok that the file doesn't exist. */
1657 if (!io_open(&io, path))
1658 return;
1660 config_lineno = 0;
1661 config_errors = FALSE;
1663 if (read_properties(&io, " \t", read_option) == ERR ||
1664 config_errors == TRUE)
1665 fprintf(stderr, "Errors while loading %s.\n", path);
1666 }
1668 static int
1669 load_options(void)
1670 {
1671 const char *home = getenv("HOME");
1672 const char *tigrc_user = getenv("TIGRC_USER");
1673 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1674 char buf[SIZEOF_STR];
1676 add_builtin_run_requests();
1678 if (!tigrc_system) {
1679 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1680 return ERR;
1681 tigrc_system = buf;
1682 }
1683 load_option_file(tigrc_system);
1685 if (!tigrc_user) {
1686 if (!home || !string_format(buf, "%s/.tigrc", home))
1687 return ERR;
1688 tigrc_user = buf;
1689 }
1690 load_option_file(tigrc_user);
1692 return OK;
1693 }
1696 /*
1697 * The viewer
1698 */
1700 struct view;
1701 struct view_ops;
1703 /* The display array of active views and the index of the current view. */
1704 static struct view *display[2];
1705 static unsigned int current_view;
1707 /* Reading from the prompt? */
1708 static bool input_mode = FALSE;
1710 #define foreach_displayed_view(view, i) \
1711 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1713 #define displayed_views() (display[1] != NULL ? 2 : 1)
1715 /* Current head and commit ID */
1716 static char ref_blob[SIZEOF_REF] = "";
1717 static char ref_commit[SIZEOF_REF] = "HEAD";
1718 static char ref_head[SIZEOF_REF] = "HEAD";
1720 struct view {
1721 const char *name; /* View name */
1722 const char *cmd_env; /* Command line set via environment */
1723 const char *id; /* Points to either of ref_{head,commit,blob} */
1725 struct view_ops *ops; /* View operations */
1727 enum keymap keymap; /* What keymap does this view have */
1728 bool git_dir; /* Whether the view requires a git directory. */
1730 char ref[SIZEOF_REF]; /* Hovered commit reference */
1731 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1733 int height, width; /* The width and height of the main window */
1734 WINDOW *win; /* The main window */
1735 WINDOW *title; /* The title window living below the main window */
1737 /* Navigation */
1738 unsigned long offset; /* Offset of the window top */
1739 unsigned long lineno; /* Current line number */
1740 unsigned long p_offset; /* Previous offset of the window top */
1741 unsigned long p_lineno; /* Previous current line number */
1742 bool p_restore; /* Should the previous position be restored. */
1744 /* Searching */
1745 char grep[SIZEOF_STR]; /* Search string */
1746 regex_t *regex; /* Pre-compiled regex */
1748 /* If non-NULL, points to the view that opened this view. If this view
1749 * is closed tig will switch back to the parent view. */
1750 struct view *parent;
1752 /* Buffering */
1753 size_t lines; /* Total number of lines */
1754 struct line *line; /* Line index */
1755 size_t line_alloc; /* Total number of allocated lines */
1756 unsigned int digits; /* Number of digits in the lines member. */
1758 /* Drawing */
1759 struct line *curline; /* Line currently being drawn. */
1760 enum line_type curtype; /* Attribute currently used for drawing. */
1761 unsigned long col; /* Column when drawing. */
1763 /* Loading */
1764 struct io io;
1765 struct io *pipe;
1766 time_t start_time;
1767 time_t update_secs;
1768 };
1770 struct view_ops {
1771 /* What type of content being displayed. Used in the title bar. */
1772 const char *type;
1773 /* Default command arguments. */
1774 const char **argv;
1775 /* Open and reads in all view content. */
1776 bool (*open)(struct view *view);
1777 /* Read one line; updates view->line. */
1778 bool (*read)(struct view *view, char *data);
1779 /* Draw one line; @lineno must be < view->height. */
1780 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1781 /* Depending on view handle a special requests. */
1782 enum request (*request)(struct view *view, enum request request, struct line *line);
1783 /* Search for regex in a line. */
1784 bool (*grep)(struct view *view, struct line *line);
1785 /* Select line */
1786 void (*select)(struct view *view, struct line *line);
1787 };
1789 static struct view_ops blame_ops;
1790 static struct view_ops blob_ops;
1791 static struct view_ops diff_ops;
1792 static struct view_ops help_ops;
1793 static struct view_ops log_ops;
1794 static struct view_ops main_ops;
1795 static struct view_ops pager_ops;
1796 static struct view_ops stage_ops;
1797 static struct view_ops status_ops;
1798 static struct view_ops tree_ops;
1800 #define VIEW_STR(name, env, ref, ops, map, git) \
1801 { name, #env, ref, ops, map, git }
1803 #define VIEW_(id, name, ops, git, ref) \
1804 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1807 static struct view views[] = {
1808 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1809 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1810 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1811 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1812 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1813 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1814 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1815 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1816 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1817 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1818 };
1820 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1821 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1823 #define foreach_view(view, i) \
1824 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1826 #define view_is_displayed(view) \
1827 (view == display[0] || view == display[1])
1830 enum line_graphic {
1831 LINE_GRAPHIC_VLINE
1832 };
1834 static int line_graphics[] = {
1835 /* LINE_GRAPHIC_VLINE: */ '|'
1836 };
1838 static inline void
1839 set_view_attr(struct view *view, enum line_type type)
1840 {
1841 if (!view->curline->selected && view->curtype != type) {
1842 wattrset(view->win, get_line_attr(type));
1843 wchgat(view->win, -1, 0, type, NULL);
1844 view->curtype = type;
1845 }
1846 }
1848 static int
1849 draw_chars(struct view *view, enum line_type type, const char *string,
1850 int max_len, bool use_tilde)
1851 {
1852 int len = 0;
1853 int col = 0;
1854 int trimmed = FALSE;
1856 if (max_len <= 0)
1857 return 0;
1859 if (opt_utf8) {
1860 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1861 } else {
1862 col = len = strlen(string);
1863 if (len > max_len) {
1864 if (use_tilde) {
1865 max_len -= 1;
1866 }
1867 col = len = max_len;
1868 trimmed = TRUE;
1869 }
1870 }
1872 set_view_attr(view, type);
1873 waddnstr(view->win, string, len);
1874 if (trimmed && use_tilde) {
1875 set_view_attr(view, LINE_DELIMITER);
1876 waddch(view->win, '~');
1877 col++;
1878 }
1880 return col;
1881 }
1883 static int
1884 draw_space(struct view *view, enum line_type type, int max, int spaces)
1885 {
1886 static char space[] = " ";
1887 int col = 0;
1889 spaces = MIN(max, spaces);
1891 while (spaces > 0) {
1892 int len = MIN(spaces, sizeof(space) - 1);
1894 col += draw_chars(view, type, space, spaces, FALSE);
1895 spaces -= len;
1896 }
1898 return col;
1899 }
1901 static bool
1902 draw_lineno(struct view *view, unsigned int lineno)
1903 {
1904 char number[10];
1905 int digits3 = view->digits < 3 ? 3 : view->digits;
1906 int max_number = MIN(digits3, STRING_SIZE(number));
1907 int max = view->width - view->col;
1908 int col;
1910 if (max < max_number)
1911 max_number = max;
1913 lineno += view->offset + 1;
1914 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1915 static char fmt[] = "%1ld";
1917 if (view->digits <= 9)
1918 fmt[1] = '0' + digits3;
1920 if (!string_format(number, fmt, lineno))
1921 number[0] = 0;
1922 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1923 } else {
1924 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1925 }
1927 if (col < max) {
1928 set_view_attr(view, LINE_DEFAULT);
1929 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1930 col++;
1931 }
1933 if (col < max)
1934 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1935 view->col += col;
1937 return view->width - view->col <= 0;
1938 }
1940 static bool
1941 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1942 {
1943 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1944 return view->width - view->col <= 0;
1945 }
1947 static bool
1948 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1949 {
1950 int max = view->width - view->col;
1951 int i;
1953 if (max < size)
1954 size = max;
1956 set_view_attr(view, type);
1957 /* Using waddch() instead of waddnstr() ensures that
1958 * they'll be rendered correctly for the cursor line. */
1959 for (i = 0; i < size; i++)
1960 waddch(view->win, graphic[i]);
1962 view->col += size;
1963 if (size < max) {
1964 waddch(view->win, ' ');
1965 view->col++;
1966 }
1968 return view->width - view->col <= 0;
1969 }
1971 static bool
1972 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1973 {
1974 int max = MIN(view->width - view->col, len);
1975 int col;
1977 if (text)
1978 col = draw_chars(view, type, text, max - 1, trim);
1979 else
1980 col = draw_space(view, type, max - 1, max - 1);
1982 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1983 return view->width - view->col <= 0;
1984 }
1986 static bool
1987 draw_date(struct view *view, struct tm *time)
1988 {
1989 char buf[DATE_COLS];
1990 char *date;
1991 int timelen = 0;
1993 if (time)
1994 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1995 date = timelen ? buf : NULL;
1997 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1998 }
2000 static bool
2001 draw_view_line(struct view *view, unsigned int lineno)
2002 {
2003 struct line *line;
2004 bool selected = (view->offset + lineno == view->lineno);
2005 bool draw_ok;
2007 assert(view_is_displayed(view));
2009 if (view->offset + lineno >= view->lines)
2010 return FALSE;
2012 line = &view->line[view->offset + lineno];
2014 wmove(view->win, lineno, 0);
2015 if (line->cleareol)
2016 wclrtoeol(view->win);
2017 view->col = 0;
2018 view->curline = line;
2019 view->curtype = LINE_NONE;
2020 line->selected = FALSE;
2021 line->dirty = line->cleareol = 0;
2023 if (selected) {
2024 set_view_attr(view, LINE_CURSOR);
2025 line->selected = TRUE;
2026 view->ops->select(view, line);
2027 }
2029 scrollok(view->win, FALSE);
2030 draw_ok = view->ops->draw(view, line, lineno);
2031 scrollok(view->win, TRUE);
2033 return draw_ok;
2034 }
2036 static void
2037 redraw_view_dirty(struct view *view)
2038 {
2039 bool dirty = FALSE;
2040 int lineno;
2042 for (lineno = 0; lineno < view->height; lineno++) {
2043 if (view->offset + lineno >= view->lines)
2044 break;
2045 if (!view->line[view->offset + lineno].dirty)
2046 continue;
2047 dirty = TRUE;
2048 if (!draw_view_line(view, lineno))
2049 break;
2050 }
2052 if (!dirty)
2053 return;
2054 redrawwin(view->win);
2055 if (input_mode)
2056 wnoutrefresh(view->win);
2057 else
2058 wrefresh(view->win);
2059 }
2061 static void
2062 redraw_view_from(struct view *view, int lineno)
2063 {
2064 assert(0 <= lineno && lineno < view->height);
2066 for (; lineno < view->height; lineno++) {
2067 if (!draw_view_line(view, lineno))
2068 break;
2069 }
2071 redrawwin(view->win);
2072 if (input_mode)
2073 wnoutrefresh(view->win);
2074 else
2075 wrefresh(view->win);
2076 }
2078 static void
2079 redraw_view(struct view *view)
2080 {
2081 werase(view->win);
2082 redraw_view_from(view, 0);
2083 }
2086 static void
2087 update_view_title(struct view *view)
2088 {
2089 char buf[SIZEOF_STR];
2090 char state[SIZEOF_STR];
2091 size_t bufpos = 0, statelen = 0;
2093 assert(view_is_displayed(view));
2095 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2096 unsigned int view_lines = view->offset + view->height;
2097 unsigned int lines = view->lines
2098 ? MIN(view_lines, view->lines) * 100 / view->lines
2099 : 0;
2101 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2102 view->ops->type,
2103 view->lineno + 1,
2104 view->lines,
2105 lines);
2107 }
2109 if (view->pipe) {
2110 time_t secs = time(NULL) - view->start_time;
2112 /* Three git seconds are a long time ... */
2113 if (secs > 2)
2114 string_format_from(state, &statelen, " loading %lds", secs);
2115 }
2117 string_format_from(buf, &bufpos, "[%s]", view->name);
2118 if (*view->ref && bufpos < view->width) {
2119 size_t refsize = strlen(view->ref);
2120 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2122 if (minsize < view->width)
2123 refsize = view->width - minsize + 7;
2124 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2125 }
2127 if (statelen && bufpos < view->width) {
2128 string_format_from(buf, &bufpos, "%s", state);
2129 }
2131 if (view == display[current_view])
2132 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2133 else
2134 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2136 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2137 wclrtoeol(view->title);
2138 wmove(view->title, 0, view->width - 1);
2140 if (input_mode)
2141 wnoutrefresh(view->title);
2142 else
2143 wrefresh(view->title);
2144 }
2146 static void
2147 resize_display(void)
2148 {
2149 int offset, i;
2150 struct view *base = display[0];
2151 struct view *view = display[1] ? display[1] : display[0];
2153 /* Setup window dimensions */
2155 getmaxyx(stdscr, base->height, base->width);
2157 /* Make room for the status window. */
2158 base->height -= 1;
2160 if (view != base) {
2161 /* Horizontal split. */
2162 view->width = base->width;
2163 view->height = SCALE_SPLIT_VIEW(base->height);
2164 base->height -= view->height;
2166 /* Make room for the title bar. */
2167 view->height -= 1;
2168 }
2170 /* Make room for the title bar. */
2171 base->height -= 1;
2173 offset = 0;
2175 foreach_displayed_view (view, i) {
2176 if (!view->win) {
2177 view->win = newwin(view->height, 0, offset, 0);
2178 if (!view->win)
2179 die("Failed to create %s view", view->name);
2181 scrollok(view->win, TRUE);
2183 view->title = newwin(1, 0, offset + view->height, 0);
2184 if (!view->title)
2185 die("Failed to create title window");
2187 } else {
2188 wresize(view->win, view->height, view->width);
2189 mvwin(view->win, offset, 0);
2190 mvwin(view->title, offset + view->height, 0);
2191 }
2193 offset += view->height + 1;
2194 }
2195 }
2197 static void
2198 redraw_display(bool clear)
2199 {
2200 struct view *view;
2201 int i;
2203 foreach_displayed_view (view, i) {
2204 if (clear)
2205 wclear(view->win);
2206 redraw_view(view);
2207 update_view_title(view);
2208 }
2209 }
2211 static void
2212 update_display_cursor(struct view *view)
2213 {
2214 /* Move the cursor to the right-most column of the cursor line.
2215 *
2216 * XXX: This could turn out to be a bit expensive, but it ensures that
2217 * the cursor does not jump around. */
2218 if (view->lines) {
2219 wmove(view->win, view->lineno - view->offset, view->width - 1);
2220 wrefresh(view->win);
2221 }
2222 }
2224 static void
2225 toggle_view_option(bool *option, const char *help)
2226 {
2227 *option = !*option;
2228 redraw_display(FALSE);
2229 report("%sabling %s", *option ? "En" : "Dis", help);
2230 }
2232 /*
2233 * Navigation
2234 */
2236 /* Scrolling backend */
2237 static void
2238 do_scroll_view(struct view *view, int lines)
2239 {
2240 bool redraw_current_line = FALSE;
2242 /* The rendering expects the new offset. */
2243 view->offset += lines;
2245 assert(0 <= view->offset && view->offset < view->lines);
2246 assert(lines);
2248 /* Move current line into the view. */
2249 if (view->lineno < view->offset) {
2250 view->lineno = view->offset;
2251 redraw_current_line = TRUE;
2252 } else if (view->lineno >= view->offset + view->height) {
2253 view->lineno = view->offset + view->height - 1;
2254 redraw_current_line = TRUE;
2255 }
2257 assert(view->offset <= view->lineno && view->lineno < view->lines);
2259 /* Redraw the whole screen if scrolling is pointless. */
2260 if (view->height < ABS(lines)) {
2261 redraw_view(view);
2263 } else {
2264 int line = lines > 0 ? view->height - lines : 0;
2265 int end = line + ABS(lines);
2267 wscrl(view->win, lines);
2269 for (; line < end; line++) {
2270 if (!draw_view_line(view, line))
2271 break;
2272 }
2274 if (redraw_current_line)
2275 draw_view_line(view, view->lineno - view->offset);
2276 }
2278 redrawwin(view->win);
2279 wrefresh(view->win);
2280 report("");
2281 }
2283 /* Scroll frontend */
2284 static void
2285 scroll_view(struct view *view, enum request request)
2286 {
2287 int lines = 1;
2289 assert(view_is_displayed(view));
2291 switch (request) {
2292 case REQ_SCROLL_PAGE_DOWN:
2293 lines = view->height;
2294 case REQ_SCROLL_LINE_DOWN:
2295 if (view->offset + lines > view->lines)
2296 lines = view->lines - view->offset;
2298 if (lines == 0 || view->offset + view->height >= view->lines) {
2299 report("Cannot scroll beyond the last line");
2300 return;
2301 }
2302 break;
2304 case REQ_SCROLL_PAGE_UP:
2305 lines = view->height;
2306 case REQ_SCROLL_LINE_UP:
2307 if (lines > view->offset)
2308 lines = view->offset;
2310 if (lines == 0) {
2311 report("Cannot scroll beyond the first line");
2312 return;
2313 }
2315 lines = -lines;
2316 break;
2318 default:
2319 die("request %d not handled in switch", request);
2320 }
2322 do_scroll_view(view, lines);
2323 }
2325 /* Cursor moving */
2326 static void
2327 move_view(struct view *view, enum request request)
2328 {
2329 int scroll_steps = 0;
2330 int steps;
2332 switch (request) {
2333 case REQ_MOVE_FIRST_LINE:
2334 steps = -view->lineno;
2335 break;
2337 case REQ_MOVE_LAST_LINE:
2338 steps = view->lines - view->lineno - 1;
2339 break;
2341 case REQ_MOVE_PAGE_UP:
2342 steps = view->height > view->lineno
2343 ? -view->lineno : -view->height;
2344 break;
2346 case REQ_MOVE_PAGE_DOWN:
2347 steps = view->lineno + view->height >= view->lines
2348 ? view->lines - view->lineno - 1 : view->height;
2349 break;
2351 case REQ_MOVE_UP:
2352 steps = -1;
2353 break;
2355 case REQ_MOVE_DOWN:
2356 steps = 1;
2357 break;
2359 default:
2360 die("request %d not handled in switch", request);
2361 }
2363 if (steps <= 0 && view->lineno == 0) {
2364 report("Cannot move beyond the first line");
2365 return;
2367 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2368 report("Cannot move beyond the last line");
2369 return;
2370 }
2372 /* Move the current line */
2373 view->lineno += steps;
2374 assert(0 <= view->lineno && view->lineno < view->lines);
2376 /* Check whether the view needs to be scrolled */
2377 if (view->lineno < view->offset ||
2378 view->lineno >= view->offset + view->height) {
2379 scroll_steps = steps;
2380 if (steps < 0 && -steps > view->offset) {
2381 scroll_steps = -view->offset;
2383 } else if (steps > 0) {
2384 if (view->lineno == view->lines - 1 &&
2385 view->lines > view->height) {
2386 scroll_steps = view->lines - view->offset - 1;
2387 if (scroll_steps >= view->height)
2388 scroll_steps -= view->height - 1;
2389 }
2390 }
2391 }
2393 if (!view_is_displayed(view)) {
2394 view->offset += scroll_steps;
2395 assert(0 <= view->offset && view->offset < view->lines);
2396 view->ops->select(view, &view->line[view->lineno]);
2397 return;
2398 }
2400 /* Repaint the old "current" line if we be scrolling */
2401 if (ABS(steps) < view->height)
2402 draw_view_line(view, view->lineno - steps - view->offset);
2404 if (scroll_steps) {
2405 do_scroll_view(view, scroll_steps);
2406 return;
2407 }
2409 /* Draw the current line */
2410 draw_view_line(view, view->lineno - view->offset);
2412 redrawwin(view->win);
2413 wrefresh(view->win);
2414 report("");
2415 }
2418 /*
2419 * Searching
2420 */
2422 static void search_view(struct view *view, enum request request);
2424 static void
2425 select_view_line(struct view *view, unsigned long lineno)
2426 {
2427 if (lineno - view->offset >= view->height) {
2428 view->offset = lineno;
2429 view->lineno = lineno;
2430 if (view_is_displayed(view))
2431 redraw_view(view);
2433 } else {
2434 unsigned long old_lineno = view->lineno - view->offset;
2436 view->lineno = lineno;
2437 if (view_is_displayed(view)) {
2438 draw_view_line(view, old_lineno);
2439 draw_view_line(view, view->lineno - view->offset);
2440 redrawwin(view->win);
2441 wrefresh(view->win);
2442 } else {
2443 view->ops->select(view, &view->line[view->lineno]);
2444 }
2445 }
2446 }
2448 static void
2449 find_next(struct view *view, enum request request)
2450 {
2451 unsigned long lineno = view->lineno;
2452 int direction;
2454 if (!*view->grep) {
2455 if (!*opt_search)
2456 report("No previous search");
2457 else
2458 search_view(view, request);
2459 return;
2460 }
2462 switch (request) {
2463 case REQ_SEARCH:
2464 case REQ_FIND_NEXT:
2465 direction = 1;
2466 break;
2468 case REQ_SEARCH_BACK:
2469 case REQ_FIND_PREV:
2470 direction = -1;
2471 break;
2473 default:
2474 return;
2475 }
2477 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2478 lineno += direction;
2480 /* Note, lineno is unsigned long so will wrap around in which case it
2481 * will become bigger than view->lines. */
2482 for (; lineno < view->lines; lineno += direction) {
2483 if (view->ops->grep(view, &view->line[lineno])) {
2484 select_view_line(view, lineno);
2485 report("Line %ld matches '%s'", lineno + 1, view->grep);
2486 return;
2487 }
2488 }
2490 report("No match found for '%s'", view->grep);
2491 }
2493 static void
2494 search_view(struct view *view, enum request request)
2495 {
2496 int regex_err;
2498 if (view->regex) {
2499 regfree(view->regex);
2500 *view->grep = 0;
2501 } else {
2502 view->regex = calloc(1, sizeof(*view->regex));
2503 if (!view->regex)
2504 return;
2505 }
2507 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2508 if (regex_err != 0) {
2509 char buf[SIZEOF_STR] = "unknown error";
2511 regerror(regex_err, view->regex, buf, sizeof(buf));
2512 report("Search failed: %s", buf);
2513 return;
2514 }
2516 string_copy(view->grep, opt_search);
2518 find_next(view, request);
2519 }
2521 /*
2522 * Incremental updating
2523 */
2525 static void
2526 reset_view(struct view *view)
2527 {
2528 int i;
2530 for (i = 0; i < view->lines; i++)
2531 free(view->line[i].data);
2532 free(view->line);
2534 view->p_offset = view->offset;
2535 view->p_lineno = view->lineno;
2537 view->line = NULL;
2538 view->offset = 0;
2539 view->lines = 0;
2540 view->lineno = 0;
2541 view->line_alloc = 0;
2542 view->vid[0] = 0;
2543 view->update_secs = 0;
2544 }
2546 static void
2547 free_argv(const char *argv[])
2548 {
2549 int argc;
2551 for (argc = 0; argv[argc]; argc++)
2552 free((void *) argv[argc]);
2553 }
2555 static bool
2556 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2557 {
2558 char buf[SIZEOF_STR];
2559 int argc;
2560 bool noreplace = flags == FORMAT_NONE;
2562 free_argv(dst_argv);
2564 for (argc = 0; src_argv[argc]; argc++) {
2565 const char *arg = src_argv[argc];
2566 size_t bufpos = 0;
2568 while (arg) {
2569 char *next = strstr(arg, "%(");
2570 int len = next - arg;
2571 const char *value;
2573 if (!next || noreplace) {
2574 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2575 noreplace = TRUE;
2576 len = strlen(arg);
2577 value = "";
2579 } else if (!prefixcmp(next, "%(directory)")) {
2580 value = opt_path;
2582 } else if (!prefixcmp(next, "%(file)")) {
2583 value = opt_file;
2585 } else if (!prefixcmp(next, "%(ref)")) {
2586 value = *opt_ref ? opt_ref : "HEAD";
2588 } else if (!prefixcmp(next, "%(head)")) {
2589 value = ref_head;
2591 } else if (!prefixcmp(next, "%(commit)")) {
2592 value = ref_commit;
2594 } else if (!prefixcmp(next, "%(blob)")) {
2595 value = ref_blob;
2597 } else {
2598 report("Unknown replacement: `%s`", next);
2599 return FALSE;
2600 }
2602 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2603 return FALSE;
2605 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2606 }
2608 dst_argv[argc] = strdup(buf);
2609 if (!dst_argv[argc])
2610 break;
2611 }
2613 dst_argv[argc] = NULL;
2615 return src_argv[argc] == NULL;
2616 }
2618 static bool
2619 restore_view_position(struct view *view)
2620 {
2621 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2622 return FALSE;
2624 /* Changing the view position cancels the restoring. */
2625 /* FIXME: Changing back to the first line is not detected. */
2626 if (view->offset != 0 || view->lineno != 0) {
2627 view->p_restore = FALSE;
2628 return FALSE;
2629 }
2631 if (view->p_lineno >= view->lines) {
2632 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2633 if (view->p_offset >= view->p_lineno) {
2634 unsigned long half = view->height / 2;
2636 if (view->p_lineno > half)
2637 view->p_offset = view->p_lineno - half;
2638 else
2639 view->p_offset = 0;
2640 }
2641 }
2643 if (view_is_displayed(view) &&
2644 view->offset != view->p_offset &&
2645 view->lineno != view->p_lineno)
2646 werase(view->win);
2648 view->offset = view->p_offset;
2649 view->lineno = view->p_lineno;
2650 view->p_restore = FALSE;
2652 return TRUE;
2653 }
2655 static void
2656 end_update(struct view *view, bool force)
2657 {
2658 if (!view->pipe)
2659 return;
2660 while (!view->ops->read(view, NULL))
2661 if (!force)
2662 return;
2663 set_nonblocking_input(FALSE);
2664 if (force)
2665 kill_io(view->pipe);
2666 done_io(view->pipe);
2667 view->pipe = NULL;
2668 }
2670 static void
2671 setup_update(struct view *view, const char *vid)
2672 {
2673 set_nonblocking_input(TRUE);
2674 reset_view(view);
2675 string_copy_rev(view->vid, vid);
2676 view->pipe = &view->io;
2677 view->start_time = time(NULL);
2678 }
2680 static bool
2681 prepare_update(struct view *view, const char *argv[], const char *dir,
2682 enum format_flags flags)
2683 {
2684 if (view->pipe)
2685 end_update(view, TRUE);
2686 return init_io_rd(&view->io, argv, dir, flags);
2687 }
2689 static bool
2690 prepare_update_file(struct view *view, const char *name)
2691 {
2692 if (view->pipe)
2693 end_update(view, TRUE);
2694 return io_open(&view->io, name);
2695 }
2697 static bool
2698 begin_update(struct view *view, bool refresh)
2699 {
2700 if (view->pipe)
2701 end_update(view, TRUE);
2703 if (refresh) {
2704 if (!start_io(&view->io))
2705 return FALSE;
2707 } else {
2708 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2709 opt_path[0] = 0;
2711 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2712 return FALSE;
2714 /* Put the current ref_* value to the view title ref
2715 * member. This is needed by the blob view. Most other
2716 * views sets it automatically after loading because the
2717 * first line is a commit line. */
2718 string_copy_rev(view->ref, view->id);
2719 }
2721 setup_update(view, view->id);
2723 return TRUE;
2724 }
2726 #define ITEM_CHUNK_SIZE 256
2727 static void *
2728 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2729 {
2730 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2731 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2733 if (mem == NULL || num_chunks != num_chunks_new) {
2734 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2735 mem = realloc(mem, *size * item_size);
2736 }
2738 return mem;
2739 }
2741 static struct line *
2742 realloc_lines(struct view *view, size_t line_size)
2743 {
2744 size_t alloc = view->line_alloc;
2745 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2746 sizeof(*view->line));
2748 if (!tmp)
2749 return NULL;
2751 view->line = tmp;
2752 view->line_alloc = alloc;
2753 return view->line;
2754 }
2756 static bool
2757 update_view(struct view *view)
2758 {
2759 char out_buffer[BUFSIZ * 2];
2760 char *line;
2761 /* Clear the view and redraw everything since the tree sorting
2762 * might have rearranged things. */
2763 bool redraw = view->lines == 0;
2764 bool can_read = TRUE;
2766 if (!view->pipe)
2767 return TRUE;
2769 if (!io_can_read(view->pipe)) {
2770 if (view->lines == 0) {
2771 time_t secs = time(NULL) - view->start_time;
2773 if (secs > view->update_secs) {
2774 if (view->update_secs == 0)
2775 redraw_view(view);
2776 update_view_title(view);
2777 view->update_secs = secs;
2778 }
2779 }
2780 return TRUE;
2781 }
2783 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2784 if (opt_iconv != ICONV_NONE) {
2785 ICONV_CONST char *inbuf = line;
2786 size_t inlen = strlen(line) + 1;
2788 char *outbuf = out_buffer;
2789 size_t outlen = sizeof(out_buffer);
2791 size_t ret;
2793 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2794 if (ret != (size_t) -1)
2795 line = out_buffer;
2796 }
2798 if (!view->ops->read(view, line)) {
2799 report("Allocation failure");
2800 end_update(view, TRUE);
2801 return FALSE;
2802 }
2803 }
2805 {
2806 unsigned long lines = view->lines;
2807 int digits;
2809 for (digits = 0; lines; digits++)
2810 lines /= 10;
2812 /* Keep the displayed view in sync with line number scaling. */
2813 if (digits != view->digits) {
2814 view->digits = digits;
2815 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2816 redraw = TRUE;
2817 }
2818 }
2820 if (io_error(view->pipe)) {
2821 report("Failed to read: %s", io_strerror(view->pipe));
2822 end_update(view, TRUE);
2824 } else if (io_eof(view->pipe)) {
2825 report("");
2826 end_update(view, FALSE);
2827 }
2829 if (restore_view_position(view))
2830 redraw = TRUE;
2832 if (!view_is_displayed(view))
2833 return TRUE;
2835 if (redraw)
2836 redraw_view_from(view, 0);
2837 else
2838 redraw_view_dirty(view);
2840 /* Update the title _after_ the redraw so that if the redraw picks up a
2841 * commit reference in view->ref it'll be available here. */
2842 update_view_title(view);
2843 return TRUE;
2844 }
2846 static struct line *
2847 add_line_data(struct view *view, void *data, enum line_type type)
2848 {
2849 struct line *line;
2851 if (!realloc_lines(view, view->lines + 1))
2852 return NULL;
2854 line = &view->line[view->lines++];
2855 memset(line, 0, sizeof(*line));
2856 line->type = type;
2857 line->data = data;
2858 line->dirty = 1;
2860 return line;
2861 }
2863 static struct line *
2864 add_line_text(struct view *view, const char *text, enum line_type type)
2865 {
2866 char *data = text ? strdup(text) : NULL;
2868 return data ? add_line_data(view, data, type) : NULL;
2869 }
2871 static struct line *
2872 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2873 {
2874 char buf[SIZEOF_STR];
2875 va_list args;
2877 va_start(args, fmt);
2878 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2879 buf[0] = 0;
2880 va_end(args);
2882 return buf[0] ? add_line_text(view, buf, type) : NULL;
2883 }
2885 /*
2886 * View opening
2887 */
2889 enum open_flags {
2890 OPEN_DEFAULT = 0, /* Use default view switching. */
2891 OPEN_SPLIT = 1, /* Split current view. */
2892 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2893 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2894 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2895 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2896 OPEN_PREPARED = 32, /* Open already prepared command. */
2897 };
2899 static void
2900 open_view(struct view *prev, enum request request, enum open_flags flags)
2901 {
2902 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2903 bool split = !!(flags & OPEN_SPLIT);
2904 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2905 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2906 struct view *view = VIEW(request);
2907 int nviews = displayed_views();
2908 struct view *base_view = display[0];
2910 if (view == prev && nviews == 1 && !reload) {
2911 report("Already in %s view", view->name);
2912 return;
2913 }
2915 if (view->git_dir && !opt_git_dir[0]) {
2916 report("The %s view is disabled in pager view", view->name);
2917 return;
2918 }
2920 if (split) {
2921 display[1] = view;
2922 if (!backgrounded)
2923 current_view = 1;
2924 } else if (!nomaximize) {
2925 /* Maximize the current view. */
2926 memset(display, 0, sizeof(display));
2927 current_view = 0;
2928 display[current_view] = view;
2929 }
2931 /* Resize the view when switching between split- and full-screen,
2932 * or when switching between two different full-screen views. */
2933 if (nviews != displayed_views() ||
2934 (nviews == 1 && base_view != display[0]))
2935 resize_display();
2937 if (view->ops->open) {
2938 if (view->pipe)
2939 end_update(view, TRUE);
2940 if (!view->ops->open(view)) {
2941 report("Failed to load %s view", view->name);
2942 return;
2943 }
2944 restore_view_position(view);
2946 } else if ((reload || strcmp(view->vid, view->id)) &&
2947 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2948 report("Failed to load %s view", view->name);
2949 return;
2950 }
2952 if (split && prev->lineno - prev->offset >= prev->height) {
2953 /* Take the title line into account. */
2954 int lines = prev->lineno - prev->offset - prev->height + 1;
2956 /* Scroll the view that was split if the current line is
2957 * outside the new limited view. */
2958 do_scroll_view(prev, lines);
2959 }
2961 if (prev && view != prev) {
2962 if (split && !backgrounded) {
2963 /* "Blur" the previous view. */
2964 update_view_title(prev);
2965 }
2967 view->parent = prev;
2968 }
2970 if (view->pipe && view->lines == 0) {
2971 /* Clear the old view and let the incremental updating refill
2972 * the screen. */
2973 werase(view->win);
2974 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2975 report("");
2976 } else if (view_is_displayed(view)) {
2977 redraw_view(view);
2978 report("");
2979 }
2981 /* If the view is backgrounded the above calls to report()
2982 * won't redraw the view title. */
2983 if (backgrounded)
2984 update_view_title(view);
2985 }
2987 static void
2988 open_external_viewer(const char *argv[], const char *dir)
2989 {
2990 def_prog_mode(); /* save current tty modes */
2991 endwin(); /* restore original tty modes */
2992 run_io_fg(argv, dir);
2993 fprintf(stderr, "Press Enter to continue");
2994 getc(opt_tty);
2995 reset_prog_mode();
2996 redraw_display(TRUE);
2997 }
2999 static void
3000 open_mergetool(const char *file)
3001 {
3002 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3004 open_external_viewer(mergetool_argv, opt_cdup);
3005 }
3007 static void
3008 open_editor(bool from_root, const char *file)
3009 {
3010 const char *editor_argv[] = { "vi", file, NULL };
3011 const char *editor;
3013 editor = getenv("GIT_EDITOR");
3014 if (!editor && *opt_editor)
3015 editor = opt_editor;
3016 if (!editor)
3017 editor = getenv("VISUAL");
3018 if (!editor)
3019 editor = getenv("EDITOR");
3020 if (!editor)
3021 editor = "vi";
3023 editor_argv[0] = editor;
3024 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3025 }
3027 static void
3028 open_run_request(enum request request)
3029 {
3030 struct run_request *req = get_run_request(request);
3031 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3033 if (!req) {
3034 report("Unknown run request");
3035 return;
3036 }
3038 if (format_argv(argv, req->argv, FORMAT_ALL))
3039 open_external_viewer(argv, NULL);
3040 free_argv(argv);
3041 }
3043 /*
3044 * User request switch noodle
3045 */
3047 static int
3048 view_driver(struct view *view, enum request request)
3049 {
3050 int i;
3052 if (request == REQ_NONE) {
3053 doupdate();
3054 return TRUE;
3055 }
3057 if (request > REQ_NONE) {
3058 open_run_request(request);
3059 /* FIXME: When all views can refresh always do this. */
3060 if (view == VIEW(REQ_VIEW_STATUS) ||
3061 view == VIEW(REQ_VIEW_MAIN) ||
3062 view == VIEW(REQ_VIEW_LOG) ||
3063 view == VIEW(REQ_VIEW_STAGE))
3064 request = REQ_REFRESH;
3065 else
3066 return TRUE;
3067 }
3069 if (view && view->lines) {
3070 request = view->ops->request(view, request, &view->line[view->lineno]);
3071 if (request == REQ_NONE)
3072 return TRUE;
3073 }
3075 switch (request) {
3076 case REQ_MOVE_UP:
3077 case REQ_MOVE_DOWN:
3078 case REQ_MOVE_PAGE_UP:
3079 case REQ_MOVE_PAGE_DOWN:
3080 case REQ_MOVE_FIRST_LINE:
3081 case REQ_MOVE_LAST_LINE:
3082 move_view(view, request);
3083 break;
3085 case REQ_SCROLL_LINE_DOWN:
3086 case REQ_SCROLL_LINE_UP:
3087 case REQ_SCROLL_PAGE_DOWN:
3088 case REQ_SCROLL_PAGE_UP:
3089 scroll_view(view, request);
3090 break;
3092 case REQ_VIEW_BLAME:
3093 if (!opt_file[0]) {
3094 report("No file chosen, press %s to open tree view",
3095 get_key(REQ_VIEW_TREE));
3096 break;
3097 }
3098 open_view(view, request, OPEN_DEFAULT);
3099 break;
3101 case REQ_VIEW_BLOB:
3102 if (!ref_blob[0]) {
3103 report("No file chosen, press %s to open tree view",
3104 get_key(REQ_VIEW_TREE));
3105 break;
3106 }
3107 open_view(view, request, OPEN_DEFAULT);
3108 break;
3110 case REQ_VIEW_PAGER:
3111 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3112 report("No pager content, press %s to run command from prompt",
3113 get_key(REQ_PROMPT));
3114 break;
3115 }
3116 open_view(view, request, OPEN_DEFAULT);
3117 break;
3119 case REQ_VIEW_STAGE:
3120 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3121 report("No stage content, press %s to open the status view and choose file",
3122 get_key(REQ_VIEW_STATUS));
3123 break;
3124 }
3125 open_view(view, request, OPEN_DEFAULT);
3126 break;
3128 case REQ_VIEW_STATUS:
3129 if (opt_is_inside_work_tree == FALSE) {
3130 report("The status view requires a working tree");
3131 break;
3132 }
3133 open_view(view, request, OPEN_DEFAULT);
3134 break;
3136 case REQ_VIEW_MAIN:
3137 case REQ_VIEW_DIFF:
3138 case REQ_VIEW_LOG:
3139 case REQ_VIEW_TREE:
3140 case REQ_VIEW_HELP:
3141 open_view(view, request, OPEN_DEFAULT);
3142 break;
3144 case REQ_NEXT:
3145 case REQ_PREVIOUS:
3146 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3148 if ((view == VIEW(REQ_VIEW_DIFF) &&
3149 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3150 (view == VIEW(REQ_VIEW_DIFF) &&
3151 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3152 (view == VIEW(REQ_VIEW_STAGE) &&
3153 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3154 (view == VIEW(REQ_VIEW_BLOB) &&
3155 view->parent == VIEW(REQ_VIEW_TREE))) {
3156 int line;
3158 view = view->parent;
3159 line = view->lineno;
3160 move_view(view, request);
3161 if (view_is_displayed(view))
3162 update_view_title(view);
3163 if (line != view->lineno)
3164 view->ops->request(view, REQ_ENTER,
3165 &view->line[view->lineno]);
3167 } else {
3168 move_view(view, request);
3169 }
3170 break;
3172 case REQ_VIEW_NEXT:
3173 {
3174 int nviews = displayed_views();
3175 int next_view = (current_view + 1) % nviews;
3177 if (next_view == current_view) {
3178 report("Only one view is displayed");
3179 break;
3180 }
3182 current_view = next_view;
3183 /* Blur out the title of the previous view. */
3184 update_view_title(view);
3185 report("");
3186 break;
3187 }
3188 case REQ_REFRESH:
3189 report("Refreshing is not yet supported for the %s view", view->name);
3190 break;
3192 case REQ_MAXIMIZE:
3193 if (displayed_views() == 2)
3194 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3195 break;
3197 case REQ_TOGGLE_LINENO:
3198 toggle_view_option(&opt_line_number, "line numbers");
3199 break;
3201 case REQ_TOGGLE_DATE:
3202 toggle_view_option(&opt_date, "date display");
3203 break;
3205 case REQ_TOGGLE_AUTHOR:
3206 toggle_view_option(&opt_author, "author display");
3207 break;
3209 case REQ_TOGGLE_REV_GRAPH:
3210 toggle_view_option(&opt_rev_graph, "revision graph display");
3211 break;
3213 case REQ_TOGGLE_REFS:
3214 toggle_view_option(&opt_show_refs, "reference display");
3215 break;
3217 case REQ_SEARCH:
3218 case REQ_SEARCH_BACK:
3219 search_view(view, request);
3220 break;
3222 case REQ_FIND_NEXT:
3223 case REQ_FIND_PREV:
3224 find_next(view, request);
3225 break;
3227 case REQ_STOP_LOADING:
3228 for (i = 0; i < ARRAY_SIZE(views); i++) {
3229 view = &views[i];
3230 if (view->pipe)
3231 report("Stopped loading the %s view", view->name),
3232 end_update(view, TRUE);
3233 }
3234 break;
3236 case REQ_SHOW_VERSION:
3237 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3238 return TRUE;
3240 case REQ_SCREEN_REDRAW:
3241 redraw_display(TRUE);
3242 break;
3244 case REQ_EDIT:
3245 report("Nothing to edit");
3246 break;
3248 case REQ_ENTER:
3249 report("Nothing to enter");
3250 break;
3252 case REQ_VIEW_CLOSE:
3253 /* XXX: Mark closed views by letting view->parent point to the
3254 * view itself. Parents to closed view should never be
3255 * followed. */
3256 if (view->parent &&
3257 view->parent->parent != view->parent) {
3258 memset(display, 0, sizeof(display));
3259 current_view = 0;
3260 display[current_view] = view->parent;
3261 view->parent = view;
3262 resize_display();
3263 redraw_display(FALSE);
3264 report("");
3265 break;
3266 }
3267 /* Fall-through */
3268 case REQ_QUIT:
3269 return FALSE;
3271 default:
3272 report("Unknown key, press 'h' for help");
3273 return TRUE;
3274 }
3276 return TRUE;
3277 }
3280 /*
3281 * View backend utilities
3282 */
3284 /* Parse author lines where the name may be empty:
3285 * author <email@address.tld> 1138474660 +0100
3286 */
3287 static void
3288 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3289 {
3290 char *nameend = strchr(ident, '<');
3291 char *emailend = strchr(ident, '>');
3293 if (nameend && emailend)
3294 *nameend = *emailend = 0;
3295 ident = chomp_string(ident);
3296 if (!*ident) {
3297 if (nameend)
3298 ident = chomp_string(nameend + 1);
3299 if (!*ident)
3300 ident = "Unknown";
3301 }
3303 string_ncopy_do(author, authorsize, ident, strlen(ident));
3305 /* Parse epoch and timezone */
3306 if (emailend && emailend[1] == ' ') {
3307 char *secs = emailend + 2;
3308 char *zone = strchr(secs, ' ');
3309 time_t time = (time_t) atol(secs);
3311 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3312 long tz;
3314 zone++;
3315 tz = ('0' - zone[1]) * 60 * 60 * 10;
3316 tz += ('0' - zone[2]) * 60 * 60;
3317 tz += ('0' - zone[3]) * 60;
3318 tz += ('0' - zone[4]) * 60;
3320 if (zone[0] == '-')
3321 tz = -tz;
3323 time -= tz;
3324 }
3326 gmtime_r(&time, tm);
3327 }
3328 }
3330 /*
3331 * Pager backend
3332 */
3334 static bool
3335 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3336 {
3337 char *text = line->data;
3339 if (opt_line_number && draw_lineno(view, lineno))
3340 return TRUE;
3342 draw_text(view, line->type, text, TRUE);
3343 return TRUE;
3344 }
3346 static bool
3347 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3348 {
3349 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3350 char refbuf[SIZEOF_STR];
3351 char *ref = NULL;
3353 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3354 ref = chomp_string(refbuf);
3356 if (!ref || !*ref)
3357 return TRUE;
3359 /* This is the only fatal call, since it can "corrupt" the buffer. */
3360 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3361 return FALSE;
3363 return TRUE;
3364 }
3366 static void
3367 add_pager_refs(struct view *view, struct line *line)
3368 {
3369 char buf[SIZEOF_STR];
3370 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3371 struct ref **refs;
3372 size_t bufpos = 0, refpos = 0;
3373 const char *sep = "Refs: ";
3374 bool is_tag = FALSE;
3376 assert(line->type == LINE_COMMIT);
3378 refs = get_refs(commit_id);
3379 if (!refs) {
3380 if (view == VIEW(REQ_VIEW_DIFF))
3381 goto try_add_describe_ref;
3382 return;
3383 }
3385 do {
3386 struct ref *ref = refs[refpos];
3387 const char *fmt = ref->tag ? "%s[%s]" :
3388 ref->remote ? "%s<%s>" : "%s%s";
3390 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3391 return;
3392 sep = ", ";
3393 if (ref->tag)
3394 is_tag = TRUE;
3395 } while (refs[refpos++]->next);
3397 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3398 try_add_describe_ref:
3399 /* Add <tag>-g<commit_id> "fake" reference. */
3400 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3401 return;
3402 }
3404 if (bufpos == 0)
3405 return;
3407 add_line_text(view, buf, LINE_PP_REFS);
3408 }
3410 static bool
3411 pager_read(struct view *view, char *data)
3412 {
3413 struct line *line;
3415 if (!data)
3416 return TRUE;
3418 line = add_line_text(view, data, get_line_type(data));
3419 if (!line)
3420 return FALSE;
3422 if (line->type == LINE_COMMIT &&
3423 (view == VIEW(REQ_VIEW_DIFF) ||
3424 view == VIEW(REQ_VIEW_LOG)))
3425 add_pager_refs(view, line);
3427 return TRUE;
3428 }
3430 static enum request
3431 pager_request(struct view *view, enum request request, struct line *line)
3432 {
3433 int split = 0;
3435 if (request != REQ_ENTER)
3436 return request;
3438 if (line->type == LINE_COMMIT &&
3439 (view == VIEW(REQ_VIEW_LOG) ||
3440 view == VIEW(REQ_VIEW_PAGER))) {
3441 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3442 split = 1;
3443 }
3445 /* Always scroll the view even if it was split. That way
3446 * you can use Enter to scroll through the log view and
3447 * split open each commit diff. */
3448 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3450 /* FIXME: A minor workaround. Scrolling the view will call report("")
3451 * but if we are scrolling a non-current view this won't properly
3452 * update the view title. */
3453 if (split)
3454 update_view_title(view);
3456 return REQ_NONE;
3457 }
3459 static bool
3460 pager_grep(struct view *view, struct line *line)
3461 {
3462 regmatch_t pmatch;
3463 char *text = line->data;
3465 if (!*text)
3466 return FALSE;
3468 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3469 return FALSE;
3471 return TRUE;
3472 }
3474 static void
3475 pager_select(struct view *view, struct line *line)
3476 {
3477 if (line->type == LINE_COMMIT) {
3478 char *text = (char *)line->data + STRING_SIZE("commit ");
3480 if (view != VIEW(REQ_VIEW_PAGER))
3481 string_copy_rev(view->ref, text);
3482 string_copy_rev(ref_commit, text);
3483 }
3484 }
3486 static struct view_ops pager_ops = {
3487 "line",
3488 NULL,
3489 NULL,
3490 pager_read,
3491 pager_draw,
3492 pager_request,
3493 pager_grep,
3494 pager_select,
3495 };
3497 static const char *log_argv[SIZEOF_ARG] = {
3498 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3499 };
3501 static enum request
3502 log_request(struct view *view, enum request request, struct line *line)
3503 {
3504 switch (request) {
3505 case REQ_REFRESH:
3506 load_refs();
3507 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3508 return REQ_NONE;
3509 default:
3510 return pager_request(view, request, line);
3511 }
3512 }
3514 static struct view_ops log_ops = {
3515 "line",
3516 log_argv,
3517 NULL,
3518 pager_read,
3519 pager_draw,
3520 log_request,
3521 pager_grep,
3522 pager_select,
3523 };
3525 static const char *diff_argv[SIZEOF_ARG] = {
3526 "git", "show", "--pretty=fuller", "--no-color", "--root",
3527 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3528 };
3530 static struct view_ops diff_ops = {
3531 "line",
3532 diff_argv,
3533 NULL,
3534 pager_read,
3535 pager_draw,
3536 pager_request,
3537 pager_grep,
3538 pager_select,
3539 };
3541 /*
3542 * Help backend
3543 */
3545 static bool
3546 help_open(struct view *view)
3547 {
3548 int lines = ARRAY_SIZE(req_info) + 2;
3549 int i;
3551 if (view->lines > 0)
3552 return TRUE;
3554 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3555 if (!req_info[i].request)
3556 lines++;
3558 lines += run_requests + 1;
3560 view->line = calloc(lines, sizeof(*view->line));
3561 if (!view->line)
3562 return FALSE;
3564 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3566 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3567 const char *key;
3569 if (req_info[i].request == REQ_NONE)
3570 continue;
3572 if (!req_info[i].request) {
3573 add_line_text(view, "", LINE_DEFAULT);
3574 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3575 continue;
3576 }
3578 key = get_key(req_info[i].request);
3579 if (!*key)
3580 key = "(no key defined)";
3582 add_line_format(view, LINE_DEFAULT, " %-25s %s",
3583 key, req_info[i].help);
3584 }
3586 if (run_requests) {
3587 add_line_text(view, "", LINE_DEFAULT);
3588 add_line_text(view, "External commands:", LINE_DEFAULT);
3589 }
3591 for (i = 0; i < run_requests; i++) {
3592 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3593 const char *key;
3594 char cmd[SIZEOF_STR];
3595 size_t bufpos;
3596 int argc;
3598 if (!req)
3599 continue;
3601 key = get_key_name(req->key);
3602 if (!*key)
3603 key = "(no key defined)";
3605 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3606 if (!string_format_from(cmd, &bufpos, "%s%s",
3607 argc ? " " : "", req->argv[argc]))
3608 return REQ_NONE;
3610 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3611 keymap_table[req->keymap].name, key, cmd);
3612 }
3614 return TRUE;
3615 }
3617 static struct view_ops help_ops = {
3618 "line",
3619 NULL,
3620 help_open,
3621 NULL,
3622 pager_draw,
3623 pager_request,
3624 pager_grep,
3625 pager_select,
3626 };
3629 /*
3630 * Tree backend
3631 */
3633 struct tree_stack_entry {
3634 struct tree_stack_entry *prev; /* Entry below this in the stack */
3635 unsigned long lineno; /* Line number to restore */
3636 char *name; /* Position of name in opt_path */
3637 };
3639 /* The top of the path stack. */
3640 static struct tree_stack_entry *tree_stack = NULL;
3641 unsigned long tree_lineno = 0;
3643 static void
3644 pop_tree_stack_entry(void)
3645 {
3646 struct tree_stack_entry *entry = tree_stack;
3648 tree_lineno = entry->lineno;
3649 entry->name[0] = 0;
3650 tree_stack = entry->prev;
3651 free(entry);
3652 }
3654 static void
3655 push_tree_stack_entry(const char *name, unsigned long lineno)
3656 {
3657 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3658 size_t pathlen = strlen(opt_path);
3660 if (!entry)
3661 return;
3663 entry->prev = tree_stack;
3664 entry->name = opt_path + pathlen;
3665 tree_stack = entry;
3667 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3668 pop_tree_stack_entry();
3669 return;
3670 }
3672 /* Move the current line to the first tree entry. */
3673 tree_lineno = 1;
3674 entry->lineno = lineno;
3675 }
3677 /* Parse output from git-ls-tree(1):
3678 *
3679 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3680 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3681 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3682 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3683 */
3685 #define SIZEOF_TREE_ATTR \
3686 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3688 #define SIZEOF_TREE_MODE \
3689 STRING_SIZE("100644 ")
3691 #define TREE_ID_OFFSET \
3692 STRING_SIZE("100644 blob ")
3694 struct tree_entry {
3695 char id[SIZEOF_REV];
3696 mode_t mode;
3697 struct tm time; /* Date from the author ident. */
3698 char author[75]; /* Author of the commit. */
3699 char name[1];
3700 };
3702 static const char *
3703 tree_path(struct line *line)
3704 {
3705 return ((struct tree_entry *) line->data)->name;
3706 }
3709 static int
3710 tree_compare_entry(struct line *line1, struct line *line2)
3711 {
3712 if (line1->type != line2->type)
3713 return line1->type == LINE_TREE_DIR ? -1 : 1;
3714 return strcmp(tree_path(line1), tree_path(line2));
3715 }
3717 static struct line *
3718 tree_entry(struct view *view, enum line_type type, const char *path,
3719 const char *mode, const char *id)
3720 {
3721 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3722 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3724 if (!entry || !line) {
3725 free(entry);
3726 return NULL;
3727 }
3729 strncpy(entry->name, path, strlen(path));
3730 if (mode)
3731 entry->mode = strtoul(mode, NULL, 8);
3732 if (id)
3733 string_copy_rev(entry->id, id);
3735 return line;
3736 }
3738 static bool
3739 tree_read_date(struct view *view, char *text, bool *read_date)
3740 {
3741 static char author_name[SIZEOF_STR];
3742 static struct tm author_time;
3744 if (!text && *read_date) {
3745 *read_date = FALSE;
3746 return TRUE;
3748 } else if (!text) {
3749 char *path = *opt_path ? opt_path : ".";
3750 /* Find next entry to process */
3751 const char *log_file[] = {
3752 "git", "log", "--no-color", "--pretty=raw",
3753 "--cc", "--raw", view->id, "--", path, NULL
3754 };
3755 struct io io = {};
3757 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3758 report("Failed to load tree data");
3759 return TRUE;
3760 }
3762 done_io(view->pipe);
3763 view->io = io;
3764 *read_date = TRUE;
3765 return FALSE;
3767 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3768 parse_author_line(text + STRING_SIZE("author "),
3769 author_name, sizeof(author_name), &author_time);
3771 } else if (*text == ':') {
3772 char *pos;
3773 size_t annotated = 1;
3774 size_t i;
3776 pos = strchr(text, '\t');
3777 if (!pos)
3778 return TRUE;
3779 text = pos + 1;
3780 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3781 text += strlen(opt_prefix);
3782 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3783 text += strlen(opt_path);
3784 pos = strchr(text, '/');
3785 if (pos)
3786 *pos = 0;
3788 for (i = 1; i < view->lines; i++) {
3789 struct line *line = &view->line[i];
3790 struct tree_entry *entry = line->data;
3792 annotated += !!*entry->author;
3793 if (*entry->author || strcmp(entry->name, text))
3794 continue;
3796 string_copy(entry->author, author_name);
3797 memcpy(&entry->time, &author_time, sizeof(entry->time));
3798 line->dirty = 1;
3799 break;
3800 }
3802 if (annotated == view->lines)
3803 kill_io(view->pipe);
3804 }
3805 return TRUE;
3806 }
3808 static bool
3809 tree_read(struct view *view, char *text)
3810 {
3811 static bool read_date = FALSE;
3812 struct tree_entry *data;
3813 struct line *entry, *line;
3814 enum line_type type;
3815 size_t textlen = text ? strlen(text) : 0;
3816 char *path = text + SIZEOF_TREE_ATTR;
3818 if (read_date || !text)
3819 return tree_read_date(view, text, &read_date);
3821 if (textlen <= SIZEOF_TREE_ATTR)
3822 return FALSE;
3823 if (view->lines == 0 &&
3824 !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3825 return FALSE;
3827 /* Strip the path part ... */
3828 if (*opt_path) {
3829 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3830 size_t striplen = strlen(opt_path);
3832 if (pathlen > striplen)
3833 memmove(path, path + striplen,
3834 pathlen - striplen + 1);
3836 /* Insert "link" to parent directory. */
3837 if (view->lines == 1 &&
3838 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3839 return FALSE;
3840 }
3842 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3843 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3844 if (!entry)
3845 return FALSE;
3846 data = entry->data;
3848 /* Skip "Directory ..." and ".." line. */
3849 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3850 if (tree_compare_entry(line, entry) <= 0)
3851 continue;
3853 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3855 line->data = data;
3856 line->type = type;
3857 for (; line <= entry; line++)
3858 line->dirty = line->cleareol = 1;
3859 return TRUE;
3860 }
3862 if (tree_lineno > view->lineno) {
3863 view->lineno = tree_lineno;
3864 tree_lineno = 0;
3865 }
3867 return TRUE;
3868 }
3870 static bool
3871 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3872 {
3873 struct tree_entry *entry = line->data;
3875 if (line->type == LINE_TREE_PARENT) {
3876 if (draw_text(view, line->type, "Directory path /", TRUE))
3877 return TRUE;
3878 } else {
3879 char mode[11] = "-r--r--r--";
3881 if (S_ISDIR(entry->mode)) {
3882 mode[3] = mode[6] = mode[9] = 'x';
3883 mode[0] = 'd';
3884 }
3885 if (S_ISLNK(entry->mode))
3886 mode[0] = 'l';
3887 if (entry->mode & S_IWUSR)
3888 mode[2] = 'w';
3889 if (entry->mode & S_IXUSR)
3890 mode[3] = 'x';
3891 if (entry->mode & S_IXGRP)
3892 mode[6] = 'x';
3893 if (entry->mode & S_IXOTH)
3894 mode[9] = 'x';
3895 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3896 return TRUE;
3898 if (opt_author &&
3899 draw_field(view, LINE_MAIN_AUTHOR, entry->author, opt_author_cols, TRUE))
3900 return TRUE;
3902 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3903 return TRUE;
3904 }
3905 if (draw_text(view, line->type, entry->name, TRUE))
3906 return TRUE;
3907 return TRUE;
3908 }
3910 static void
3911 open_blob_editor()
3912 {
3913 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3914 int fd = mkstemp(file);
3916 if (fd == -1)
3917 report("Failed to create temporary file");
3918 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3919 report("Failed to save blob data to file");
3920 else
3921 open_editor(FALSE, file);
3922 if (fd != -1)
3923 unlink(file);
3924 }
3926 static enum request
3927 tree_request(struct view *view, enum request request, struct line *line)
3928 {
3929 enum open_flags flags;
3931 switch (request) {
3932 case REQ_VIEW_BLAME:
3933 if (line->type != LINE_TREE_FILE) {
3934 report("Blame only supported for files");
3935 return REQ_NONE;
3936 }
3938 string_copy(opt_ref, view->vid);
3939 return request;
3941 case REQ_EDIT:
3942 if (line->type != LINE_TREE_FILE) {
3943 report("Edit only supported for files");
3944 } else if (!is_head_commit(view->vid)) {
3945 open_blob_editor();
3946 } else {
3947 open_editor(TRUE, opt_file);
3948 }
3949 return REQ_NONE;
3951 case REQ_PARENT:
3952 if (!*opt_path) {
3953 /* quit view if at top of tree */
3954 return REQ_VIEW_CLOSE;
3955 }
3956 /* fake 'cd ..' */
3957 line = &view->line[1];
3958 break;
3960 case REQ_ENTER:
3961 break;
3963 default:
3964 return request;
3965 }
3967 /* Cleanup the stack if the tree view is at a different tree. */
3968 while (!*opt_path && tree_stack)
3969 pop_tree_stack_entry();
3971 switch (line->type) {
3972 case LINE_TREE_DIR:
3973 /* Depending on whether it is a subdir or parent (updir?) link
3974 * mangle the path buffer. */
3975 if (line == &view->line[1] && *opt_path) {
3976 pop_tree_stack_entry();
3978 } else {
3979 const char *basename = tree_path(line);
3981 push_tree_stack_entry(basename, view->lineno);
3982 }
3984 /* Trees and subtrees share the same ID, so they are not not
3985 * unique like blobs. */
3986 flags = OPEN_RELOAD;
3987 request = REQ_VIEW_TREE;
3988 break;
3990 case LINE_TREE_FILE:
3991 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3992 request = REQ_VIEW_BLOB;
3993 break;
3995 default:
3996 return REQ_NONE;
3997 }
3999 open_view(view, request, flags);
4000 if (request == REQ_VIEW_TREE)
4001 view->lineno = tree_lineno;
4003 return REQ_NONE;
4004 }
4006 static void
4007 tree_select(struct view *view, struct line *line)
4008 {
4009 struct tree_entry *entry = line->data;
4011 if (line->type == LINE_TREE_FILE) {
4012 string_copy_rev(ref_blob, entry->id);
4013 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4015 } else if (line->type != LINE_TREE_DIR) {
4016 return;
4017 }
4019 string_copy_rev(view->ref, entry->id);
4020 }
4022 static const char *tree_argv[SIZEOF_ARG] = {
4023 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4024 };
4026 static struct view_ops tree_ops = {
4027 "file",
4028 tree_argv,
4029 NULL,
4030 tree_read,
4031 tree_draw,
4032 tree_request,
4033 pager_grep,
4034 tree_select,
4035 };
4037 static bool
4038 blob_read(struct view *view, char *line)
4039 {
4040 if (!line)
4041 return TRUE;
4042 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4043 }
4045 static enum request
4046 blob_request(struct view *view, enum request request, struct line *line)
4047 {
4048 switch (request) {
4049 case REQ_EDIT:
4050 open_blob_editor();
4051 return REQ_NONE;
4052 default:
4053 return pager_request(view, request, line);
4054 }
4055 }
4057 static const char *blob_argv[SIZEOF_ARG] = {
4058 "git", "cat-file", "blob", "%(blob)", NULL
4059 };
4061 static struct view_ops blob_ops = {
4062 "line",
4063 blob_argv,
4064 NULL,
4065 blob_read,
4066 pager_draw,
4067 blob_request,
4068 pager_grep,
4069 pager_select,
4070 };
4072 /*
4073 * Blame backend
4074 *
4075 * Loading the blame view is a two phase job:
4076 *
4077 * 1. File content is read either using opt_file from the
4078 * filesystem or using git-cat-file.
4079 * 2. Then blame information is incrementally added by
4080 * reading output from git-blame.
4081 */
4083 static const char *blame_head_argv[] = {
4084 "git", "blame", "--incremental", "--", "%(file)", NULL
4085 };
4087 static const char *blame_ref_argv[] = {
4088 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4089 };
4091 static const char *blame_cat_file_argv[] = {
4092 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4093 };
4095 struct blame_commit {
4096 char id[SIZEOF_REV]; /* SHA1 ID. */
4097 char title[128]; /* First line of the commit message. */
4098 char author[75]; /* Author of the commit. */
4099 struct tm time; /* Date from the author ident. */
4100 char filename[128]; /* Name of file. */
4101 bool has_previous; /* Was a "previous" line detected. */
4102 };
4104 struct blame {
4105 struct blame_commit *commit;
4106 char text[1];
4107 };
4109 static bool
4110 blame_open(struct view *view)
4111 {
4112 if (*opt_ref || !io_open(&view->io, opt_file)) {
4113 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4114 return FALSE;
4115 }
4117 setup_update(view, opt_file);
4118 string_format(view->ref, "%s ...", opt_file);
4120 return TRUE;
4121 }
4123 static struct blame_commit *
4124 get_blame_commit(struct view *view, const char *id)
4125 {
4126 size_t i;
4128 for (i = 0; i < view->lines; i++) {
4129 struct blame *blame = view->line[i].data;
4131 if (!blame->commit)
4132 continue;
4134 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4135 return blame->commit;
4136 }
4138 {
4139 struct blame_commit *commit = calloc(1, sizeof(*commit));
4141 if (commit)
4142 string_ncopy(commit->id, id, SIZEOF_REV);
4143 return commit;
4144 }
4145 }
4147 static bool
4148 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4149 {
4150 const char *pos = *posref;
4152 *posref = NULL;
4153 pos = strchr(pos + 1, ' ');
4154 if (!pos || !isdigit(pos[1]))
4155 return FALSE;
4156 *number = atoi(pos + 1);
4157 if (*number < min || *number > max)
4158 return FALSE;
4160 *posref = pos;
4161 return TRUE;
4162 }
4164 static struct blame_commit *
4165 parse_blame_commit(struct view *view, const char *text, int *blamed)
4166 {
4167 struct blame_commit *commit;
4168 struct blame *blame;
4169 const char *pos = text + SIZEOF_REV - 1;
4170 size_t lineno;
4171 size_t group;
4173 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4174 return NULL;
4176 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4177 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4178 return NULL;
4180 commit = get_blame_commit(view, text);
4181 if (!commit)
4182 return NULL;
4184 *blamed += group;
4185 while (group--) {
4186 struct line *line = &view->line[lineno + group - 1];
4188 blame = line->data;
4189 blame->commit = commit;
4190 line->dirty = 1;
4191 }
4193 return commit;
4194 }
4196 static bool
4197 blame_read_file(struct view *view, const char *line, bool *read_file)
4198 {
4199 if (!line) {
4200 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4201 struct io io = {};
4203 if (view->lines == 0 && !view->parent)
4204 die("No blame exist for %s", view->vid);
4206 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4207 report("Failed to load blame data");
4208 return TRUE;
4209 }
4211 done_io(view->pipe);
4212 view->io = io;
4213 *read_file = FALSE;
4214 return FALSE;
4216 } else {
4217 size_t linelen = strlen(line);
4218 struct blame *blame = malloc(sizeof(*blame) + linelen);
4220 blame->commit = NULL;
4221 strncpy(blame->text, line, linelen);
4222 blame->text[linelen] = 0;
4223 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4224 }
4225 }
4227 static bool
4228 match_blame_header(const char *name, char **line)
4229 {
4230 size_t namelen = strlen(name);
4231 bool matched = !strncmp(name, *line, namelen);
4233 if (matched)
4234 *line += namelen;
4236 return matched;
4237 }
4239 static bool
4240 blame_read(struct view *view, char *line)
4241 {
4242 static struct blame_commit *commit = NULL;
4243 static int blamed = 0;
4244 static time_t author_time;
4245 static bool read_file = TRUE;
4247 if (read_file)
4248 return blame_read_file(view, line, &read_file);
4250 if (!line) {
4251 /* Reset all! */
4252 commit = NULL;
4253 blamed = 0;
4254 read_file = TRUE;
4255 string_format(view->ref, "%s", view->vid);
4256 if (view_is_displayed(view)) {
4257 update_view_title(view);
4258 redraw_view_from(view, 0);
4259 }
4260 return TRUE;
4261 }
4263 if (!commit) {
4264 commit = parse_blame_commit(view, line, &blamed);
4265 string_format(view->ref, "%s %2d%%", view->vid,
4266 view->lines ? blamed * 100 / view->lines : 0);
4268 } else if (match_blame_header("author ", &line)) {
4269 string_ncopy(commit->author, line, strlen(line));
4271 } else if (match_blame_header("author-time ", &line)) {
4272 author_time = (time_t) atol(line);
4274 } else if (match_blame_header("author-tz ", &line)) {
4275 long tz;
4277 tz = ('0' - line[1]) * 60 * 60 * 10;
4278 tz += ('0' - line[2]) * 60 * 60;
4279 tz += ('0' - line[3]) * 60;
4280 tz += ('0' - line[4]) * 60;
4282 if (line[0] == '-')
4283 tz = -tz;
4285 author_time -= tz;
4286 gmtime_r(&author_time, &commit->time);
4288 } else if (match_blame_header("summary ", &line)) {
4289 string_ncopy(commit->title, line, strlen(line));
4291 } else if (match_blame_header("previous ", &line)) {
4292 commit->has_previous = TRUE;
4294 } else if (match_blame_header("filename ", &line)) {
4295 string_ncopy(commit->filename, line, strlen(line));
4296 commit = NULL;
4297 }
4299 return TRUE;
4300 }
4302 static bool
4303 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4304 {
4305 struct blame *blame = line->data;
4306 struct tm *time = NULL;
4307 const char *id = NULL, *author = NULL;
4309 if (blame->commit && *blame->commit->filename) {
4310 id = blame->commit->id;
4311 author = blame->commit->author;
4312 time = &blame->commit->time;
4313 }
4315 if (opt_date && draw_date(view, time))
4316 return TRUE;
4318 if (opt_author &&
4319 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4320 return TRUE;
4322 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4323 return TRUE;
4325 if (draw_lineno(view, lineno))
4326 return TRUE;
4328 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4329 return TRUE;
4330 }
4332 static bool
4333 check_blame_commit(struct blame *blame)
4334 {
4335 if (!blame->commit)
4336 report("Commit data not loaded yet");
4337 else if (!strcmp(blame->commit->id, NULL_ID))
4338 report("No commit exist for the selected line");
4339 else
4340 return TRUE;
4341 return FALSE;
4342 }
4344 static enum request
4345 blame_request(struct view *view, enum request request, struct line *line)
4346 {
4347 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4348 struct blame *blame = line->data;
4350 switch (request) {
4351 case REQ_VIEW_BLAME:
4352 if (check_blame_commit(blame)) {
4353 string_copy(opt_ref, blame->commit->id);
4354 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4355 }
4356 break;
4358 case REQ_ENTER:
4359 if (!blame->commit) {
4360 report("No commit loaded yet");
4361 break;
4362 }
4364 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4365 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4366 break;
4368 if (!strcmp(blame->commit->id, NULL_ID)) {
4369 struct view *diff = VIEW(REQ_VIEW_DIFF);
4370 const char *diff_index_argv[] = {
4371 "git", "diff-index", "--root", "--patch-with-stat",
4372 "-C", "-M", "HEAD", "--", view->vid, NULL
4373 };
4375 if (!blame->commit->has_previous) {
4376 diff_index_argv[1] = "diff";
4377 diff_index_argv[2] = "--no-color";
4378 diff_index_argv[6] = "--";
4379 diff_index_argv[7] = "/dev/null";
4380 }
4382 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4383 report("Failed to allocate diff command");
4384 break;
4385 }
4386 flags |= OPEN_PREPARED;
4387 }
4389 open_view(view, REQ_VIEW_DIFF, flags);
4390 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4391 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4392 break;
4394 default:
4395 return request;
4396 }
4398 return REQ_NONE;
4399 }
4401 static bool
4402 blame_grep(struct view *view, struct line *line)
4403 {
4404 struct blame *blame = line->data;
4405 struct blame_commit *commit = blame->commit;
4406 regmatch_t pmatch;
4408 #define MATCH(text, on) \
4409 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4411 if (commit) {
4412 char buf[DATE_COLS + 1];
4414 if (MATCH(commit->title, 1) ||
4415 MATCH(commit->author, opt_author) ||
4416 MATCH(commit->id, opt_date))
4417 return TRUE;
4419 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4420 MATCH(buf, 1))
4421 return TRUE;
4422 }
4424 return MATCH(blame->text, 1);
4426 #undef MATCH
4427 }
4429 static void
4430 blame_select(struct view *view, struct line *line)
4431 {
4432 struct blame *blame = line->data;
4433 struct blame_commit *commit = blame->commit;
4435 if (!commit)
4436 return;
4438 if (!strcmp(commit->id, NULL_ID))
4439 string_ncopy(ref_commit, "HEAD", 4);
4440 else
4441 string_copy_rev(ref_commit, commit->id);
4442 }
4444 static struct view_ops blame_ops = {
4445 "line",
4446 NULL,
4447 blame_open,
4448 blame_read,
4449 blame_draw,
4450 blame_request,
4451 blame_grep,
4452 blame_select,
4453 };
4455 /*
4456 * Status backend
4457 */
4459 struct status {
4460 char status;
4461 struct {
4462 mode_t mode;
4463 char rev[SIZEOF_REV];
4464 char name[SIZEOF_STR];
4465 } old;
4466 struct {
4467 mode_t mode;
4468 char rev[SIZEOF_REV];
4469 char name[SIZEOF_STR];
4470 } new;
4471 };
4473 static char status_onbranch[SIZEOF_STR];
4474 static struct status stage_status;
4475 static enum line_type stage_line_type;
4476 static size_t stage_chunks;
4477 static int *stage_chunk;
4479 /* This should work even for the "On branch" line. */
4480 static inline bool
4481 status_has_none(struct view *view, struct line *line)
4482 {
4483 return line < view->line + view->lines && !line[1].data;
4484 }
4486 /* Get fields from the diff line:
4487 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4488 */
4489 static inline bool
4490 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4491 {
4492 const char *old_mode = buf + 1;
4493 const char *new_mode = buf + 8;
4494 const char *old_rev = buf + 15;
4495 const char *new_rev = buf + 56;
4496 const char *status = buf + 97;
4498 if (bufsize < 98 ||
4499 old_mode[-1] != ':' ||
4500 new_mode[-1] != ' ' ||
4501 old_rev[-1] != ' ' ||
4502 new_rev[-1] != ' ' ||
4503 status[-1] != ' ')
4504 return FALSE;
4506 file->status = *status;
4508 string_copy_rev(file->old.rev, old_rev);
4509 string_copy_rev(file->new.rev, new_rev);
4511 file->old.mode = strtoul(old_mode, NULL, 8);
4512 file->new.mode = strtoul(new_mode, NULL, 8);
4514 file->old.name[0] = file->new.name[0] = 0;
4516 return TRUE;
4517 }
4519 static bool
4520 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4521 {
4522 struct status *file = NULL;
4523 struct status *unmerged = NULL;
4524 char *buf;
4525 struct io io = {};
4527 if (!run_io(&io, argv, NULL, IO_RD))
4528 return FALSE;
4530 add_line_data(view, NULL, type);
4532 while ((buf = io_get(&io, 0, TRUE))) {
4533 if (!file) {
4534 file = calloc(1, sizeof(*file));
4535 if (!file || !add_line_data(view, file, type))
4536 goto error_out;
4537 }
4539 /* Parse diff info part. */
4540 if (status) {
4541 file->status = status;
4542 if (status == 'A')
4543 string_copy(file->old.rev, NULL_ID);
4545 } else if (!file->status) {
4546 if (!status_get_diff(file, buf, strlen(buf)))
4547 goto error_out;
4549 buf = io_get(&io, 0, TRUE);
4550 if (!buf)
4551 break;
4553 /* Collapse all 'M'odified entries that follow a
4554 * associated 'U'nmerged entry. */
4555 if (file->status == 'U') {
4556 unmerged = file;
4558 } else if (unmerged) {
4559 int collapse = !strcmp(buf, unmerged->new.name);
4561 unmerged = NULL;
4562 if (collapse) {
4563 free(file);
4564 file = NULL;
4565 view->lines--;
4566 continue;
4567 }
4568 }
4569 }
4571 /* Grab the old name for rename/copy. */
4572 if (!*file->old.name &&
4573 (file->status == 'R' || file->status == 'C')) {
4574 string_ncopy(file->old.name, buf, strlen(buf));
4576 buf = io_get(&io, 0, TRUE);
4577 if (!buf)
4578 break;
4579 }
4581 /* git-ls-files just delivers a NUL separated list of
4582 * file names similar to the second half of the
4583 * git-diff-* output. */
4584 string_ncopy(file->new.name, buf, strlen(buf));
4585 if (!*file->old.name)
4586 string_copy(file->old.name, file->new.name);
4587 file = NULL;
4588 }
4590 if (io_error(&io)) {
4591 error_out:
4592 done_io(&io);
4593 return FALSE;
4594 }
4596 if (!view->line[view->lines - 1].data)
4597 add_line_data(view, NULL, LINE_STAT_NONE);
4599 done_io(&io);
4600 return TRUE;
4601 }
4603 /* Don't show unmerged entries in the staged section. */
4604 static const char *status_diff_index_argv[] = {
4605 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4606 "--cached", "-M", "HEAD", NULL
4607 };
4609 static const char *status_diff_files_argv[] = {
4610 "git", "diff-files", "-z", NULL
4611 };
4613 static const char *status_list_other_argv[] = {
4614 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4615 };
4617 static const char *status_list_no_head_argv[] = {
4618 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4619 };
4621 static const char *update_index_argv[] = {
4622 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4623 };
4625 /* Restore the previous line number to stay in the context or select a
4626 * line with something that can be updated. */
4627 static void
4628 status_restore(struct view *view)
4629 {
4630 if (view->p_lineno >= view->lines)
4631 view->p_lineno = view->lines - 1;
4632 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4633 view->p_lineno++;
4634 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4635 view->p_lineno--;
4637 /* If the above fails, always skip the "On branch" line. */
4638 if (view->p_lineno < view->lines)
4639 view->lineno = view->p_lineno;
4640 else
4641 view->lineno = 1;
4643 if (view->lineno < view->offset)
4644 view->offset = view->lineno;
4645 else if (view->offset + view->height <= view->lineno)
4646 view->offset = view->lineno - view->height + 1;
4648 view->p_restore = FALSE;
4649 }
4651 /* First parse staged info using git-diff-index(1), then parse unstaged
4652 * info using git-diff-files(1), and finally untracked files using
4653 * git-ls-files(1). */
4654 static bool
4655 status_open(struct view *view)
4656 {
4657 reset_view(view);
4659 add_line_data(view, NULL, LINE_STAT_HEAD);
4660 if (is_initial_commit())
4661 string_copy(status_onbranch, "Initial commit");
4662 else if (!*opt_head)
4663 string_copy(status_onbranch, "Not currently on any branch");
4664 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4665 return FALSE;
4667 run_io_bg(update_index_argv);
4669 if (is_initial_commit()) {
4670 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4671 return FALSE;
4672 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4673 return FALSE;
4674 }
4676 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4677 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4678 return FALSE;
4680 /* Restore the exact position or use the specialized restore
4681 * mode? */
4682 if (!view->p_restore)
4683 status_restore(view);
4684 return TRUE;
4685 }
4687 static bool
4688 status_draw(struct view *view, struct line *line, unsigned int lineno)
4689 {
4690 struct status *status = line->data;
4691 enum line_type type;
4692 const char *text;
4694 if (!status) {
4695 switch (line->type) {
4696 case LINE_STAT_STAGED:
4697 type = LINE_STAT_SECTION;
4698 text = "Changes to be committed:";
4699 break;
4701 case LINE_STAT_UNSTAGED:
4702 type = LINE_STAT_SECTION;
4703 text = "Changed but not updated:";
4704 break;
4706 case LINE_STAT_UNTRACKED:
4707 type = LINE_STAT_SECTION;
4708 text = "Untracked files:";
4709 break;
4711 case LINE_STAT_NONE:
4712 type = LINE_DEFAULT;
4713 text = " (no files)";
4714 break;
4716 case LINE_STAT_HEAD:
4717 type = LINE_STAT_HEAD;
4718 text = status_onbranch;
4719 break;
4721 default:
4722 return FALSE;
4723 }
4724 } else {
4725 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4727 buf[0] = status->status;
4728 if (draw_text(view, line->type, buf, TRUE))
4729 return TRUE;
4730 type = LINE_DEFAULT;
4731 text = status->new.name;
4732 }
4734 draw_text(view, type, text, TRUE);
4735 return TRUE;
4736 }
4738 static enum request
4739 status_enter(struct view *view, struct line *line)
4740 {
4741 struct status *status = line->data;
4742 const char *oldpath = status ? status->old.name : NULL;
4743 /* Diffs for unmerged entries are empty when passing the new
4744 * path, so leave it empty. */
4745 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4746 const char *info;
4747 enum open_flags split;
4748 struct view *stage = VIEW(REQ_VIEW_STAGE);
4750 if (line->type == LINE_STAT_NONE ||
4751 (!status && line[1].type == LINE_STAT_NONE)) {
4752 report("No file to diff");
4753 return REQ_NONE;
4754 }
4756 switch (line->type) {
4757 case LINE_STAT_STAGED:
4758 if (is_initial_commit()) {
4759 const char *no_head_diff_argv[] = {
4760 "git", "diff", "--no-color", "--patch-with-stat",
4761 "--", "/dev/null", newpath, NULL
4762 };
4764 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4765 return REQ_QUIT;
4766 } else {
4767 const char *index_show_argv[] = {
4768 "git", "diff-index", "--root", "--patch-with-stat",
4769 "-C", "-M", "--cached", "HEAD", "--",
4770 oldpath, newpath, NULL
4771 };
4773 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4774 return REQ_QUIT;
4775 }
4777 if (status)
4778 info = "Staged changes to %s";
4779 else
4780 info = "Staged changes";
4781 break;
4783 case LINE_STAT_UNSTAGED:
4784 {
4785 const char *files_show_argv[] = {
4786 "git", "diff-files", "--root", "--patch-with-stat",
4787 "-C", "-M", "--", oldpath, newpath, NULL
4788 };
4790 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4791 return REQ_QUIT;
4792 if (status)
4793 info = "Unstaged changes to %s";
4794 else
4795 info = "Unstaged changes";
4796 break;
4797 }
4798 case LINE_STAT_UNTRACKED:
4799 if (!newpath) {
4800 report("No file to show");
4801 return REQ_NONE;
4802 }
4804 if (!suffixcmp(status->new.name, -1, "/")) {
4805 report("Cannot display a directory");
4806 return REQ_NONE;
4807 }
4809 if (!prepare_update_file(stage, newpath))
4810 return REQ_QUIT;
4811 info = "Untracked file %s";
4812 break;
4814 case LINE_STAT_HEAD:
4815 return REQ_NONE;
4817 default:
4818 die("line type %d not handled in switch", line->type);
4819 }
4821 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4822 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4823 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4824 if (status) {
4825 stage_status = *status;
4826 } else {
4827 memset(&stage_status, 0, sizeof(stage_status));
4828 }
4830 stage_line_type = line->type;
4831 stage_chunks = 0;
4832 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4833 }
4835 return REQ_NONE;
4836 }
4838 static bool
4839 status_exists(struct status *status, enum line_type type)
4840 {
4841 struct view *view = VIEW(REQ_VIEW_STATUS);
4842 unsigned long lineno;
4844 for (lineno = 0; lineno < view->lines; lineno++) {
4845 struct line *line = &view->line[lineno];
4846 struct status *pos = line->data;
4848 if (line->type != type)
4849 continue;
4850 if (!pos && (!status || !status->status) && line[1].data) {
4851 select_view_line(view, lineno);
4852 return TRUE;
4853 }
4854 if (pos && !strcmp(status->new.name, pos->new.name)) {
4855 select_view_line(view, lineno);
4856 return TRUE;
4857 }
4858 }
4860 return FALSE;
4861 }
4864 static bool
4865 status_update_prepare(struct io *io, enum line_type type)
4866 {
4867 const char *staged_argv[] = {
4868 "git", "update-index", "-z", "--index-info", NULL
4869 };
4870 const char *others_argv[] = {
4871 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4872 };
4874 switch (type) {
4875 case LINE_STAT_STAGED:
4876 return run_io(io, staged_argv, opt_cdup, IO_WR);
4878 case LINE_STAT_UNSTAGED:
4879 return run_io(io, others_argv, opt_cdup, IO_WR);
4881 case LINE_STAT_UNTRACKED:
4882 return run_io(io, others_argv, NULL, IO_WR);
4884 default:
4885 die("line type %d not handled in switch", type);
4886 return FALSE;
4887 }
4888 }
4890 static bool
4891 status_update_write(struct io *io, struct status *status, enum line_type type)
4892 {
4893 char buf[SIZEOF_STR];
4894 size_t bufsize = 0;
4896 switch (type) {
4897 case LINE_STAT_STAGED:
4898 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4899 status->old.mode,
4900 status->old.rev,
4901 status->old.name, 0))
4902 return FALSE;
4903 break;
4905 case LINE_STAT_UNSTAGED:
4906 case LINE_STAT_UNTRACKED:
4907 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4908 return FALSE;
4909 break;
4911 default:
4912 die("line type %d not handled in switch", type);
4913 }
4915 return io_write(io, buf, bufsize);
4916 }
4918 static bool
4919 status_update_file(struct status *status, enum line_type type)
4920 {
4921 struct io io = {};
4922 bool result;
4924 if (!status_update_prepare(&io, type))
4925 return FALSE;
4927 result = status_update_write(&io, status, type);
4928 done_io(&io);
4929 return result;
4930 }
4932 static bool
4933 status_update_files(struct view *view, struct line *line)
4934 {
4935 struct io io = {};
4936 bool result = TRUE;
4937 struct line *pos = view->line + view->lines;
4938 int files = 0;
4939 int file, done;
4941 if (!status_update_prepare(&io, line->type))
4942 return FALSE;
4944 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4945 files++;
4947 for (file = 0, done = 0; result && file < files; line++, file++) {
4948 int almost_done = file * 100 / files;
4950 if (almost_done > done) {
4951 done = almost_done;
4952 string_format(view->ref, "updating file %u of %u (%d%% done)",
4953 file, files, done);
4954 update_view_title(view);
4955 }
4956 result = status_update_write(&io, line->data, line->type);
4957 }
4959 done_io(&io);
4960 return result;
4961 }
4963 static bool
4964 status_update(struct view *view)
4965 {
4966 struct line *line = &view->line[view->lineno];
4968 assert(view->lines);
4970 if (!line->data) {
4971 /* This should work even for the "On branch" line. */
4972 if (line < view->line + view->lines && !line[1].data) {
4973 report("Nothing to update");
4974 return FALSE;
4975 }
4977 if (!status_update_files(view, line + 1)) {
4978 report("Failed to update file status");
4979 return FALSE;
4980 }
4982 } else if (!status_update_file(line->data, line->type)) {
4983 report("Failed to update file status");
4984 return FALSE;
4985 }
4987 return TRUE;
4988 }
4990 static bool
4991 status_revert(struct status *status, enum line_type type, bool has_none)
4992 {
4993 if (!status || type != LINE_STAT_UNSTAGED) {
4994 if (type == LINE_STAT_STAGED) {
4995 report("Cannot revert changes to staged files");
4996 } else if (type == LINE_STAT_UNTRACKED) {
4997 report("Cannot revert changes to untracked files");
4998 } else if (has_none) {
4999 report("Nothing to revert");
5000 } else {
5001 report("Cannot revert changes to multiple files");
5002 }
5003 return FALSE;
5005 } else {
5006 const char *checkout_argv[] = {
5007 "git", "checkout", "--", status->old.name, NULL
5008 };
5010 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5011 return FALSE;
5012 return run_io_fg(checkout_argv, opt_cdup);
5013 }
5014 }
5016 static enum request
5017 status_request(struct view *view, enum request request, struct line *line)
5018 {
5019 struct status *status = line->data;
5021 switch (request) {
5022 case REQ_STATUS_UPDATE:
5023 if (!status_update(view))
5024 return REQ_NONE;
5025 break;
5027 case REQ_STATUS_REVERT:
5028 if (!status_revert(status, line->type, status_has_none(view, line)))
5029 return REQ_NONE;
5030 break;
5032 case REQ_STATUS_MERGE:
5033 if (!status || status->status != 'U') {
5034 report("Merging only possible for files with unmerged status ('U').");
5035 return REQ_NONE;
5036 }
5037 open_mergetool(status->new.name);
5038 break;
5040 case REQ_EDIT:
5041 if (!status)
5042 return request;
5043 if (status->status == 'D') {
5044 report("File has been deleted.");
5045 return REQ_NONE;
5046 }
5048 open_editor(status->status != '?', status->new.name);
5049 break;
5051 case REQ_VIEW_BLAME:
5052 if (status) {
5053 string_copy(opt_file, status->new.name);
5054 opt_ref[0] = 0;
5055 }
5056 return request;
5058 case REQ_ENTER:
5059 /* After returning the status view has been split to
5060 * show the stage view. No further reloading is
5061 * necessary. */
5062 status_enter(view, line);
5063 return REQ_NONE;
5065 case REQ_REFRESH:
5066 /* Simply reload the view. */
5067 break;
5069 default:
5070 return request;
5071 }
5073 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5075 return REQ_NONE;
5076 }
5078 static void
5079 status_select(struct view *view, struct line *line)
5080 {
5081 struct status *status = line->data;
5082 char file[SIZEOF_STR] = "all files";
5083 const char *text;
5084 const char *key;
5086 if (status && !string_format(file, "'%s'", status->new.name))
5087 return;
5089 if (!status && line[1].type == LINE_STAT_NONE)
5090 line++;
5092 switch (line->type) {
5093 case LINE_STAT_STAGED:
5094 text = "Press %s to unstage %s for commit";
5095 break;
5097 case LINE_STAT_UNSTAGED:
5098 text = "Press %s to stage %s for commit";
5099 break;
5101 case LINE_STAT_UNTRACKED:
5102 text = "Press %s to stage %s for addition";
5103 break;
5105 case LINE_STAT_HEAD:
5106 case LINE_STAT_NONE:
5107 text = "Nothing to update";
5108 break;
5110 default:
5111 die("line type %d not handled in switch", line->type);
5112 }
5114 if (status && status->status == 'U') {
5115 text = "Press %s to resolve conflict in %s";
5116 key = get_key(REQ_STATUS_MERGE);
5118 } else {
5119 key = get_key(REQ_STATUS_UPDATE);
5120 }
5122 string_format(view->ref, text, key, file);
5123 }
5125 static bool
5126 status_grep(struct view *view, struct line *line)
5127 {
5128 struct status *status = line->data;
5129 enum { S_STATUS, S_NAME, S_END } state;
5130 char buf[2] = "?";
5131 regmatch_t pmatch;
5133 if (!status)
5134 return FALSE;
5136 for (state = S_STATUS; state < S_END; state++) {
5137 const char *text;
5139 switch (state) {
5140 case S_NAME: text = status->new.name; break;
5141 case S_STATUS:
5142 buf[0] = status->status;
5143 text = buf;
5144 break;
5146 default:
5147 return FALSE;
5148 }
5150 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5151 return TRUE;
5152 }
5154 return FALSE;
5155 }
5157 static struct view_ops status_ops = {
5158 "file",
5159 NULL,
5160 status_open,
5161 NULL,
5162 status_draw,
5163 status_request,
5164 status_grep,
5165 status_select,
5166 };
5169 static bool
5170 stage_diff_write(struct io *io, struct line *line, struct line *end)
5171 {
5172 while (line < end) {
5173 if (!io_write(io, line->data, strlen(line->data)) ||
5174 !io_write(io, "\n", 1))
5175 return FALSE;
5176 line++;
5177 if (line->type == LINE_DIFF_CHUNK ||
5178 line->type == LINE_DIFF_HEADER)
5179 break;
5180 }
5182 return TRUE;
5183 }
5185 static struct line *
5186 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5187 {
5188 for (; view->line < line; line--)
5189 if (line->type == type)
5190 return line;
5192 return NULL;
5193 }
5195 static bool
5196 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5197 {
5198 const char *apply_argv[SIZEOF_ARG] = {
5199 "git", "apply", "--whitespace=nowarn", NULL
5200 };
5201 struct line *diff_hdr;
5202 struct io io = {};
5203 int argc = 3;
5205 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5206 if (!diff_hdr)
5207 return FALSE;
5209 if (!revert)
5210 apply_argv[argc++] = "--cached";
5211 if (revert || stage_line_type == LINE_STAT_STAGED)
5212 apply_argv[argc++] = "-R";
5213 apply_argv[argc++] = "-";
5214 apply_argv[argc++] = NULL;
5215 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5216 return FALSE;
5218 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5219 !stage_diff_write(&io, chunk, view->line + view->lines))
5220 chunk = NULL;
5222 done_io(&io);
5223 run_io_bg(update_index_argv);
5225 return chunk ? TRUE : FALSE;
5226 }
5228 static bool
5229 stage_update(struct view *view, struct line *line)
5230 {
5231 struct line *chunk = NULL;
5233 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5234 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5236 if (chunk) {
5237 if (!stage_apply_chunk(view, chunk, FALSE)) {
5238 report("Failed to apply chunk");
5239 return FALSE;
5240 }
5242 } else if (!stage_status.status) {
5243 view = VIEW(REQ_VIEW_STATUS);
5245 for (line = view->line; line < view->line + view->lines; line++)
5246 if (line->type == stage_line_type)
5247 break;
5249 if (!status_update_files(view, line + 1)) {
5250 report("Failed to update files");
5251 return FALSE;
5252 }
5254 } else if (!status_update_file(&stage_status, stage_line_type)) {
5255 report("Failed to update file");
5256 return FALSE;
5257 }
5259 return TRUE;
5260 }
5262 static bool
5263 stage_revert(struct view *view, struct line *line)
5264 {
5265 struct line *chunk = NULL;
5267 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5268 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5270 if (chunk) {
5271 if (!prompt_yesno("Are you sure you want to revert changes?"))
5272 return FALSE;
5274 if (!stage_apply_chunk(view, chunk, TRUE)) {
5275 report("Failed to revert chunk");
5276 return FALSE;
5277 }
5278 return TRUE;
5280 } else {
5281 return status_revert(stage_status.status ? &stage_status : NULL,
5282 stage_line_type, FALSE);
5283 }
5284 }
5287 static void
5288 stage_next(struct view *view, struct line *line)
5289 {
5290 int i;
5292 if (!stage_chunks) {
5293 static size_t alloc = 0;
5294 int *tmp;
5296 for (line = view->line; line < view->line + view->lines; line++) {
5297 if (line->type != LINE_DIFF_CHUNK)
5298 continue;
5300 tmp = realloc_items(stage_chunk, &alloc,
5301 stage_chunks, sizeof(*tmp));
5302 if (!tmp) {
5303 report("Allocation failure");
5304 return;
5305 }
5307 stage_chunk = tmp;
5308 stage_chunk[stage_chunks++] = line - view->line;
5309 }
5310 }
5312 for (i = 0; i < stage_chunks; i++) {
5313 if (stage_chunk[i] > view->lineno) {
5314 do_scroll_view(view, stage_chunk[i] - view->lineno);
5315 report("Chunk %d of %d", i + 1, stage_chunks);
5316 return;
5317 }
5318 }
5320 report("No next chunk found");
5321 }
5323 static enum request
5324 stage_request(struct view *view, enum request request, struct line *line)
5325 {
5326 switch (request) {
5327 case REQ_STATUS_UPDATE:
5328 if (!stage_update(view, line))
5329 return REQ_NONE;
5330 break;
5332 case REQ_STATUS_REVERT:
5333 if (!stage_revert(view, line))
5334 return REQ_NONE;
5335 break;
5337 case REQ_STAGE_NEXT:
5338 if (stage_line_type == LINE_STAT_UNTRACKED) {
5339 report("File is untracked; press %s to add",
5340 get_key(REQ_STATUS_UPDATE));
5341 return REQ_NONE;
5342 }
5343 stage_next(view, line);
5344 return REQ_NONE;
5346 case REQ_EDIT:
5347 if (!stage_status.new.name[0])
5348 return request;
5349 if (stage_status.status == 'D') {
5350 report("File has been deleted.");
5351 return REQ_NONE;
5352 }
5354 open_editor(stage_status.status != '?', stage_status.new.name);
5355 break;
5357 case REQ_REFRESH:
5358 /* Reload everything ... */
5359 break;
5361 case REQ_VIEW_BLAME:
5362 if (stage_status.new.name[0]) {
5363 string_copy(opt_file, stage_status.new.name);
5364 opt_ref[0] = 0;
5365 }
5366 return request;
5368 case REQ_ENTER:
5369 return pager_request(view, request, line);
5371 default:
5372 return request;
5373 }
5375 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5376 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5378 /* Check whether the staged entry still exists, and close the
5379 * stage view if it doesn't. */
5380 if (!status_exists(&stage_status, stage_line_type)) {
5381 status_restore(VIEW(REQ_VIEW_STATUS));
5382 return REQ_VIEW_CLOSE;
5383 }
5385 if (stage_line_type == LINE_STAT_UNTRACKED) {
5386 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5387 report("Cannot display a directory");
5388 return REQ_NONE;
5389 }
5391 if (!prepare_update_file(view, stage_status.new.name)) {
5392 report("Failed to open file: %s", strerror(errno));
5393 return REQ_NONE;
5394 }
5395 }
5396 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5398 return REQ_NONE;
5399 }
5401 static struct view_ops stage_ops = {
5402 "line",
5403 NULL,
5404 NULL,
5405 pager_read,
5406 pager_draw,
5407 stage_request,
5408 pager_grep,
5409 pager_select,
5410 };
5413 /*
5414 * Revision graph
5415 */
5417 struct commit {
5418 char id[SIZEOF_REV]; /* SHA1 ID. */
5419 char title[128]; /* First line of the commit message. */
5420 char author[75]; /* Author of the commit. */
5421 struct tm time; /* Date from the author ident. */
5422 struct ref **refs; /* Repository references. */
5423 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5424 size_t graph_size; /* The width of the graph array. */
5425 bool has_parents; /* Rewritten --parents seen. */
5426 };
5428 /* Size of rev graph with no "padding" columns */
5429 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5431 struct rev_graph {
5432 struct rev_graph *prev, *next, *parents;
5433 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5434 size_t size;
5435 struct commit *commit;
5436 size_t pos;
5437 unsigned int boundary:1;
5438 };
5440 /* Parents of the commit being visualized. */
5441 static struct rev_graph graph_parents[4];
5443 /* The current stack of revisions on the graph. */
5444 static struct rev_graph graph_stacks[4] = {
5445 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5446 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5447 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5448 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5449 };
5451 static inline bool
5452 graph_parent_is_merge(struct rev_graph *graph)
5453 {
5454 return graph->parents->size > 1;
5455 }
5457 static inline void
5458 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5459 {
5460 struct commit *commit = graph->commit;
5462 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5463 commit->graph[commit->graph_size++] = symbol;
5464 }
5466 static void
5467 clear_rev_graph(struct rev_graph *graph)
5468 {
5469 graph->boundary = 0;
5470 graph->size = graph->pos = 0;
5471 graph->commit = NULL;
5472 memset(graph->parents, 0, sizeof(*graph->parents));
5473 }
5475 static void
5476 done_rev_graph(struct rev_graph *graph)
5477 {
5478 if (graph_parent_is_merge(graph) &&
5479 graph->pos < graph->size - 1 &&
5480 graph->next->size == graph->size + graph->parents->size - 1) {
5481 size_t i = graph->pos + graph->parents->size - 1;
5483 graph->commit->graph_size = i * 2;
5484 while (i < graph->next->size - 1) {
5485 append_to_rev_graph(graph, ' ');
5486 append_to_rev_graph(graph, '\\');
5487 i++;
5488 }
5489 }
5491 clear_rev_graph(graph);
5492 }
5494 static void
5495 push_rev_graph(struct rev_graph *graph, const char *parent)
5496 {
5497 int i;
5499 /* "Collapse" duplicate parents lines.
5500 *
5501 * FIXME: This needs to also update update the drawn graph but
5502 * for now it just serves as a method for pruning graph lines. */
5503 for (i = 0; i < graph->size; i++)
5504 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5505 return;
5507 if (graph->size < SIZEOF_REVITEMS) {
5508 string_copy_rev(graph->rev[graph->size++], parent);
5509 }
5510 }
5512 static chtype
5513 get_rev_graph_symbol(struct rev_graph *graph)
5514 {
5515 chtype symbol;
5517 if (graph->boundary)
5518 symbol = REVGRAPH_BOUND;
5519 else if (graph->parents->size == 0)
5520 symbol = REVGRAPH_INIT;
5521 else if (graph_parent_is_merge(graph))
5522 symbol = REVGRAPH_MERGE;
5523 else if (graph->pos >= graph->size)
5524 symbol = REVGRAPH_BRANCH;
5525 else
5526 symbol = REVGRAPH_COMMIT;
5528 return symbol;
5529 }
5531 static void
5532 draw_rev_graph(struct rev_graph *graph)
5533 {
5534 struct rev_filler {
5535 chtype separator, line;
5536 };
5537 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5538 static struct rev_filler fillers[] = {
5539 { ' ', '|' },
5540 { '`', '.' },
5541 { '\'', ' ' },
5542 { '/', ' ' },
5543 };
5544 chtype symbol = get_rev_graph_symbol(graph);
5545 struct rev_filler *filler;
5546 size_t i;
5548 if (opt_line_graphics)
5549 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5551 filler = &fillers[DEFAULT];
5553 for (i = 0; i < graph->pos; i++) {
5554 append_to_rev_graph(graph, filler->line);
5555 if (graph_parent_is_merge(graph->prev) &&
5556 graph->prev->pos == i)
5557 filler = &fillers[RSHARP];
5559 append_to_rev_graph(graph, filler->separator);
5560 }
5562 /* Place the symbol for this revision. */
5563 append_to_rev_graph(graph, symbol);
5565 if (graph->prev->size > graph->size)
5566 filler = &fillers[RDIAG];
5567 else
5568 filler = &fillers[DEFAULT];
5570 i++;
5572 for (; i < graph->size; i++) {
5573 append_to_rev_graph(graph, filler->separator);
5574 append_to_rev_graph(graph, filler->line);
5575 if (graph_parent_is_merge(graph->prev) &&
5576 i < graph->prev->pos + graph->parents->size)
5577 filler = &fillers[RSHARP];
5578 if (graph->prev->size > graph->size)
5579 filler = &fillers[LDIAG];
5580 }
5582 if (graph->prev->size > graph->size) {
5583 append_to_rev_graph(graph, filler->separator);
5584 if (filler->line != ' ')
5585 append_to_rev_graph(graph, filler->line);
5586 }
5587 }
5589 /* Prepare the next rev graph */
5590 static void
5591 prepare_rev_graph(struct rev_graph *graph)
5592 {
5593 size_t i;
5595 /* First, traverse all lines of revisions up to the active one. */
5596 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5597 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5598 break;
5600 push_rev_graph(graph->next, graph->rev[graph->pos]);
5601 }
5603 /* Interleave the new revision parent(s). */
5604 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5605 push_rev_graph(graph->next, graph->parents->rev[i]);
5607 /* Lastly, put any remaining revisions. */
5608 for (i = graph->pos + 1; i < graph->size; i++)
5609 push_rev_graph(graph->next, graph->rev[i]);
5610 }
5612 static void
5613 update_rev_graph(struct view *view, struct rev_graph *graph)
5614 {
5615 /* If this is the finalizing update ... */
5616 if (graph->commit)
5617 prepare_rev_graph(graph);
5619 /* Graph visualization needs a one rev look-ahead,
5620 * so the first update doesn't visualize anything. */
5621 if (!graph->prev->commit)
5622 return;
5624 if (view->lines > 2)
5625 view->line[view->lines - 3].dirty = 1;
5626 if (view->lines > 1)
5627 view->line[view->lines - 2].dirty = 1;
5628 draw_rev_graph(graph->prev);
5629 done_rev_graph(graph->prev->prev);
5630 }
5633 /*
5634 * Main view backend
5635 */
5637 static const char *main_argv[SIZEOF_ARG] = {
5638 "git", "log", "--no-color", "--pretty=raw", "--parents",
5639 "--topo-order", "%(head)", NULL
5640 };
5642 static bool
5643 main_draw(struct view *view, struct line *line, unsigned int lineno)
5644 {
5645 struct commit *commit = line->data;
5647 if (!*commit->author)
5648 return FALSE;
5650 if (opt_date && draw_date(view, &commit->time))
5651 return TRUE;
5653 if (opt_author &&
5654 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5655 return TRUE;
5657 if (opt_rev_graph && commit->graph_size &&
5658 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5659 return TRUE;
5661 if (opt_show_refs && commit->refs) {
5662 size_t i = 0;
5664 do {
5665 enum line_type type;
5667 if (commit->refs[i]->head)
5668 type = LINE_MAIN_HEAD;
5669 else if (commit->refs[i]->ltag)
5670 type = LINE_MAIN_LOCAL_TAG;
5671 else if (commit->refs[i]->tag)
5672 type = LINE_MAIN_TAG;
5673 else if (commit->refs[i]->tracked)
5674 type = LINE_MAIN_TRACKED;
5675 else if (commit->refs[i]->remote)
5676 type = LINE_MAIN_REMOTE;
5677 else
5678 type = LINE_MAIN_REF;
5680 if (draw_text(view, type, "[", TRUE) ||
5681 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5682 draw_text(view, type, "]", TRUE))
5683 return TRUE;
5685 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5686 return TRUE;
5687 } while (commit->refs[i++]->next);
5688 }
5690 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5691 return TRUE;
5692 }
5694 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5695 static bool
5696 main_read(struct view *view, char *line)
5697 {
5698 static struct rev_graph *graph = graph_stacks;
5699 enum line_type type;
5700 struct commit *commit;
5702 if (!line) {
5703 int i;
5705 if (!view->lines && !view->parent)
5706 die("No revisions match the given arguments.");
5707 if (view->lines > 0) {
5708 commit = view->line[view->lines - 1].data;
5709 view->line[view->lines - 1].dirty = 1;
5710 if (!*commit->author) {
5711 view->lines--;
5712 free(commit);
5713 graph->commit = NULL;
5714 }
5715 }
5716 update_rev_graph(view, graph);
5718 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5719 clear_rev_graph(&graph_stacks[i]);
5720 return TRUE;
5721 }
5723 type = get_line_type(line);
5724 if (type == LINE_COMMIT) {
5725 commit = calloc(1, sizeof(struct commit));
5726 if (!commit)
5727 return FALSE;
5729 line += STRING_SIZE("commit ");
5730 if (*line == '-') {
5731 graph->boundary = 1;
5732 line++;
5733 }
5735 string_copy_rev(commit->id, line);
5736 commit->refs = get_refs(commit->id);
5737 graph->commit = commit;
5738 add_line_data(view, commit, LINE_MAIN_COMMIT);
5740 while ((line = strchr(line, ' '))) {
5741 line++;
5742 push_rev_graph(graph->parents, line);
5743 commit->has_parents = TRUE;
5744 }
5745 return TRUE;
5746 }
5748 if (!view->lines)
5749 return TRUE;
5750 commit = view->line[view->lines - 1].data;
5752 switch (type) {
5753 case LINE_PARENT:
5754 if (commit->has_parents)
5755 break;
5756 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5757 break;
5759 case LINE_AUTHOR:
5760 parse_author_line(line + STRING_SIZE("author "),
5761 commit->author, sizeof(commit->author),
5762 &commit->time);
5763 update_rev_graph(view, graph);
5764 graph = graph->next;
5765 break;
5767 default:
5768 /* Fill in the commit title if it has not already been set. */
5769 if (commit->title[0])
5770 break;
5772 /* Require titles to start with a non-space character at the
5773 * offset used by git log. */
5774 if (strncmp(line, " ", 4))
5775 break;
5776 line += 4;
5777 /* Well, if the title starts with a whitespace character,
5778 * try to be forgiving. Otherwise we end up with no title. */
5779 while (isspace(*line))
5780 line++;
5781 if (*line == '\0')
5782 break;
5783 /* FIXME: More graceful handling of titles; append "..." to
5784 * shortened titles, etc. */
5786 string_ncopy(commit->title, line, strlen(line));
5787 view->line[view->lines - 1].dirty = 1;
5788 }
5790 return TRUE;
5791 }
5793 static enum request
5794 main_request(struct view *view, enum request request, struct line *line)
5795 {
5796 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5798 switch (request) {
5799 case REQ_ENTER:
5800 open_view(view, REQ_VIEW_DIFF, flags);
5801 break;
5802 case REQ_REFRESH:
5803 load_refs();
5804 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5805 break;
5806 default:
5807 return request;
5808 }
5810 return REQ_NONE;
5811 }
5813 static bool
5814 grep_refs(struct ref **refs, regex_t *regex)
5815 {
5816 regmatch_t pmatch;
5817 size_t i = 0;
5819 if (!refs)
5820 return FALSE;
5821 do {
5822 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5823 return TRUE;
5824 } while (refs[i++]->next);
5826 return FALSE;
5827 }
5829 static bool
5830 main_grep(struct view *view, struct line *line)
5831 {
5832 struct commit *commit = line->data;
5833 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5834 char buf[DATE_COLS + 1];
5835 regmatch_t pmatch;
5837 for (state = S_TITLE; state < S_END; state++) {
5838 char *text;
5840 switch (state) {
5841 case S_TITLE: text = commit->title; break;
5842 case S_AUTHOR:
5843 if (!opt_author)
5844 continue;
5845 text = commit->author;
5846 break;
5847 case S_DATE:
5848 if (!opt_date)
5849 continue;
5850 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5851 continue;
5852 text = buf;
5853 break;
5854 case S_REFS:
5855 if (!opt_show_refs)
5856 continue;
5857 if (grep_refs(commit->refs, view->regex) == TRUE)
5858 return TRUE;
5859 continue;
5860 default:
5861 return FALSE;
5862 }
5864 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5865 return TRUE;
5866 }
5868 return FALSE;
5869 }
5871 static void
5872 main_select(struct view *view, struct line *line)
5873 {
5874 struct commit *commit = line->data;
5876 string_copy_rev(view->ref, commit->id);
5877 string_copy_rev(ref_commit, view->ref);
5878 }
5880 static struct view_ops main_ops = {
5881 "commit",
5882 main_argv,
5883 NULL,
5884 main_read,
5885 main_draw,
5886 main_request,
5887 main_grep,
5888 main_select,
5889 };
5892 /*
5893 * Unicode / UTF-8 handling
5894 *
5895 * NOTE: Much of the following code for dealing with unicode is derived from
5896 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5897 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5898 */
5900 /* I've (over)annotated a lot of code snippets because I am not entirely
5901 * confident that the approach taken by this small UTF-8 interface is correct.
5902 * --jonas */
5904 static inline int
5905 unicode_width(unsigned long c)
5906 {
5907 if (c >= 0x1100 &&
5908 (c <= 0x115f /* Hangul Jamo */
5909 || c == 0x2329
5910 || c == 0x232a
5911 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5912 /* CJK ... Yi */
5913 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5914 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5915 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5916 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5917 || (c >= 0xffe0 && c <= 0xffe6)
5918 || (c >= 0x20000 && c <= 0x2fffd)
5919 || (c >= 0x30000 && c <= 0x3fffd)))
5920 return 2;
5922 if (c == '\t')
5923 return opt_tab_size;
5925 return 1;
5926 }
5928 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5929 * Illegal bytes are set one. */
5930 static const unsigned char utf8_bytes[256] = {
5931 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,
5932 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,
5933 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,
5934 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,
5935 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,
5936 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,
5937 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,
5938 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,
5939 };
5941 /* Decode UTF-8 multi-byte representation into a unicode character. */
5942 static inline unsigned long
5943 utf8_to_unicode(const char *string, size_t length)
5944 {
5945 unsigned long unicode;
5947 switch (length) {
5948 case 1:
5949 unicode = string[0];
5950 break;
5951 case 2:
5952 unicode = (string[0] & 0x1f) << 6;
5953 unicode += (string[1] & 0x3f);
5954 break;
5955 case 3:
5956 unicode = (string[0] & 0x0f) << 12;
5957 unicode += ((string[1] & 0x3f) << 6);
5958 unicode += (string[2] & 0x3f);
5959 break;
5960 case 4:
5961 unicode = (string[0] & 0x0f) << 18;
5962 unicode += ((string[1] & 0x3f) << 12);
5963 unicode += ((string[2] & 0x3f) << 6);
5964 unicode += (string[3] & 0x3f);
5965 break;
5966 case 5:
5967 unicode = (string[0] & 0x0f) << 24;
5968 unicode += ((string[1] & 0x3f) << 18);
5969 unicode += ((string[2] & 0x3f) << 12);
5970 unicode += ((string[3] & 0x3f) << 6);
5971 unicode += (string[4] & 0x3f);
5972 break;
5973 case 6:
5974 unicode = (string[0] & 0x01) << 30;
5975 unicode += ((string[1] & 0x3f) << 24);
5976 unicode += ((string[2] & 0x3f) << 18);
5977 unicode += ((string[3] & 0x3f) << 12);
5978 unicode += ((string[4] & 0x3f) << 6);
5979 unicode += (string[5] & 0x3f);
5980 break;
5981 default:
5982 die("Invalid unicode length");
5983 }
5985 /* Invalid characters could return the special 0xfffd value but NUL
5986 * should be just as good. */
5987 return unicode > 0xffff ? 0 : unicode;
5988 }
5990 /* Calculates how much of string can be shown within the given maximum width
5991 * and sets trimmed parameter to non-zero value if all of string could not be
5992 * shown. If the reserve flag is TRUE, it will reserve at least one
5993 * trailing character, which can be useful when drawing a delimiter.
5994 *
5995 * Returns the number of bytes to output from string to satisfy max_width. */
5996 static size_t
5997 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5998 {
5999 const char *start = string;
6000 const char *end = strchr(string, '\0');
6001 unsigned char last_bytes = 0;
6002 size_t last_ucwidth = 0;
6004 *width = 0;
6005 *trimmed = 0;
6007 while (string < end) {
6008 int c = *(unsigned char *) string;
6009 unsigned char bytes = utf8_bytes[c];
6010 size_t ucwidth;
6011 unsigned long unicode;
6013 if (string + bytes > end)
6014 break;
6016 /* Change representation to figure out whether
6017 * it is a single- or double-width character. */
6019 unicode = utf8_to_unicode(string, bytes);
6020 /* FIXME: Graceful handling of invalid unicode character. */
6021 if (!unicode)
6022 break;
6024 ucwidth = unicode_width(unicode);
6025 *width += ucwidth;
6026 if (*width > max_width) {
6027 *trimmed = 1;
6028 *width -= ucwidth;
6029 if (reserve && *width == max_width) {
6030 string -= last_bytes;
6031 *width -= last_ucwidth;
6032 }
6033 break;
6034 }
6036 string += bytes;
6037 last_bytes = bytes;
6038 last_ucwidth = ucwidth;
6039 }
6041 return string - start;
6042 }
6045 /*
6046 * Status management
6047 */
6049 /* Whether or not the curses interface has been initialized. */
6050 static bool cursed = FALSE;
6052 /* The status window is used for polling keystrokes. */
6053 static WINDOW *status_win;
6055 static bool status_empty = TRUE;
6057 /* Update status and title window. */
6058 static void
6059 report(const char *msg, ...)
6060 {
6061 struct view *view = display[current_view];
6063 if (input_mode)
6064 return;
6066 if (!view) {
6067 char buf[SIZEOF_STR];
6068 va_list args;
6070 va_start(args, msg);
6071 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6072 buf[sizeof(buf) - 1] = 0;
6073 buf[sizeof(buf) - 2] = '.';
6074 buf[sizeof(buf) - 3] = '.';
6075 buf[sizeof(buf) - 4] = '.';
6076 }
6077 va_end(args);
6078 die("%s", buf);
6079 }
6081 if (!status_empty || *msg) {
6082 va_list args;
6084 va_start(args, msg);
6086 wmove(status_win, 0, 0);
6087 if (*msg) {
6088 vwprintw(status_win, msg, args);
6089 status_empty = FALSE;
6090 } else {
6091 status_empty = TRUE;
6092 }
6093 wclrtoeol(status_win);
6094 wrefresh(status_win);
6096 va_end(args);
6097 }
6099 update_view_title(view);
6100 update_display_cursor(view);
6101 }
6103 /* Controls when nodelay should be in effect when polling user input. */
6104 static void
6105 set_nonblocking_input(bool loading)
6106 {
6107 static unsigned int loading_views;
6109 if ((loading == FALSE && loading_views-- == 1) ||
6110 (loading == TRUE && loading_views++ == 0))
6111 nodelay(status_win, loading);
6112 }
6114 static void
6115 init_display(void)
6116 {
6117 int x, y;
6119 /* Initialize the curses library */
6120 if (isatty(STDIN_FILENO)) {
6121 cursed = !!initscr();
6122 opt_tty = stdin;
6123 } else {
6124 /* Leave stdin and stdout alone when acting as a pager. */
6125 opt_tty = fopen("/dev/tty", "r+");
6126 if (!opt_tty)
6127 die("Failed to open /dev/tty");
6128 cursed = !!newterm(NULL, opt_tty, opt_tty);
6129 }
6131 if (!cursed)
6132 die("Failed to initialize curses");
6134 nonl(); /* Tell curses not to do NL->CR/NL on output */
6135 cbreak(); /* Take input chars one at a time, no wait for \n */
6136 noecho(); /* Don't echo input */
6137 leaveok(stdscr, TRUE);
6139 if (has_colors())
6140 init_colors();
6142 getmaxyx(stdscr, y, x);
6143 status_win = newwin(1, 0, y - 1, 0);
6144 if (!status_win)
6145 die("Failed to create status window");
6147 /* Enable keyboard mapping */
6148 keypad(status_win, TRUE);
6149 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6151 TABSIZE = opt_tab_size;
6152 if (opt_line_graphics) {
6153 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6154 }
6155 }
6157 static int
6158 get_input(bool prompting)
6159 {
6160 struct view *view;
6161 int i, key;
6163 if (prompting)
6164 input_mode = TRUE;
6166 while (true) {
6167 foreach_view (view, i)
6168 update_view(view);
6170 /* Refresh, accept single keystroke of input */
6171 key = wgetch(status_win);
6173 /* wgetch() with nodelay() enabled returns ERR when
6174 * there's no input. */
6175 if (key == ERR) {
6176 doupdate();
6178 } else if (key == KEY_RESIZE) {
6179 int height, width;
6181 getmaxyx(stdscr, height, width);
6183 /* Resize the status view and let the view driver take
6184 * care of resizing the displayed views. */
6185 resize_display();
6186 redraw_display(TRUE);
6187 wresize(status_win, 1, width);
6188 mvwin(status_win, height - 1, 0);
6189 wrefresh(status_win);
6191 } else {
6192 input_mode = FALSE;
6193 return key;
6194 }
6195 }
6196 }
6198 static bool
6199 prompt_yesno(const char *prompt)
6200 {
6201 enum { WAIT, STOP, CANCEL } status = WAIT;
6202 bool answer = FALSE;
6204 while (status == WAIT) {
6205 int key;
6207 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
6208 wclrtoeol(status_win);
6210 key = get_input(TRUE);
6211 switch (key) {
6212 case 'y':
6213 case 'Y':
6214 answer = TRUE;
6215 status = STOP;
6216 break;
6218 case KEY_ESC:
6219 case KEY_RETURN:
6220 case KEY_ENTER:
6221 case KEY_BACKSPACE:
6222 case 'n':
6223 case 'N':
6224 case '\n':
6225 default:
6226 answer = FALSE;
6227 status = CANCEL;
6228 }
6229 }
6231 /* Clear the status window */
6232 status_empty = FALSE;
6233 report("");
6235 return answer;
6236 }
6238 static char *
6239 read_prompt(const char *prompt)
6240 {
6241 enum { READING, STOP, CANCEL } status = READING;
6242 static char buf[SIZEOF_STR];
6243 int pos = 0;
6245 while (status == READING) {
6246 int key;
6248 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6249 wclrtoeol(status_win);
6251 key = get_input(TRUE);
6252 switch (key) {
6253 case KEY_RETURN:
6254 case KEY_ENTER:
6255 case '\n':
6256 status = pos ? STOP : CANCEL;
6257 break;
6259 case KEY_BACKSPACE:
6260 if (pos > 0)
6261 pos--;
6262 else
6263 status = CANCEL;
6264 break;
6266 case KEY_ESC:
6267 status = CANCEL;
6268 break;
6270 default:
6271 if (pos >= sizeof(buf)) {
6272 report("Input string too long");
6273 return NULL;
6274 }
6276 if (isprint(key))
6277 buf[pos++] = (char) key;
6278 }
6279 }
6281 /* Clear the status window */
6282 status_empty = FALSE;
6283 report("");
6285 if (status == CANCEL)
6286 return NULL;
6288 buf[pos++] = 0;
6290 return buf;
6291 }
6293 /*
6294 * Repository properties
6295 */
6297 static int
6298 git_properties(const char **argv, const char *separators,
6299 int (*read_property)(char *, size_t, char *, size_t))
6300 {
6301 struct io io = {};
6303 if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6304 return read_properties(&io, separators, read_property);
6305 return ERR;
6306 }
6308 static struct ref *refs = NULL;
6309 static size_t refs_alloc = 0;
6310 static size_t refs_size = 0;
6312 /* Id <-> ref store */
6313 static struct ref ***id_refs = NULL;
6314 static size_t id_refs_alloc = 0;
6315 static size_t id_refs_size = 0;
6317 static int
6318 compare_refs(const void *ref1_, const void *ref2_)
6319 {
6320 const struct ref *ref1 = *(const struct ref **)ref1_;
6321 const struct ref *ref2 = *(const struct ref **)ref2_;
6323 if (ref1->tag != ref2->tag)
6324 return ref2->tag - ref1->tag;
6325 if (ref1->ltag != ref2->ltag)
6326 return ref2->ltag - ref2->ltag;
6327 if (ref1->head != ref2->head)
6328 return ref2->head - ref1->head;
6329 if (ref1->tracked != ref2->tracked)
6330 return ref2->tracked - ref1->tracked;
6331 if (ref1->remote != ref2->remote)
6332 return ref2->remote - ref1->remote;
6333 return strcmp(ref1->name, ref2->name);
6334 }
6336 static struct ref **
6337 get_refs(const char *id)
6338 {
6339 struct ref ***tmp_id_refs;
6340 struct ref **ref_list = NULL;
6341 size_t ref_list_alloc = 0;
6342 size_t ref_list_size = 0;
6343 size_t i;
6345 for (i = 0; i < id_refs_size; i++)
6346 if (!strcmp(id, id_refs[i][0]->id))
6347 return id_refs[i];
6349 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6350 sizeof(*id_refs));
6351 if (!tmp_id_refs)
6352 return NULL;
6354 id_refs = tmp_id_refs;
6356 for (i = 0; i < refs_size; i++) {
6357 struct ref **tmp;
6359 if (strcmp(id, refs[i].id))
6360 continue;
6362 tmp = realloc_items(ref_list, &ref_list_alloc,
6363 ref_list_size + 1, sizeof(*ref_list));
6364 if (!tmp) {
6365 if (ref_list)
6366 free(ref_list);
6367 return NULL;
6368 }
6370 ref_list = tmp;
6371 ref_list[ref_list_size] = &refs[i];
6372 /* XXX: The properties of the commit chains ensures that we can
6373 * safely modify the shared ref. The repo references will
6374 * always be similar for the same id. */
6375 ref_list[ref_list_size]->next = 1;
6377 ref_list_size++;
6378 }
6380 if (ref_list) {
6381 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6382 ref_list[ref_list_size - 1]->next = 0;
6383 id_refs[id_refs_size++] = ref_list;
6384 }
6386 return ref_list;
6387 }
6389 static int
6390 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6391 {
6392 struct ref *ref;
6393 bool tag = FALSE;
6394 bool ltag = FALSE;
6395 bool remote = FALSE;
6396 bool tracked = FALSE;
6397 bool check_replace = FALSE;
6398 bool head = FALSE;
6400 if (!prefixcmp(name, "refs/tags/")) {
6401 if (!suffixcmp(name, namelen, "^{}")) {
6402 namelen -= 3;
6403 name[namelen] = 0;
6404 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6405 check_replace = TRUE;
6406 } else {
6407 ltag = TRUE;
6408 }
6410 tag = TRUE;
6411 namelen -= STRING_SIZE("refs/tags/");
6412 name += STRING_SIZE("refs/tags/");
6414 } else if (!prefixcmp(name, "refs/remotes/")) {
6415 remote = TRUE;
6416 namelen -= STRING_SIZE("refs/remotes/");
6417 name += STRING_SIZE("refs/remotes/");
6418 tracked = !strcmp(opt_remote, name);
6420 } else if (!prefixcmp(name, "refs/heads/")) {
6421 namelen -= STRING_SIZE("refs/heads/");
6422 name += STRING_SIZE("refs/heads/");
6423 head = !strncmp(opt_head, name, namelen);
6425 } else if (!strcmp(name, "HEAD")) {
6426 string_ncopy(opt_head_rev, id, idlen);
6427 return OK;
6428 }
6430 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6431 /* it's an annotated tag, replace the previous sha1 with the
6432 * resolved commit id; relies on the fact git-ls-remote lists
6433 * the commit id of an annotated tag right before the commit id
6434 * it points to. */
6435 refs[refs_size - 1].ltag = ltag;
6436 string_copy_rev(refs[refs_size - 1].id, id);
6438 return OK;
6439 }
6440 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6441 if (!refs)
6442 return ERR;
6444 ref = &refs[refs_size++];
6445 ref->name = malloc(namelen + 1);
6446 if (!ref->name)
6447 return ERR;
6449 strncpy(ref->name, name, namelen);
6450 ref->name[namelen] = 0;
6451 ref->head = head;
6452 ref->tag = tag;
6453 ref->ltag = ltag;
6454 ref->remote = remote;
6455 ref->tracked = tracked;
6456 string_copy_rev(ref->id, id);
6458 return OK;
6459 }
6461 static int
6462 load_refs(void)
6463 {
6464 static const char *ls_remote_argv[SIZEOF_ARG] = {
6465 "git", "ls-remote", ".", NULL
6466 };
6467 static bool init = FALSE;
6469 if (!init) {
6470 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6471 init = TRUE;
6472 }
6474 if (!*opt_git_dir)
6475 return OK;
6477 while (refs_size > 0)
6478 free(refs[--refs_size].name);
6479 while (id_refs_size > 0)
6480 free(id_refs[--id_refs_size]);
6482 return git_properties(ls_remote_argv, "\t", read_ref);
6483 }
6485 static int
6486 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6487 {
6488 if (!strcmp(name, "i18n.commitencoding"))
6489 string_ncopy(opt_encoding, value, valuelen);
6491 if (!strcmp(name, "core.editor"))
6492 string_ncopy(opt_editor, value, valuelen);
6494 /* branch.<head>.remote */
6495 if (*opt_head &&
6496 !strncmp(name, "branch.", 7) &&
6497 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6498 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6499 string_ncopy(opt_remote, value, valuelen);
6501 if (*opt_head && *opt_remote &&
6502 !strncmp(name, "branch.", 7) &&
6503 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6504 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6505 size_t from = strlen(opt_remote);
6507 if (!prefixcmp(value, "refs/heads/")) {
6508 value += STRING_SIZE("refs/heads/");
6509 valuelen -= STRING_SIZE("refs/heads/");
6510 }
6512 if (!string_format_from(opt_remote, &from, "/%s", value))
6513 opt_remote[0] = 0;
6514 }
6516 return OK;
6517 }
6519 static int
6520 load_git_config(void)
6521 {
6522 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6524 return git_properties(config_list_argv, "=", read_repo_config_option);
6525 }
6527 static int
6528 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6529 {
6530 if (!opt_git_dir[0]) {
6531 string_ncopy(opt_git_dir, name, namelen);
6533 } else if (opt_is_inside_work_tree == -1) {
6534 /* This can be 3 different values depending on the
6535 * version of git being used. If git-rev-parse does not
6536 * understand --is-inside-work-tree it will simply echo
6537 * the option else either "true" or "false" is printed.
6538 * Default to true for the unknown case. */
6539 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6541 } else if (*name == '.') {
6542 string_ncopy(opt_cdup, name, namelen);
6544 } else {
6545 string_ncopy(opt_prefix, name, namelen);
6546 }
6548 return OK;
6549 }
6551 static int
6552 load_repo_info(void)
6553 {
6554 const char *head_argv[] = {
6555 "git", "symbolic-ref", "HEAD", NULL
6556 };
6557 const char *rev_parse_argv[] = {
6558 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6559 "--show-cdup", "--show-prefix", NULL
6560 };
6562 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6563 chomp_string(opt_head);
6564 if (!prefixcmp(opt_head, "refs/heads/")) {
6565 char *offset = opt_head + STRING_SIZE("refs/heads/");
6567 memmove(opt_head, offset, strlen(offset) + 1);
6568 }
6569 }
6571 return git_properties(rev_parse_argv, "=", read_repo_info);
6572 }
6574 static int
6575 read_properties(struct io *io, const char *separators,
6576 int (*read_property)(char *, size_t, char *, size_t))
6577 {
6578 char *name;
6579 int state = OK;
6581 if (!start_io(io))
6582 return ERR;
6584 while (state == OK && (name = io_get(io, '\n', TRUE))) {
6585 char *value;
6586 size_t namelen;
6587 size_t valuelen;
6589 name = chomp_string(name);
6590 namelen = strcspn(name, separators);
6592 if (name[namelen]) {
6593 name[namelen] = 0;
6594 value = chomp_string(name + namelen + 1);
6595 valuelen = strlen(value);
6597 } else {
6598 value = "";
6599 valuelen = 0;
6600 }
6602 state = read_property(name, namelen, value, valuelen);
6603 }
6605 if (state != ERR && io_error(io))
6606 state = ERR;
6607 done_io(io);
6609 return state;
6610 }
6613 /*
6614 * Main
6615 */
6617 static void __NORETURN
6618 quit(int sig)
6619 {
6620 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6621 if (cursed)
6622 endwin();
6623 exit(0);
6624 }
6626 static void __NORETURN
6627 die(const char *err, ...)
6628 {
6629 va_list args;
6631 endwin();
6633 va_start(args, err);
6634 fputs("tig: ", stderr);
6635 vfprintf(stderr, err, args);
6636 fputs("\n", stderr);
6637 va_end(args);
6639 exit(1);
6640 }
6642 static void
6643 warn(const char *msg, ...)
6644 {
6645 va_list args;
6647 va_start(args, msg);
6648 fputs("tig warning: ", stderr);
6649 vfprintf(stderr, msg, args);
6650 fputs("\n", stderr);
6651 va_end(args);
6652 }
6654 int
6655 main(int argc, const char *argv[])
6656 {
6657 const char **run_argv = NULL;
6658 struct view *view;
6659 enum request request;
6660 size_t i;
6662 signal(SIGINT, quit);
6664 if (setlocale(LC_ALL, "")) {
6665 char *codeset = nl_langinfo(CODESET);
6667 string_ncopy(opt_codeset, codeset, strlen(codeset));
6668 }
6670 if (load_repo_info() == ERR)
6671 die("Failed to load repo info.");
6673 if (load_options() == ERR)
6674 die("Failed to load user config.");
6676 if (load_git_config() == ERR)
6677 die("Failed to load repo config.");
6679 request = parse_options(argc, argv, &run_argv);
6680 if (request == REQ_NONE)
6681 return 0;
6683 /* Require a git repository unless when running in pager mode. */
6684 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6685 die("Not a git repository");
6687 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6688 opt_utf8 = FALSE;
6690 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6691 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6692 if (opt_iconv == ICONV_NONE)
6693 die("Failed to initialize character set conversion");
6694 }
6696 if (load_refs() == ERR)
6697 die("Failed to load refs.");
6699 foreach_view (view, i)
6700 argv_from_env(view->ops->argv, view->cmd_env);
6702 init_display();
6704 if (request == REQ_VIEW_PAGER || run_argv) {
6705 if (request == REQ_VIEW_PAGER)
6706 io_open(&VIEW(request)->io, "");
6707 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6708 die("Failed to format arguments");
6709 open_view(NULL, request, OPEN_PREPARED);
6710 request = REQ_NONE;
6711 }
6713 while (view_driver(display[current_view], request)) {
6714 int key = get_input(FALSE);
6716 view = display[current_view];
6717 request = get_keybinding(view->keymap, key);
6719 /* Some low-level request handling. This keeps access to
6720 * status_win restricted. */
6721 switch (request) {
6722 case REQ_PROMPT:
6723 {
6724 char *cmd = read_prompt(":");
6726 if (cmd) {
6727 struct view *next = VIEW(REQ_VIEW_PAGER);
6728 const char *argv[SIZEOF_ARG] = { "git" };
6729 int argc = 1;
6731 /* When running random commands, initially show the
6732 * command in the title. However, it maybe later be
6733 * overwritten if a commit line is selected. */
6734 string_ncopy(next->ref, cmd, strlen(cmd));
6736 if (!argv_from_string(argv, &argc, cmd)) {
6737 report("Too many arguments");
6738 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6739 report("Failed to format command");
6740 } else {
6741 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6742 }
6743 }
6745 request = REQ_NONE;
6746 break;
6747 }
6748 case REQ_SEARCH:
6749 case REQ_SEARCH_BACK:
6750 {
6751 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6752 char *search = read_prompt(prompt);
6754 if (search)
6755 string_ncopy(opt_search, search, strlen(search));
6756 else
6757 request = REQ_NONE;
6758 break;
6759 }
6760 default:
6761 break;
6762 }
6763 }
6765 quit(0);
6767 return 0;
6768 }