3802204dc05e6f294e4ecf3fcafbbc42b7c654b8
1 /* Copyright (c) 2006-2008 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/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
50 #else
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
53 #else
54 #include <ncurses.h>
55 #endif
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
71 #define ABS(x) ((x) >= 0 ? (x) : -(x))
72 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
75 #define STRING_SIZE(x) (sizeof(x) - 1)
77 #define SIZEOF_STR 1024 /* Default string size. */
78 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
79 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
81 /* Revision graph */
83 #define REVGRAPH_INIT 'I'
84 #define REVGRAPH_MERGE 'M'
85 #define REVGRAPH_BRANCH '+'
86 #define REVGRAPH_COMMIT '*'
87 #define REVGRAPH_BOUND '^'
89 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
91 /* This color name can be used to refer to the default term colors. */
92 #define COLOR_DEFAULT (-1)
94 #define ICONV_NONE ((iconv_t) -1)
95 #ifndef ICONV_CONST
96 #define ICONV_CONST /* nothing */
97 #endif
99 /* The format and size of the date column in the main view. */
100 #define DATE_FORMAT "%Y-%m-%d %H:%M"
101 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
103 #define AUTHOR_COLS 20
104 #define ID_COLS 8
106 /* The default interval between line numbers. */
107 #define NUMBER_INTERVAL 5
109 #define TAB_SIZE 8
111 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
113 #define NULL_ID "0000000000000000000000000000000000000000"
115 #ifndef GIT_CONFIG
116 #define GIT_CONFIG "git config"
117 #endif
119 #define TIG_LS_REMOTE \
120 "git ls-remote . 2>/dev/null"
122 #define TIG_DIFF_CMD \
123 "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
125 #define TIG_LOG_CMD \
126 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
128 #define TIG_MAIN_CMD \
129 "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
131 #define TIG_TREE_CMD \
132 "git ls-tree %s %s"
134 #define TIG_BLOB_CMD \
135 "git cat-file blob %s"
137 /* XXX: Needs to be defined to the empty string. */
138 #define TIG_HELP_CMD ""
139 #define TIG_PAGER_CMD ""
140 #define TIG_STATUS_CMD ""
141 #define TIG_STAGE_CMD ""
142 #define TIG_BLAME_CMD ""
144 /* Some ascii-shorthands fitted into the ncurses namespace. */
145 #define KEY_TAB '\t'
146 #define KEY_RETURN '\r'
147 #define KEY_ESC 27
150 struct ref {
151 char *name; /* Ref name; tag or head names are shortened. */
152 char id[SIZEOF_REV]; /* Commit SHA1 ID */
153 unsigned int head:1; /* Is it the current HEAD? */
154 unsigned int tag:1; /* Is it a tag? */
155 unsigned int ltag:1; /* If so, is the tag local? */
156 unsigned int remote:1; /* Is it a remote ref? */
157 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
158 unsigned int next:1; /* For ref lists: are there more refs? */
159 };
161 static struct ref **get_refs(char *id);
163 struct int_map {
164 const char *name;
165 int namelen;
166 int value;
167 };
169 static int
170 set_from_int_map(struct int_map *map, size_t map_size,
171 int *value, const char *name, int namelen)
172 {
174 int i;
176 for (i = 0; i < map_size; i++)
177 if (namelen == map[i].namelen &&
178 !strncasecmp(name, map[i].name, namelen)) {
179 *value = map[i].value;
180 return OK;
181 }
183 return ERR;
184 }
187 /*
188 * String helpers
189 */
191 static inline void
192 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
193 {
194 if (srclen > dstlen - 1)
195 srclen = dstlen - 1;
197 strncpy(dst, src, srclen);
198 dst[srclen] = 0;
199 }
201 /* Shorthands for safely copying into a fixed buffer. */
203 #define string_copy(dst, src) \
204 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
206 #define string_ncopy(dst, src, srclen) \
207 string_ncopy_do(dst, sizeof(dst), src, srclen)
209 #define string_copy_rev(dst, src) \
210 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
212 #define string_add(dst, from, src) \
213 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
215 static char *
216 chomp_string(char *name)
217 {
218 int namelen;
220 while (isspace(*name))
221 name++;
223 namelen = strlen(name) - 1;
224 while (namelen > 0 && isspace(name[namelen]))
225 name[namelen--] = 0;
227 return name;
228 }
230 static bool
231 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
232 {
233 va_list args;
234 size_t pos = bufpos ? *bufpos : 0;
236 va_start(args, fmt);
237 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
238 va_end(args);
240 if (bufpos)
241 *bufpos = pos;
243 return pos >= bufsize ? FALSE : TRUE;
244 }
246 #define string_format(buf, fmt, args...) \
247 string_nformat(buf, sizeof(buf), NULL, fmt, args)
249 #define string_format_from(buf, from, fmt, args...) \
250 string_nformat(buf, sizeof(buf), from, fmt, args)
252 static int
253 string_enum_compare(const char *str1, const char *str2, int len)
254 {
255 size_t i;
257 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
259 /* Diff-Header == DIFF_HEADER */
260 for (i = 0; i < len; i++) {
261 if (toupper(str1[i]) == toupper(str2[i]))
262 continue;
264 if (string_enum_sep(str1[i]) &&
265 string_enum_sep(str2[i]))
266 continue;
268 return str1[i] - str2[i];
269 }
271 return 0;
272 }
274 /* Shell quoting
275 *
276 * NOTE: The following is a slightly modified copy of the git project's shell
277 * quoting routines found in the quote.c file.
278 *
279 * Help to copy the thing properly quoted for the shell safety. any single
280 * quote is replaced with '\'', any exclamation point is replaced with '\!',
281 * and the whole thing is enclosed in a
282 *
283 * E.g.
284 * original sq_quote result
285 * name ==> name ==> 'name'
286 * a b ==> a b ==> 'a b'
287 * a'b ==> a'\''b ==> 'a'\''b'
288 * a!b ==> a'\!'b ==> 'a'\!'b'
289 */
291 static size_t
292 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
293 {
294 char c;
296 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
298 BUFPUT('\'');
299 while ((c = *src++)) {
300 if (c == '\'' || c == '!') {
301 BUFPUT('\'');
302 BUFPUT('\\');
303 BUFPUT(c);
304 BUFPUT('\'');
305 } else {
306 BUFPUT(c);
307 }
308 }
309 BUFPUT('\'');
311 if (bufsize < SIZEOF_STR)
312 buf[bufsize] = 0;
314 return bufsize;
315 }
318 /*
319 * User requests
320 */
322 #define REQ_INFO \
323 /* XXX: Keep the view request first and in sync with views[]. */ \
324 REQ_GROUP("View switching") \
325 REQ_(VIEW_MAIN, "Show main view"), \
326 REQ_(VIEW_DIFF, "Show diff view"), \
327 REQ_(VIEW_LOG, "Show log view"), \
328 REQ_(VIEW_TREE, "Show tree view"), \
329 REQ_(VIEW_BLOB, "Show blob view"), \
330 REQ_(VIEW_BLAME, "Show blame view"), \
331 REQ_(VIEW_HELP, "Show help page"), \
332 REQ_(VIEW_PAGER, "Show pager view"), \
333 REQ_(VIEW_STATUS, "Show status view"), \
334 REQ_(VIEW_STAGE, "Show stage view"), \
335 \
336 REQ_GROUP("View manipulation") \
337 REQ_(ENTER, "Enter current line and scroll"), \
338 REQ_(NEXT, "Move to next"), \
339 REQ_(PREVIOUS, "Move to previous"), \
340 REQ_(VIEW_NEXT, "Move focus to next view"), \
341 REQ_(REFRESH, "Reload and refresh"), \
342 REQ_(MAXIMIZE, "Maximize the current view"), \
343 REQ_(VIEW_CLOSE, "Close the current view"), \
344 REQ_(QUIT, "Close all views and quit"), \
345 \
346 REQ_GROUP("Cursor navigation") \
347 REQ_(MOVE_UP, "Move cursor one line up"), \
348 REQ_(MOVE_DOWN, "Move cursor one line down"), \
349 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
350 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
351 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
352 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
353 \
354 REQ_GROUP("Scrolling") \
355 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
356 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
357 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
358 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
359 \
360 REQ_GROUP("Searching") \
361 REQ_(SEARCH, "Search the view"), \
362 REQ_(SEARCH_BACK, "Search backwards in the view"), \
363 REQ_(FIND_NEXT, "Find next search match"), \
364 REQ_(FIND_PREV, "Find previous search match"), \
365 \
366 REQ_GROUP("Misc") \
367 REQ_(PROMPT, "Bring up the prompt"), \
368 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
369 REQ_(SCREEN_RESIZE, "Resize the screen"), \
370 REQ_(SHOW_VERSION, "Show version information"), \
371 REQ_(STOP_LOADING, "Stop all loading views"), \
372 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
373 REQ_(TOGGLE_DATE, "Toggle date display"), \
374 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
375 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
376 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
377 REQ_(STATUS_UPDATE, "Update file status"), \
378 REQ_(STATUS_MERGE, "Merge file using external tool"), \
379 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
380 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
381 REQ_(EDIT, "Open in editor"), \
382 REQ_(NONE, "Do nothing")
385 /* User action requests. */
386 enum request {
387 #define REQ_GROUP(help)
388 #define REQ_(req, help) REQ_##req
390 /* Offset all requests to avoid conflicts with ncurses getch values. */
391 REQ_OFFSET = KEY_MAX + 1,
392 REQ_INFO
394 #undef REQ_GROUP
395 #undef REQ_
396 };
398 struct request_info {
399 enum request request;
400 char *name;
401 int namelen;
402 char *help;
403 };
405 static struct request_info req_info[] = {
406 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
407 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
408 REQ_INFO
409 #undef REQ_GROUP
410 #undef REQ_
411 };
413 static enum request
414 get_request(const char *name)
415 {
416 int namelen = strlen(name);
417 int i;
419 for (i = 0; i < ARRAY_SIZE(req_info); i++)
420 if (req_info[i].namelen == namelen &&
421 !string_enum_compare(req_info[i].name, name, namelen))
422 return req_info[i].request;
424 return REQ_NONE;
425 }
428 /*
429 * Options
430 */
432 static const char usage[] =
433 "tig " TIG_VERSION " (" __DATE__ ")\n"
434 "\n"
435 "Usage: tig [options] [revs] [--] [paths]\n"
436 " or: tig show [options] [revs] [--] [paths]\n"
437 " or: tig blame [rev] path\n"
438 " or: tig status\n"
439 " or: tig < [git command output]\n"
440 "\n"
441 "Options:\n"
442 " -v, --version Show version and exit\n"
443 " -h, --help Show help message and exit";
445 /* Option and state variables. */
446 static bool opt_date = TRUE;
447 static bool opt_author = TRUE;
448 static bool opt_line_number = FALSE;
449 static bool opt_line_graphics = TRUE;
450 static bool opt_rev_graph = FALSE;
451 static bool opt_show_refs = TRUE;
452 static int opt_num_interval = NUMBER_INTERVAL;
453 static int opt_tab_size = TAB_SIZE;
454 static enum request opt_request = REQ_VIEW_MAIN;
455 static char opt_cmd[SIZEOF_STR] = "";
456 static char opt_path[SIZEOF_STR] = "";
457 static char opt_file[SIZEOF_STR] = "";
458 static char opt_ref[SIZEOF_REF] = "";
459 static char opt_head[SIZEOF_REF] = "";
460 static char opt_remote[SIZEOF_REF] = "";
461 static bool opt_no_head = TRUE;
462 static FILE *opt_pipe = NULL;
463 static char opt_encoding[20] = "UTF-8";
464 static bool opt_utf8 = TRUE;
465 static char opt_codeset[20] = "UTF-8";
466 static iconv_t opt_iconv = ICONV_NONE;
467 static char opt_search[SIZEOF_STR] = "";
468 static char opt_cdup[SIZEOF_STR] = "";
469 static char opt_git_dir[SIZEOF_STR] = "";
470 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
471 static char opt_editor[SIZEOF_STR] = "";
473 static bool
474 parse_options(int argc, char *argv[])
475 {
476 size_t buf_size;
477 char *subcommand;
478 bool seen_dashdash = FALSE;
479 int i;
481 if (!isatty(STDIN_FILENO)) {
482 opt_request = REQ_VIEW_PAGER;
483 opt_pipe = stdin;
484 return TRUE;
485 }
487 if (argc <= 1)
488 return TRUE;
490 subcommand = argv[1];
491 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
492 opt_request = REQ_VIEW_STATUS;
493 if (!strcmp(subcommand, "-S"))
494 warn("`-S' has been deprecated; use `tig status' instead");
495 if (argc > 2)
496 warn("ignoring arguments after `%s'", subcommand);
497 return TRUE;
499 } else if (!strcmp(subcommand, "blame")) {
500 opt_request = REQ_VIEW_BLAME;
501 if (argc <= 2 || argc > 4)
502 die("invalid number of options to blame\n\n%s", usage);
504 i = 2;
505 if (argc == 4) {
506 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
507 i++;
508 }
510 string_ncopy(opt_file, argv[i], strlen(argv[i]));
511 return TRUE;
513 } else if (!strcmp(subcommand, "show")) {
514 opt_request = REQ_VIEW_DIFF;
516 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
517 opt_request = subcommand[0] == 'l'
518 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
519 warn("`tig %s' has been deprecated", subcommand);
521 } else {
522 subcommand = NULL;
523 }
525 if (!subcommand)
526 /* XXX: This is vulnerable to the user overriding
527 * options required for the main view parser. */
528 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
529 else
530 string_format(opt_cmd, "git %s", subcommand);
532 buf_size = strlen(opt_cmd);
534 for (i = 1 + !!subcommand; i < argc; i++) {
535 char *opt = argv[i];
537 if (seen_dashdash || !strcmp(opt, "--")) {
538 seen_dashdash = TRUE;
540 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
541 printf("tig version %s\n", TIG_VERSION);
542 return FALSE;
544 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
545 printf("%s\n", usage);
546 return FALSE;
547 }
549 opt_cmd[buf_size++] = ' ';
550 buf_size = sq_quote(opt_cmd, buf_size, opt);
551 if (buf_size >= sizeof(opt_cmd))
552 die("command too long");
553 }
555 opt_cmd[buf_size] = 0;
557 return TRUE;
558 }
561 /*
562 * Line-oriented content detection.
563 */
565 #define LINE_INFO \
566 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
567 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
568 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
569 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
570 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
571 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
572 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
573 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
574 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
575 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
576 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
577 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
578 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
579 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
580 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
581 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
582 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
583 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
584 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
585 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
586 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
587 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
588 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
589 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
590 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
591 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
592 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
593 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
594 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
595 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
596 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
597 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
598 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
599 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
600 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
601 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
602 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
603 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
604 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
605 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
606 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
607 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
608 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
609 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
610 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
611 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
612 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
613 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
614 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
615 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
616 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
617 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
618 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
619 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
621 enum line_type {
622 #define LINE(type, line, fg, bg, attr) \
623 LINE_##type
624 LINE_INFO,
625 LINE_NONE
626 #undef LINE
627 };
629 struct line_info {
630 const char *name; /* Option name. */
631 int namelen; /* Size of option name. */
632 const char *line; /* The start of line to match. */
633 int linelen; /* Size of string to match. */
634 int fg, bg, attr; /* Color and text attributes for the lines. */
635 };
637 static struct line_info line_info[] = {
638 #define LINE(type, line, fg, bg, attr) \
639 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
640 LINE_INFO
641 #undef LINE
642 };
644 static enum line_type
645 get_line_type(char *line)
646 {
647 int linelen = strlen(line);
648 enum line_type type;
650 for (type = 0; type < ARRAY_SIZE(line_info); type++)
651 /* Case insensitive search matches Signed-off-by lines better. */
652 if (linelen >= line_info[type].linelen &&
653 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
654 return type;
656 return LINE_DEFAULT;
657 }
659 static inline int
660 get_line_attr(enum line_type type)
661 {
662 assert(type < ARRAY_SIZE(line_info));
663 return COLOR_PAIR(type) | line_info[type].attr;
664 }
666 static struct line_info *
667 get_line_info(char *name)
668 {
669 size_t namelen = strlen(name);
670 enum line_type type;
672 for (type = 0; type < ARRAY_SIZE(line_info); type++)
673 if (namelen == line_info[type].namelen &&
674 !string_enum_compare(line_info[type].name, name, namelen))
675 return &line_info[type];
677 return NULL;
678 }
680 static void
681 init_colors(void)
682 {
683 int default_bg = line_info[LINE_DEFAULT].bg;
684 int default_fg = line_info[LINE_DEFAULT].fg;
685 enum line_type type;
687 start_color();
689 if (assume_default_colors(default_fg, default_bg) == ERR) {
690 default_bg = COLOR_BLACK;
691 default_fg = COLOR_WHITE;
692 }
694 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
695 struct line_info *info = &line_info[type];
696 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
697 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
699 init_pair(type, fg, bg);
700 }
701 }
703 struct line {
704 enum line_type type;
706 /* State flags */
707 unsigned int selected:1;
708 unsigned int dirty:1;
710 void *data; /* User data */
711 };
714 /*
715 * Keys
716 */
718 struct keybinding {
719 int alias;
720 enum request request;
721 struct keybinding *next;
722 };
724 static struct keybinding default_keybindings[] = {
725 /* View switching */
726 { 'm', REQ_VIEW_MAIN },
727 { 'd', REQ_VIEW_DIFF },
728 { 'l', REQ_VIEW_LOG },
729 { 't', REQ_VIEW_TREE },
730 { 'f', REQ_VIEW_BLOB },
731 { 'B', REQ_VIEW_BLAME },
732 { 'p', REQ_VIEW_PAGER },
733 { 'h', REQ_VIEW_HELP },
734 { 'S', REQ_VIEW_STATUS },
735 { 'c', REQ_VIEW_STAGE },
737 /* View manipulation */
738 { 'q', REQ_VIEW_CLOSE },
739 { KEY_TAB, REQ_VIEW_NEXT },
740 { KEY_RETURN, REQ_ENTER },
741 { KEY_UP, REQ_PREVIOUS },
742 { KEY_DOWN, REQ_NEXT },
743 { 'R', REQ_REFRESH },
744 { KEY_F(5), REQ_REFRESH },
745 { 'O', REQ_MAXIMIZE },
747 /* Cursor navigation */
748 { 'k', REQ_MOVE_UP },
749 { 'j', REQ_MOVE_DOWN },
750 { KEY_HOME, REQ_MOVE_FIRST_LINE },
751 { KEY_END, REQ_MOVE_LAST_LINE },
752 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
753 { ' ', REQ_MOVE_PAGE_DOWN },
754 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
755 { 'b', REQ_MOVE_PAGE_UP },
756 { '-', REQ_MOVE_PAGE_UP },
758 /* Scrolling */
759 { KEY_IC, REQ_SCROLL_LINE_UP },
760 { KEY_DC, REQ_SCROLL_LINE_DOWN },
761 { 'w', REQ_SCROLL_PAGE_UP },
762 { 's', REQ_SCROLL_PAGE_DOWN },
764 /* Searching */
765 { '/', REQ_SEARCH },
766 { '?', REQ_SEARCH_BACK },
767 { 'n', REQ_FIND_NEXT },
768 { 'N', REQ_FIND_PREV },
770 /* Misc */
771 { 'Q', REQ_QUIT },
772 { 'z', REQ_STOP_LOADING },
773 { 'v', REQ_SHOW_VERSION },
774 { 'r', REQ_SCREEN_REDRAW },
775 { '.', REQ_TOGGLE_LINENO },
776 { 'D', REQ_TOGGLE_DATE },
777 { 'A', REQ_TOGGLE_AUTHOR },
778 { 'g', REQ_TOGGLE_REV_GRAPH },
779 { 'F', REQ_TOGGLE_REFS },
780 { ':', REQ_PROMPT },
781 { 'u', REQ_STATUS_UPDATE },
782 { 'M', REQ_STATUS_MERGE },
783 { '@', REQ_STAGE_NEXT },
784 { ',', REQ_TREE_PARENT },
785 { 'e', REQ_EDIT },
787 /* Using the ncurses SIGWINCH handler. */
788 { KEY_RESIZE, REQ_SCREEN_RESIZE },
789 };
791 #define KEYMAP_INFO \
792 KEYMAP_(GENERIC), \
793 KEYMAP_(MAIN), \
794 KEYMAP_(DIFF), \
795 KEYMAP_(LOG), \
796 KEYMAP_(TREE), \
797 KEYMAP_(BLOB), \
798 KEYMAP_(BLAME), \
799 KEYMAP_(PAGER), \
800 KEYMAP_(HELP), \
801 KEYMAP_(STATUS), \
802 KEYMAP_(STAGE)
804 enum keymap {
805 #define KEYMAP_(name) KEYMAP_##name
806 KEYMAP_INFO
807 #undef KEYMAP_
808 };
810 static struct int_map keymap_table[] = {
811 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
812 KEYMAP_INFO
813 #undef KEYMAP_
814 };
816 #define set_keymap(map, name) \
817 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
819 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
821 static void
822 add_keybinding(enum keymap keymap, enum request request, int key)
823 {
824 struct keybinding *keybinding;
826 keybinding = calloc(1, sizeof(*keybinding));
827 if (!keybinding)
828 die("Failed to allocate keybinding");
830 keybinding->alias = key;
831 keybinding->request = request;
832 keybinding->next = keybindings[keymap];
833 keybindings[keymap] = keybinding;
834 }
836 /* Looks for a key binding first in the given map, then in the generic map, and
837 * lastly in the default keybindings. */
838 static enum request
839 get_keybinding(enum keymap keymap, int key)
840 {
841 struct keybinding *kbd;
842 int i;
844 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
845 if (kbd->alias == key)
846 return kbd->request;
848 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
849 if (kbd->alias == key)
850 return kbd->request;
852 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
853 if (default_keybindings[i].alias == key)
854 return default_keybindings[i].request;
856 return (enum request) key;
857 }
860 struct key {
861 char *name;
862 int value;
863 };
865 static struct key key_table[] = {
866 { "Enter", KEY_RETURN },
867 { "Space", ' ' },
868 { "Backspace", KEY_BACKSPACE },
869 { "Tab", KEY_TAB },
870 { "Escape", KEY_ESC },
871 { "Left", KEY_LEFT },
872 { "Right", KEY_RIGHT },
873 { "Up", KEY_UP },
874 { "Down", KEY_DOWN },
875 { "Insert", KEY_IC },
876 { "Delete", KEY_DC },
877 { "Hash", '#' },
878 { "Home", KEY_HOME },
879 { "End", KEY_END },
880 { "PageUp", KEY_PPAGE },
881 { "PageDown", KEY_NPAGE },
882 { "F1", KEY_F(1) },
883 { "F2", KEY_F(2) },
884 { "F3", KEY_F(3) },
885 { "F4", KEY_F(4) },
886 { "F5", KEY_F(5) },
887 { "F6", KEY_F(6) },
888 { "F7", KEY_F(7) },
889 { "F8", KEY_F(8) },
890 { "F9", KEY_F(9) },
891 { "F10", KEY_F(10) },
892 { "F11", KEY_F(11) },
893 { "F12", KEY_F(12) },
894 };
896 static int
897 get_key_value(const char *name)
898 {
899 int i;
901 for (i = 0; i < ARRAY_SIZE(key_table); i++)
902 if (!strcasecmp(key_table[i].name, name))
903 return key_table[i].value;
905 if (strlen(name) == 1 && isprint(*name))
906 return (int) *name;
908 return ERR;
909 }
911 static char *
912 get_key_name(int key_value)
913 {
914 static char key_char[] = "'X'";
915 char *seq = NULL;
916 int key;
918 for (key = 0; key < ARRAY_SIZE(key_table); key++)
919 if (key_table[key].value == key_value)
920 seq = key_table[key].name;
922 if (seq == NULL &&
923 key_value < 127 &&
924 isprint(key_value)) {
925 key_char[1] = (char) key_value;
926 seq = key_char;
927 }
929 return seq ? seq : "'?'";
930 }
932 static char *
933 get_key(enum request request)
934 {
935 static char buf[BUFSIZ];
936 size_t pos = 0;
937 char *sep = "";
938 int i;
940 buf[pos] = 0;
942 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
943 struct keybinding *keybinding = &default_keybindings[i];
945 if (keybinding->request != request)
946 continue;
948 if (!string_format_from(buf, &pos, "%s%s", sep,
949 get_key_name(keybinding->alias)))
950 return "Too many keybindings!";
951 sep = ", ";
952 }
954 return buf;
955 }
957 struct run_request {
958 enum keymap keymap;
959 int key;
960 char cmd[SIZEOF_STR];
961 };
963 static struct run_request *run_request;
964 static size_t run_requests;
966 static enum request
967 add_run_request(enum keymap keymap, int key, int argc, char **argv)
968 {
969 struct run_request *req;
970 char cmd[SIZEOF_STR];
971 size_t bufpos;
973 for (bufpos = 0; argc > 0; argc--, argv++)
974 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
975 return REQ_NONE;
977 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
978 if (!req)
979 return REQ_NONE;
981 run_request = req;
982 req = &run_request[run_requests++];
983 string_copy(req->cmd, cmd);
984 req->keymap = keymap;
985 req->key = key;
987 return REQ_NONE + run_requests;
988 }
990 static struct run_request *
991 get_run_request(enum request request)
992 {
993 if (request <= REQ_NONE)
994 return NULL;
995 return &run_request[request - REQ_NONE - 1];
996 }
998 static void
999 add_builtin_run_requests(void)
1000 {
1001 struct {
1002 enum keymap keymap;
1003 int key;
1004 char *argv[1];
1005 } reqs[] = {
1006 { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
1007 { KEYMAP_GENERIC, 'G', { "git gc" } },
1008 };
1009 int i;
1011 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1012 enum request req;
1014 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1015 if (req != REQ_NONE)
1016 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1017 }
1018 }
1020 /*
1021 * User config file handling.
1022 */
1024 static struct int_map color_map[] = {
1025 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1026 COLOR_MAP(DEFAULT),
1027 COLOR_MAP(BLACK),
1028 COLOR_MAP(BLUE),
1029 COLOR_MAP(CYAN),
1030 COLOR_MAP(GREEN),
1031 COLOR_MAP(MAGENTA),
1032 COLOR_MAP(RED),
1033 COLOR_MAP(WHITE),
1034 COLOR_MAP(YELLOW),
1035 };
1037 #define set_color(color, name) \
1038 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1040 static struct int_map attr_map[] = {
1041 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1042 ATTR_MAP(NORMAL),
1043 ATTR_MAP(BLINK),
1044 ATTR_MAP(BOLD),
1045 ATTR_MAP(DIM),
1046 ATTR_MAP(REVERSE),
1047 ATTR_MAP(STANDOUT),
1048 ATTR_MAP(UNDERLINE),
1049 };
1051 #define set_attribute(attr, name) \
1052 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1054 static int config_lineno;
1055 static bool config_errors;
1056 static char *config_msg;
1058 /* Wants: object fgcolor bgcolor [attr] */
1059 static int
1060 option_color_command(int argc, char *argv[])
1061 {
1062 struct line_info *info;
1064 if (argc != 3 && argc != 4) {
1065 config_msg = "Wrong number of arguments given to color command";
1066 return ERR;
1067 }
1069 info = get_line_info(argv[0]);
1070 if (!info) {
1071 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1072 info = get_line_info("delimiter");
1074 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1075 info = get_line_info("date");
1077 } else {
1078 config_msg = "Unknown color name";
1079 return ERR;
1080 }
1081 }
1083 if (set_color(&info->fg, argv[1]) == ERR ||
1084 set_color(&info->bg, argv[2]) == ERR) {
1085 config_msg = "Unknown color";
1086 return ERR;
1087 }
1089 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1090 config_msg = "Unknown attribute";
1091 return ERR;
1092 }
1094 return OK;
1095 }
1097 static bool parse_bool(const char *s)
1098 {
1099 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1100 !strcmp(s, "yes")) ? TRUE : FALSE;
1101 }
1103 /* Wants: name = value */
1104 static int
1105 option_set_command(int argc, char *argv[])
1106 {
1107 if (argc != 3) {
1108 config_msg = "Wrong number of arguments given to set command";
1109 return ERR;
1110 }
1112 if (strcmp(argv[1], "=")) {
1113 config_msg = "No value assigned";
1114 return ERR;
1115 }
1117 if (!strcmp(argv[0], "show-author")) {
1118 opt_author = parse_bool(argv[2]);
1119 return OK;
1120 }
1122 if (!strcmp(argv[0], "show-date")) {
1123 opt_date = parse_bool(argv[2]);
1124 return OK;
1125 }
1127 if (!strcmp(argv[0], "show-rev-graph")) {
1128 opt_rev_graph = parse_bool(argv[2]);
1129 return OK;
1130 }
1132 if (!strcmp(argv[0], "show-refs")) {
1133 opt_show_refs = parse_bool(argv[2]);
1134 return OK;
1135 }
1137 if (!strcmp(argv[0], "show-line-numbers")) {
1138 opt_line_number = parse_bool(argv[2]);
1139 return OK;
1140 }
1142 if (!strcmp(argv[0], "line-graphics")) {
1143 opt_line_graphics = parse_bool(argv[2]);
1144 return OK;
1145 }
1147 if (!strcmp(argv[0], "line-number-interval")) {
1148 opt_num_interval = atoi(argv[2]);
1149 return OK;
1150 }
1152 if (!strcmp(argv[0], "tab-size")) {
1153 opt_tab_size = atoi(argv[2]);
1154 return OK;
1155 }
1157 if (!strcmp(argv[0], "commit-encoding")) {
1158 char *arg = argv[2];
1159 int delimiter = *arg;
1160 int i;
1162 switch (delimiter) {
1163 case '"':
1164 case '\'':
1165 for (arg++, i = 0; arg[i]; i++)
1166 if (arg[i] == delimiter) {
1167 arg[i] = 0;
1168 break;
1169 }
1170 default:
1171 string_ncopy(opt_encoding, arg, strlen(arg));
1172 return OK;
1173 }
1174 }
1176 config_msg = "Unknown variable name";
1177 return ERR;
1178 }
1180 /* Wants: mode request key */
1181 static int
1182 option_bind_command(int argc, char *argv[])
1183 {
1184 enum request request;
1185 int keymap;
1186 int key;
1188 if (argc < 3) {
1189 config_msg = "Wrong number of arguments given to bind command";
1190 return ERR;
1191 }
1193 if (set_keymap(&keymap, argv[0]) == ERR) {
1194 config_msg = "Unknown key map";
1195 return ERR;
1196 }
1198 key = get_key_value(argv[1]);
1199 if (key == ERR) {
1200 config_msg = "Unknown key";
1201 return ERR;
1202 }
1204 request = get_request(argv[2]);
1205 if (request == REQ_NONE) {
1206 const char *obsolete[] = { "cherry-pick" };
1207 size_t namelen = strlen(argv[2]);
1208 int i;
1210 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1211 if (namelen == strlen(obsolete[i]) &&
1212 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1213 config_msg = "Obsolete request name";
1214 return ERR;
1215 }
1216 }
1217 }
1218 if (request == REQ_NONE && *argv[2]++ == '!')
1219 request = add_run_request(keymap, key, argc - 2, argv + 2);
1220 if (request == REQ_NONE) {
1221 config_msg = "Unknown request name";
1222 return ERR;
1223 }
1225 add_keybinding(keymap, request, key);
1227 return OK;
1228 }
1230 static int
1231 set_option(char *opt, char *value)
1232 {
1233 char *argv[16];
1234 int valuelen;
1235 int argc = 0;
1237 /* Tokenize */
1238 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1239 argv[argc++] = value;
1240 value += valuelen;
1242 /* Nothing more to tokenize or last available token. */
1243 if (!*value || argc >= ARRAY_SIZE(argv))
1244 break;
1246 *value++ = 0;
1247 while (isspace(*value))
1248 value++;
1249 }
1251 if (!strcmp(opt, "color"))
1252 return option_color_command(argc, argv);
1254 if (!strcmp(opt, "set"))
1255 return option_set_command(argc, argv);
1257 if (!strcmp(opt, "bind"))
1258 return option_bind_command(argc, argv);
1260 config_msg = "Unknown option command";
1261 return ERR;
1262 }
1264 static int
1265 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1266 {
1267 int status = OK;
1269 config_lineno++;
1270 config_msg = "Internal error";
1272 /* Check for comment markers, since read_properties() will
1273 * only ensure opt and value are split at first " \t". */
1274 optlen = strcspn(opt, "#");
1275 if (optlen == 0)
1276 return OK;
1278 if (opt[optlen] != 0) {
1279 config_msg = "No option value";
1280 status = ERR;
1282 } else {
1283 /* Look for comment endings in the value. */
1284 size_t len = strcspn(value, "#");
1286 if (len < valuelen) {
1287 valuelen = len;
1288 value[valuelen] = 0;
1289 }
1291 status = set_option(opt, value);
1292 }
1294 if (status == ERR) {
1295 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1296 config_lineno, (int) optlen, opt, config_msg);
1297 config_errors = TRUE;
1298 }
1300 /* Always keep going if errors are encountered. */
1301 return OK;
1302 }
1304 static void
1305 load_option_file(const char *path)
1306 {
1307 FILE *file;
1309 /* It's ok that the file doesn't exist. */
1310 file = fopen(path, "r");
1311 if (!file)
1312 return;
1314 config_lineno = 0;
1315 config_errors = FALSE;
1317 if (read_properties(file, " \t", read_option) == ERR ||
1318 config_errors == TRUE)
1319 fprintf(stderr, "Errors while loading %s.\n", path);
1320 }
1322 static int
1323 load_options(void)
1324 {
1325 char *home = getenv("HOME");
1326 char *tigrc_user = getenv("TIGRC_USER");
1327 char *tigrc_system = getenv("TIGRC_SYSTEM");
1328 char buf[SIZEOF_STR];
1330 add_builtin_run_requests();
1332 if (!tigrc_system) {
1333 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1334 return ERR;
1335 tigrc_system = buf;
1336 }
1337 load_option_file(tigrc_system);
1339 if (!tigrc_user) {
1340 if (!home || !string_format(buf, "%s/.tigrc", home))
1341 return ERR;
1342 tigrc_user = buf;
1343 }
1344 load_option_file(tigrc_user);
1346 return OK;
1347 }
1350 /*
1351 * The viewer
1352 */
1354 struct view;
1355 struct view_ops;
1357 /* The display array of active views and the index of the current view. */
1358 static struct view *display[2];
1359 static unsigned int current_view;
1361 /* Reading from the prompt? */
1362 static bool input_mode = FALSE;
1364 #define foreach_displayed_view(view, i) \
1365 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1367 #define displayed_views() (display[1] != NULL ? 2 : 1)
1369 /* Current head and commit ID */
1370 static char ref_blob[SIZEOF_REF] = "";
1371 static char ref_commit[SIZEOF_REF] = "HEAD";
1372 static char ref_head[SIZEOF_REF] = "HEAD";
1374 struct view {
1375 const char *name; /* View name */
1376 const char *cmd_fmt; /* Default command line format */
1377 const char *cmd_env; /* Command line set via environment */
1378 const char *id; /* Points to either of ref_{head,commit,blob} */
1380 struct view_ops *ops; /* View operations */
1382 enum keymap keymap; /* What keymap does this view have */
1383 bool git_dir; /* Whether the view requires a git directory. */
1385 char cmd[SIZEOF_STR]; /* Command buffer */
1386 char ref[SIZEOF_REF]; /* Hovered commit reference */
1387 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1389 int height, width; /* The width and height of the main window */
1390 WINDOW *win; /* The main window */
1391 WINDOW *title; /* The title window living below the main window */
1393 /* Navigation */
1394 unsigned long offset; /* Offset of the window top */
1395 unsigned long lineno; /* Current line number */
1397 /* Searching */
1398 char grep[SIZEOF_STR]; /* Search string */
1399 regex_t *regex; /* Pre-compiled regex */
1401 /* If non-NULL, points to the view that opened this view. If this view
1402 * is closed tig will switch back to the parent view. */
1403 struct view *parent;
1405 /* Buffering */
1406 size_t lines; /* Total number of lines */
1407 struct line *line; /* Line index */
1408 size_t line_alloc; /* Total number of allocated lines */
1409 size_t line_size; /* Total number of used lines */
1410 unsigned int digits; /* Number of digits in the lines member. */
1412 /* Drawing */
1413 struct line *curline; /* Line currently being drawn. */
1414 enum line_type curtype; /* Attribute currently used for drawing. */
1415 unsigned long col; /* Column when drawing. */
1417 /* Loading */
1418 FILE *pipe;
1419 time_t start_time;
1420 };
1422 struct view_ops {
1423 /* What type of content being displayed. Used in the title bar. */
1424 const char *type;
1425 /* Open and reads in all view content. */
1426 bool (*open)(struct view *view);
1427 /* Read one line; updates view->line. */
1428 bool (*read)(struct view *view, char *data);
1429 /* Draw one line; @lineno must be < view->height. */
1430 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1431 /* Depending on view handle a special requests. */
1432 enum request (*request)(struct view *view, enum request request, struct line *line);
1433 /* Search for regex in a line. */
1434 bool (*grep)(struct view *view, struct line *line);
1435 /* Select line */
1436 void (*select)(struct view *view, struct line *line);
1437 };
1439 static struct view_ops pager_ops;
1440 static struct view_ops main_ops;
1441 static struct view_ops tree_ops;
1442 static struct view_ops blob_ops;
1443 static struct view_ops blame_ops;
1444 static struct view_ops help_ops;
1445 static struct view_ops status_ops;
1446 static struct view_ops stage_ops;
1448 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1449 { name, cmd, #env, ref, ops, map, git }
1451 #define VIEW_(id, name, ops, git, ref) \
1452 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1455 static struct view views[] = {
1456 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1457 VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit),
1458 VIEW_(LOG, "log", &pager_ops, TRUE, ref_head),
1459 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1460 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1461 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1462 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1463 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1464 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1465 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1466 };
1468 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1469 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1471 #define foreach_view(view, i) \
1472 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1474 #define view_is_displayed(view) \
1475 (view == display[0] || view == display[1])
1478 enum line_graphic {
1479 LINE_GRAPHIC_VLINE
1480 };
1482 static int line_graphics[] = {
1483 /* LINE_GRAPHIC_VLINE: */ '|'
1484 };
1486 static inline void
1487 set_view_attr(struct view *view, enum line_type type)
1488 {
1489 if (!view->curline->selected && view->curtype != type) {
1490 wattrset(view->win, get_line_attr(type));
1491 wchgat(view->win, -1, 0, type, NULL);
1492 view->curtype = type;
1493 }
1494 }
1496 static int
1497 draw_chars(struct view *view, enum line_type type, const char *string,
1498 int max_len, bool use_tilde)
1499 {
1500 int len = 0;
1501 int col = 0;
1502 int trimmed = FALSE;
1504 if (max_len <= 0)
1505 return 0;
1507 if (opt_utf8) {
1508 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1509 } else {
1510 col = len = strlen(string);
1511 if (len > max_len) {
1512 if (use_tilde) {
1513 max_len -= 1;
1514 }
1515 col = len = max_len;
1516 trimmed = TRUE;
1517 }
1518 }
1520 set_view_attr(view, type);
1521 waddnstr(view->win, string, len);
1522 if (trimmed && use_tilde) {
1523 set_view_attr(view, LINE_DELIMITER);
1524 waddch(view->win, '~');
1525 col++;
1526 }
1528 return col;
1529 }
1531 static int
1532 draw_space(struct view *view, enum line_type type, int max, int spaces)
1533 {
1534 static char space[] = " ";
1535 int col = 0;
1537 spaces = MIN(max, spaces);
1539 while (spaces > 0) {
1540 int len = MIN(spaces, sizeof(space) - 1);
1542 col += draw_chars(view, type, space, spaces, FALSE);
1543 spaces -= len;
1544 }
1546 return col;
1547 }
1549 static bool
1550 draw_lineno(struct view *view, unsigned int lineno)
1551 {
1552 char number[10];
1553 int digits3 = view->digits < 3 ? 3 : view->digits;
1554 int max_number = MIN(digits3, STRING_SIZE(number));
1555 int max = view->width - view->col;
1556 int col;
1558 if (max < max_number)
1559 max_number = max;
1561 lineno += view->offset + 1;
1562 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1563 static char fmt[] = "%1ld";
1565 if (view->digits <= 9)
1566 fmt[1] = '0' + digits3;
1568 if (!string_format(number, fmt, lineno))
1569 number[0] = 0;
1570 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1571 } else {
1572 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1573 }
1575 if (col < max) {
1576 set_view_attr(view, LINE_DEFAULT);
1577 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1578 col++;
1579 }
1581 if (col < max)
1582 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1583 view->col += col;
1585 return view->width - view->col <= 0;
1586 }
1588 static bool
1589 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1590 {
1591 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1592 return view->width - view->col <= 0;
1593 }
1595 static bool
1596 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1597 {
1598 int max = view->width - view->col;
1599 int i;
1601 if (max < size)
1602 size = max;
1604 set_view_attr(view, type);
1605 /* Using waddch() instead of waddnstr() ensures that
1606 * they'll be rendered correctly for the cursor line. */
1607 for (i = 0; i < size; i++)
1608 waddch(view->win, graphic[i]);
1610 view->col += size;
1611 if (size < max) {
1612 waddch(view->win, ' ');
1613 view->col++;
1614 }
1616 return view->width - view->col <= 0;
1617 }
1619 static bool
1620 draw_field(struct view *view, enum line_type type, char *text, int len, bool trim)
1621 {
1622 int max = MIN(view->width - view->col, len);
1623 int col;
1625 if (text)
1626 col = draw_chars(view, type, text, max - 1, trim);
1627 else
1628 col = draw_space(view, type, max - 1, max - 1);
1630 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1631 return view->width - view->col <= 0;
1632 }
1634 static bool
1635 draw_date(struct view *view, struct tm *time)
1636 {
1637 char buf[DATE_COLS];
1638 char *date;
1639 int timelen = 0;
1641 if (time)
1642 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1643 date = timelen ? buf : NULL;
1645 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1646 }
1648 static bool
1649 draw_view_line(struct view *view, unsigned int lineno)
1650 {
1651 struct line *line;
1652 bool selected = (view->offset + lineno == view->lineno);
1653 bool draw_ok;
1655 assert(view_is_displayed(view));
1657 if (view->offset + lineno >= view->lines)
1658 return FALSE;
1660 line = &view->line[view->offset + lineno];
1662 wmove(view->win, lineno, 0);
1663 view->col = 0;
1664 view->curline = line;
1665 view->curtype = LINE_NONE;
1666 line->selected = FALSE;
1668 if (selected) {
1669 set_view_attr(view, LINE_CURSOR);
1670 line->selected = TRUE;
1671 view->ops->select(view, line);
1672 } else if (line->selected) {
1673 wclrtoeol(view->win);
1674 }
1676 scrollok(view->win, FALSE);
1677 draw_ok = view->ops->draw(view, line, lineno);
1678 scrollok(view->win, TRUE);
1680 return draw_ok;
1681 }
1683 static void
1684 redraw_view_dirty(struct view *view)
1685 {
1686 bool dirty = FALSE;
1687 int lineno;
1689 for (lineno = 0; lineno < view->height; lineno++) {
1690 struct line *line = &view->line[view->offset + lineno];
1692 if (!line->dirty)
1693 continue;
1694 line->dirty = 0;
1695 dirty = TRUE;
1696 if (!draw_view_line(view, lineno))
1697 break;
1698 }
1700 if (!dirty)
1701 return;
1702 redrawwin(view->win);
1703 if (input_mode)
1704 wnoutrefresh(view->win);
1705 else
1706 wrefresh(view->win);
1707 }
1709 static void
1710 redraw_view_from(struct view *view, int lineno)
1711 {
1712 assert(0 <= lineno && lineno < view->height);
1714 for (; lineno < view->height; lineno++) {
1715 if (!draw_view_line(view, lineno))
1716 break;
1717 }
1719 redrawwin(view->win);
1720 if (input_mode)
1721 wnoutrefresh(view->win);
1722 else
1723 wrefresh(view->win);
1724 }
1726 static void
1727 redraw_view(struct view *view)
1728 {
1729 wclear(view->win);
1730 redraw_view_from(view, 0);
1731 }
1734 static void
1735 update_view_title(struct view *view)
1736 {
1737 char buf[SIZEOF_STR];
1738 char state[SIZEOF_STR];
1739 size_t bufpos = 0, statelen = 0;
1741 assert(view_is_displayed(view));
1743 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1744 unsigned int view_lines = view->offset + view->height;
1745 unsigned int lines = view->lines
1746 ? MIN(view_lines, view->lines) * 100 / view->lines
1747 : 0;
1749 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1750 view->ops->type,
1751 view->lineno + 1,
1752 view->lines,
1753 lines);
1755 if (view->pipe) {
1756 time_t secs = time(NULL) - view->start_time;
1758 /* Three git seconds are a long time ... */
1759 if (secs > 2)
1760 string_format_from(state, &statelen, " %lds", secs);
1761 }
1762 }
1764 string_format_from(buf, &bufpos, "[%s]", view->name);
1765 if (*view->ref && bufpos < view->width) {
1766 size_t refsize = strlen(view->ref);
1767 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1769 if (minsize < view->width)
1770 refsize = view->width - minsize + 7;
1771 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1772 }
1774 if (statelen && bufpos < view->width) {
1775 string_format_from(buf, &bufpos, " %s", state);
1776 }
1778 if (view == display[current_view])
1779 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1780 else
1781 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1783 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1784 wclrtoeol(view->title);
1785 wmove(view->title, 0, view->width - 1);
1787 if (input_mode)
1788 wnoutrefresh(view->title);
1789 else
1790 wrefresh(view->title);
1791 }
1793 static void
1794 resize_display(void)
1795 {
1796 int offset, i;
1797 struct view *base = display[0];
1798 struct view *view = display[1] ? display[1] : display[0];
1800 /* Setup window dimensions */
1802 getmaxyx(stdscr, base->height, base->width);
1804 /* Make room for the status window. */
1805 base->height -= 1;
1807 if (view != base) {
1808 /* Horizontal split. */
1809 view->width = base->width;
1810 view->height = SCALE_SPLIT_VIEW(base->height);
1811 base->height -= view->height;
1813 /* Make room for the title bar. */
1814 view->height -= 1;
1815 }
1817 /* Make room for the title bar. */
1818 base->height -= 1;
1820 offset = 0;
1822 foreach_displayed_view (view, i) {
1823 if (!view->win) {
1824 view->win = newwin(view->height, 0, offset, 0);
1825 if (!view->win)
1826 die("Failed to create %s view", view->name);
1828 scrollok(view->win, TRUE);
1830 view->title = newwin(1, 0, offset + view->height, 0);
1831 if (!view->title)
1832 die("Failed to create title window");
1834 } else {
1835 wresize(view->win, view->height, view->width);
1836 mvwin(view->win, offset, 0);
1837 mvwin(view->title, offset + view->height, 0);
1838 }
1840 offset += view->height + 1;
1841 }
1842 }
1844 static void
1845 redraw_display(void)
1846 {
1847 struct view *view;
1848 int i;
1850 foreach_displayed_view (view, i) {
1851 redraw_view(view);
1852 update_view_title(view);
1853 }
1854 }
1856 static void
1857 update_display_cursor(struct view *view)
1858 {
1859 /* Move the cursor to the right-most column of the cursor line.
1860 *
1861 * XXX: This could turn out to be a bit expensive, but it ensures that
1862 * the cursor does not jump around. */
1863 if (view->lines) {
1864 wmove(view->win, view->lineno - view->offset, view->width - 1);
1865 wrefresh(view->win);
1866 }
1867 }
1869 /*
1870 * Navigation
1871 */
1873 /* Scrolling backend */
1874 static void
1875 do_scroll_view(struct view *view, int lines)
1876 {
1877 bool redraw_current_line = FALSE;
1879 /* The rendering expects the new offset. */
1880 view->offset += lines;
1882 assert(0 <= view->offset && view->offset < view->lines);
1883 assert(lines);
1885 /* Move current line into the view. */
1886 if (view->lineno < view->offset) {
1887 view->lineno = view->offset;
1888 redraw_current_line = TRUE;
1889 } else if (view->lineno >= view->offset + view->height) {
1890 view->lineno = view->offset + view->height - 1;
1891 redraw_current_line = TRUE;
1892 }
1894 assert(view->offset <= view->lineno && view->lineno < view->lines);
1896 /* Redraw the whole screen if scrolling is pointless. */
1897 if (view->height < ABS(lines)) {
1898 redraw_view(view);
1900 } else {
1901 int line = lines > 0 ? view->height - lines : 0;
1902 int end = line + ABS(lines);
1904 wscrl(view->win, lines);
1906 for (; line < end; line++) {
1907 if (!draw_view_line(view, line))
1908 break;
1909 }
1911 if (redraw_current_line)
1912 draw_view_line(view, view->lineno - view->offset);
1913 }
1915 redrawwin(view->win);
1916 wrefresh(view->win);
1917 report("");
1918 }
1920 /* Scroll frontend */
1921 static void
1922 scroll_view(struct view *view, enum request request)
1923 {
1924 int lines = 1;
1926 assert(view_is_displayed(view));
1928 switch (request) {
1929 case REQ_SCROLL_PAGE_DOWN:
1930 lines = view->height;
1931 case REQ_SCROLL_LINE_DOWN:
1932 if (view->offset + lines > view->lines)
1933 lines = view->lines - view->offset;
1935 if (lines == 0 || view->offset + view->height >= view->lines) {
1936 report("Cannot scroll beyond the last line");
1937 return;
1938 }
1939 break;
1941 case REQ_SCROLL_PAGE_UP:
1942 lines = view->height;
1943 case REQ_SCROLL_LINE_UP:
1944 if (lines > view->offset)
1945 lines = view->offset;
1947 if (lines == 0) {
1948 report("Cannot scroll beyond the first line");
1949 return;
1950 }
1952 lines = -lines;
1953 break;
1955 default:
1956 die("request %d not handled in switch", request);
1957 }
1959 do_scroll_view(view, lines);
1960 }
1962 /* Cursor moving */
1963 static void
1964 move_view(struct view *view, enum request request)
1965 {
1966 int scroll_steps = 0;
1967 int steps;
1969 switch (request) {
1970 case REQ_MOVE_FIRST_LINE:
1971 steps = -view->lineno;
1972 break;
1974 case REQ_MOVE_LAST_LINE:
1975 steps = view->lines - view->lineno - 1;
1976 break;
1978 case REQ_MOVE_PAGE_UP:
1979 steps = view->height > view->lineno
1980 ? -view->lineno : -view->height;
1981 break;
1983 case REQ_MOVE_PAGE_DOWN:
1984 steps = view->lineno + view->height >= view->lines
1985 ? view->lines - view->lineno - 1 : view->height;
1986 break;
1988 case REQ_MOVE_UP:
1989 steps = -1;
1990 break;
1992 case REQ_MOVE_DOWN:
1993 steps = 1;
1994 break;
1996 default:
1997 die("request %d not handled in switch", request);
1998 }
2000 if (steps <= 0 && view->lineno == 0) {
2001 report("Cannot move beyond the first line");
2002 return;
2004 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2005 report("Cannot move beyond the last line");
2006 return;
2007 }
2009 /* Move the current line */
2010 view->lineno += steps;
2011 assert(0 <= view->lineno && view->lineno < view->lines);
2013 /* Check whether the view needs to be scrolled */
2014 if (view->lineno < view->offset ||
2015 view->lineno >= view->offset + view->height) {
2016 scroll_steps = steps;
2017 if (steps < 0 && -steps > view->offset) {
2018 scroll_steps = -view->offset;
2020 } else if (steps > 0) {
2021 if (view->lineno == view->lines - 1 &&
2022 view->lines > view->height) {
2023 scroll_steps = view->lines - view->offset - 1;
2024 if (scroll_steps >= view->height)
2025 scroll_steps -= view->height - 1;
2026 }
2027 }
2028 }
2030 if (!view_is_displayed(view)) {
2031 view->offset += scroll_steps;
2032 assert(0 <= view->offset && view->offset < view->lines);
2033 view->ops->select(view, &view->line[view->lineno]);
2034 return;
2035 }
2037 /* Repaint the old "current" line if we be scrolling */
2038 if (ABS(steps) < view->height)
2039 draw_view_line(view, view->lineno - steps - view->offset);
2041 if (scroll_steps) {
2042 do_scroll_view(view, scroll_steps);
2043 return;
2044 }
2046 /* Draw the current line */
2047 draw_view_line(view, view->lineno - view->offset);
2049 redrawwin(view->win);
2050 wrefresh(view->win);
2051 report("");
2052 }
2055 /*
2056 * Searching
2057 */
2059 static void search_view(struct view *view, enum request request);
2061 static bool
2062 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2063 {
2064 assert(view_is_displayed(view));
2066 if (!view->ops->grep(view, line))
2067 return FALSE;
2069 if (lineno - view->offset >= view->height) {
2070 view->offset = lineno;
2071 view->lineno = lineno;
2072 redraw_view(view);
2074 } else {
2075 unsigned long old_lineno = view->lineno - view->offset;
2077 view->lineno = lineno;
2078 draw_view_line(view, old_lineno);
2080 draw_view_line(view, view->lineno - view->offset);
2081 redrawwin(view->win);
2082 wrefresh(view->win);
2083 }
2085 report("Line %ld matches '%s'", lineno + 1, view->grep);
2086 return TRUE;
2087 }
2089 static void
2090 find_next(struct view *view, enum request request)
2091 {
2092 unsigned long lineno = view->lineno;
2093 int direction;
2095 if (!*view->grep) {
2096 if (!*opt_search)
2097 report("No previous search");
2098 else
2099 search_view(view, request);
2100 return;
2101 }
2103 switch (request) {
2104 case REQ_SEARCH:
2105 case REQ_FIND_NEXT:
2106 direction = 1;
2107 break;
2109 case REQ_SEARCH_BACK:
2110 case REQ_FIND_PREV:
2111 direction = -1;
2112 break;
2114 default:
2115 return;
2116 }
2118 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2119 lineno += direction;
2121 /* Note, lineno is unsigned long so will wrap around in which case it
2122 * will become bigger than view->lines. */
2123 for (; lineno < view->lines; lineno += direction) {
2124 struct line *line = &view->line[lineno];
2126 if (find_next_line(view, lineno, line))
2127 return;
2128 }
2130 report("No match found for '%s'", view->grep);
2131 }
2133 static void
2134 search_view(struct view *view, enum request request)
2135 {
2136 int regex_err;
2138 if (view->regex) {
2139 regfree(view->regex);
2140 *view->grep = 0;
2141 } else {
2142 view->regex = calloc(1, sizeof(*view->regex));
2143 if (!view->regex)
2144 return;
2145 }
2147 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2148 if (regex_err != 0) {
2149 char buf[SIZEOF_STR] = "unknown error";
2151 regerror(regex_err, view->regex, buf, sizeof(buf));
2152 report("Search failed: %s", buf);
2153 return;
2154 }
2156 string_copy(view->grep, opt_search);
2158 find_next(view, request);
2159 }
2161 /*
2162 * Incremental updating
2163 */
2165 static void
2166 end_update(struct view *view, bool force)
2167 {
2168 if (!view->pipe)
2169 return;
2170 while (!view->ops->read(view, NULL))
2171 if (!force)
2172 return;
2173 set_nonblocking_input(FALSE);
2174 if (view->pipe == stdin)
2175 fclose(view->pipe);
2176 else
2177 pclose(view->pipe);
2178 view->pipe = NULL;
2179 }
2181 static bool
2182 begin_update(struct view *view)
2183 {
2184 if (opt_cmd[0]) {
2185 string_copy(view->cmd, opt_cmd);
2186 opt_cmd[0] = 0;
2187 /* When running random commands, initially show the
2188 * command in the title. However, it maybe later be
2189 * overwritten if a commit line is selected. */
2190 if (view == VIEW(REQ_VIEW_PAGER))
2191 string_copy(view->ref, view->cmd);
2192 else
2193 view->ref[0] = 0;
2195 } else if (view == VIEW(REQ_VIEW_TREE)) {
2196 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2197 char path[SIZEOF_STR];
2199 if (strcmp(view->vid, view->id))
2200 opt_path[0] = path[0] = 0;
2201 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2202 return FALSE;
2204 if (!string_format(view->cmd, format, view->id, path))
2205 return FALSE;
2207 } else {
2208 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2209 const char *id = view->id;
2211 if (!string_format(view->cmd, format, id, id, id, id, id))
2212 return FALSE;
2214 /* Put the current ref_* value to the view title ref
2215 * member. This is needed by the blob view. Most other
2216 * views sets it automatically after loading because the
2217 * first line is a commit line. */
2218 string_copy_rev(view->ref, view->id);
2219 }
2221 /* Special case for the pager view. */
2222 if (opt_pipe) {
2223 view->pipe = opt_pipe;
2224 opt_pipe = NULL;
2225 } else {
2226 view->pipe = popen(view->cmd, "r");
2227 }
2229 if (!view->pipe)
2230 return FALSE;
2232 set_nonblocking_input(TRUE);
2234 view->offset = 0;
2235 view->lines = 0;
2236 view->lineno = 0;
2237 string_copy_rev(view->vid, view->id);
2239 if (view->line) {
2240 int i;
2242 for (i = 0; i < view->lines; i++)
2243 if (view->line[i].data)
2244 free(view->line[i].data);
2246 free(view->line);
2247 view->line = NULL;
2248 }
2250 view->start_time = time(NULL);
2252 return TRUE;
2253 }
2255 #define ITEM_CHUNK_SIZE 256
2256 static void *
2257 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2258 {
2259 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2260 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2262 if (mem == NULL || num_chunks != num_chunks_new) {
2263 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2264 mem = realloc(mem, *size * item_size);
2265 }
2267 return mem;
2268 }
2270 static struct line *
2271 realloc_lines(struct view *view, size_t line_size)
2272 {
2273 size_t alloc = view->line_alloc;
2274 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2275 sizeof(*view->line));
2277 if (!tmp)
2278 return NULL;
2280 view->line = tmp;
2281 view->line_alloc = alloc;
2282 view->line_size = line_size;
2283 return view->line;
2284 }
2286 static bool
2287 update_view(struct view *view)
2288 {
2289 char in_buffer[BUFSIZ];
2290 char out_buffer[BUFSIZ * 2];
2291 char *line;
2292 /* The number of lines to read. If too low it will cause too much
2293 * redrawing (and possible flickering), if too high responsiveness
2294 * will suffer. */
2295 unsigned long lines = view->height;
2296 int redraw_from = -1;
2298 if (!view->pipe)
2299 return TRUE;
2301 /* Only redraw if lines are visible. */
2302 if (view->offset + view->height >= view->lines)
2303 redraw_from = view->lines - view->offset;
2305 /* FIXME: This is probably not perfect for backgrounded views. */
2306 if (!realloc_lines(view, view->lines + lines))
2307 goto alloc_error;
2309 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2310 size_t linelen = strlen(line);
2312 if (linelen)
2313 line[linelen - 1] = 0;
2315 if (opt_iconv != ICONV_NONE) {
2316 ICONV_CONST char *inbuf = line;
2317 size_t inlen = linelen;
2319 char *outbuf = out_buffer;
2320 size_t outlen = sizeof(out_buffer);
2322 size_t ret;
2324 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2325 if (ret != (size_t) -1) {
2326 line = out_buffer;
2327 linelen = strlen(out_buffer);
2328 }
2329 }
2331 if (!view->ops->read(view, line))
2332 goto alloc_error;
2334 if (lines-- == 1)
2335 break;
2336 }
2338 {
2339 int digits;
2341 lines = view->lines;
2342 for (digits = 0; lines; digits++)
2343 lines /= 10;
2345 /* Keep the displayed view in sync with line number scaling. */
2346 if (digits != view->digits) {
2347 view->digits = digits;
2348 redraw_from = 0;
2349 }
2350 }
2352 if (!view_is_displayed(view))
2353 goto check_pipe;
2355 if (view == VIEW(REQ_VIEW_TREE)) {
2356 /* Clear the view and redraw everything since the tree sorting
2357 * might have rearranged things. */
2358 redraw_view(view);
2360 } else if (redraw_from >= 0) {
2361 /* If this is an incremental update, redraw the previous line
2362 * since for commits some members could have changed when
2363 * loading the main view. */
2364 if (redraw_from > 0)
2365 redraw_from--;
2367 /* Since revision graph visualization requires knowledge
2368 * about the parent commit, it causes a further one-off
2369 * needed to be redrawn for incremental updates. */
2370 if (redraw_from > 0 && opt_rev_graph)
2371 redraw_from--;
2373 /* Incrementally draw avoids flickering. */
2374 redraw_view_from(view, redraw_from);
2375 }
2377 if (view == VIEW(REQ_VIEW_BLAME))
2378 redraw_view_dirty(view);
2380 /* Update the title _after_ the redraw so that if the redraw picks up a
2381 * commit reference in view->ref it'll be available here. */
2382 update_view_title(view);
2384 check_pipe:
2385 if (ferror(view->pipe)) {
2386 report("Failed to read: %s", strerror(errno));
2387 end_update(view, TRUE);
2389 } else if (feof(view->pipe)) {
2390 report("");
2391 end_update(view, FALSE);
2392 }
2394 return TRUE;
2396 alloc_error:
2397 report("Allocation failure");
2398 end_update(view, TRUE);
2399 return FALSE;
2400 }
2402 static struct line *
2403 add_line_data(struct view *view, void *data, enum line_type type)
2404 {
2405 struct line *line = &view->line[view->lines++];
2407 memset(line, 0, sizeof(*line));
2408 line->type = type;
2409 line->data = data;
2411 return line;
2412 }
2414 static struct line *
2415 add_line_text(struct view *view, char *data, enum line_type type)
2416 {
2417 if (data)
2418 data = strdup(data);
2420 return data ? add_line_data(view, data, type) : NULL;
2421 }
2424 /*
2425 * View opening
2426 */
2428 enum open_flags {
2429 OPEN_DEFAULT = 0, /* Use default view switching. */
2430 OPEN_SPLIT = 1, /* Split current view. */
2431 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2432 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2433 OPEN_NOMAXIMIZE = 8 /* Do not maximize the current view. */
2434 };
2436 static void
2437 open_view(struct view *prev, enum request request, enum open_flags flags)
2438 {
2439 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2440 bool split = !!(flags & OPEN_SPLIT);
2441 bool reload = !!(flags & OPEN_RELOAD);
2442 bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2443 struct view *view = VIEW(request);
2444 int nviews = displayed_views();
2445 struct view *base_view = display[0];
2447 if (view == prev && nviews == 1 && !reload) {
2448 report("Already in %s view", view->name);
2449 return;
2450 }
2452 if (view->git_dir && !opt_git_dir[0]) {
2453 report("The %s view is disabled in pager view", view->name);
2454 return;
2455 }
2457 if (split) {
2458 display[1] = view;
2459 if (!backgrounded)
2460 current_view = 1;
2461 } else if (!nomaximize) {
2462 /* Maximize the current view. */
2463 memset(display, 0, sizeof(display));
2464 current_view = 0;
2465 display[current_view] = view;
2466 }
2468 /* Resize the view when switching between split- and full-screen,
2469 * or when switching between two different full-screen views. */
2470 if (nviews != displayed_views() ||
2471 (nviews == 1 && base_view != display[0]))
2472 resize_display();
2474 if (view->pipe)
2475 end_update(view, TRUE);
2477 if (view->ops->open) {
2478 if (!view->ops->open(view)) {
2479 report("Failed to load %s view", view->name);
2480 return;
2481 }
2483 } else if ((reload || strcmp(view->vid, view->id)) &&
2484 !begin_update(view)) {
2485 report("Failed to load %s view", view->name);
2486 return;
2487 }
2489 if (split && prev->lineno - prev->offset >= prev->height) {
2490 /* Take the title line into account. */
2491 int lines = prev->lineno - prev->offset - prev->height + 1;
2493 /* Scroll the view that was split if the current line is
2494 * outside the new limited view. */
2495 do_scroll_view(prev, lines);
2496 }
2498 if (prev && view != prev) {
2499 if (split && !backgrounded) {
2500 /* "Blur" the previous view. */
2501 update_view_title(prev);
2502 }
2504 view->parent = prev;
2505 }
2507 if (view->pipe && view->lines == 0) {
2508 /* Clear the old view and let the incremental updating refill
2509 * the screen. */
2510 werase(view->win);
2511 report("");
2512 } else {
2513 redraw_view(view);
2514 report("");
2515 }
2517 /* If the view is backgrounded the above calls to report()
2518 * won't redraw the view title. */
2519 if (backgrounded)
2520 update_view_title(view);
2521 }
2523 static void
2524 open_external_viewer(const char *cmd)
2525 {
2526 def_prog_mode(); /* save current tty modes */
2527 endwin(); /* restore original tty modes */
2528 system(cmd);
2529 fprintf(stderr, "Press Enter to continue");
2530 getc(stdin);
2531 reset_prog_mode();
2532 redraw_display();
2533 }
2535 static void
2536 open_mergetool(const char *file)
2537 {
2538 char cmd[SIZEOF_STR];
2539 char file_sq[SIZEOF_STR];
2541 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2542 string_format(cmd, "git mergetool %s", file_sq)) {
2543 open_external_viewer(cmd);
2544 }
2545 }
2547 static void
2548 open_editor(bool from_root, const char *file)
2549 {
2550 char cmd[SIZEOF_STR];
2551 char file_sq[SIZEOF_STR];
2552 char *editor;
2553 char *prefix = from_root ? opt_cdup : "";
2555 editor = getenv("GIT_EDITOR");
2556 if (!editor && *opt_editor)
2557 editor = opt_editor;
2558 if (!editor)
2559 editor = getenv("VISUAL");
2560 if (!editor)
2561 editor = getenv("EDITOR");
2562 if (!editor)
2563 editor = "vi";
2565 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2566 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2567 open_external_viewer(cmd);
2568 }
2569 }
2571 static void
2572 open_run_request(enum request request)
2573 {
2574 struct run_request *req = get_run_request(request);
2575 char buf[SIZEOF_STR * 2];
2576 size_t bufpos;
2577 char *cmd;
2579 if (!req) {
2580 report("Unknown run request");
2581 return;
2582 }
2584 bufpos = 0;
2585 cmd = req->cmd;
2587 while (cmd) {
2588 char *next = strstr(cmd, "%(");
2589 int len = next - cmd;
2590 char *value;
2592 if (!next) {
2593 len = strlen(cmd);
2594 value = "";
2596 } else if (!strncmp(next, "%(head)", 7)) {
2597 value = ref_head;
2599 } else if (!strncmp(next, "%(commit)", 9)) {
2600 value = ref_commit;
2602 } else if (!strncmp(next, "%(blob)", 7)) {
2603 value = ref_blob;
2605 } else {
2606 report("Unknown replacement in run request: `%s`", req->cmd);
2607 return;
2608 }
2610 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2611 return;
2613 if (next)
2614 next = strchr(next, ')') + 1;
2615 cmd = next;
2616 }
2618 open_external_viewer(buf);
2619 }
2621 /*
2622 * User request switch noodle
2623 */
2625 static int
2626 view_driver(struct view *view, enum request request)
2627 {
2628 int i;
2630 if (request == REQ_NONE) {
2631 doupdate();
2632 return TRUE;
2633 }
2635 if (request > REQ_NONE) {
2636 open_run_request(request);
2637 /* FIXME: When all views can refresh always do this. */
2638 if (view == VIEW(REQ_VIEW_STATUS) ||
2639 view == VIEW(REQ_VIEW_STAGE))
2640 request = REQ_REFRESH;
2641 else
2642 return TRUE;
2643 }
2645 if (view && view->lines) {
2646 request = view->ops->request(view, request, &view->line[view->lineno]);
2647 if (request == REQ_NONE)
2648 return TRUE;
2649 }
2651 switch (request) {
2652 case REQ_MOVE_UP:
2653 case REQ_MOVE_DOWN:
2654 case REQ_MOVE_PAGE_UP:
2655 case REQ_MOVE_PAGE_DOWN:
2656 case REQ_MOVE_FIRST_LINE:
2657 case REQ_MOVE_LAST_LINE:
2658 move_view(view, request);
2659 break;
2661 case REQ_SCROLL_LINE_DOWN:
2662 case REQ_SCROLL_LINE_UP:
2663 case REQ_SCROLL_PAGE_DOWN:
2664 case REQ_SCROLL_PAGE_UP:
2665 scroll_view(view, request);
2666 break;
2668 case REQ_VIEW_BLAME:
2669 if (!opt_file[0]) {
2670 report("No file chosen, press %s to open tree view",
2671 get_key(REQ_VIEW_TREE));
2672 break;
2673 }
2674 open_view(view, request, OPEN_DEFAULT);
2675 break;
2677 case REQ_VIEW_BLOB:
2678 if (!ref_blob[0]) {
2679 report("No file chosen, press %s to open tree view",
2680 get_key(REQ_VIEW_TREE));
2681 break;
2682 }
2683 open_view(view, request, OPEN_DEFAULT);
2684 break;
2686 case REQ_VIEW_PAGER:
2687 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2688 report("No pager content, press %s to run command from prompt",
2689 get_key(REQ_PROMPT));
2690 break;
2691 }
2692 open_view(view, request, OPEN_DEFAULT);
2693 break;
2695 case REQ_VIEW_STAGE:
2696 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2697 report("No stage content, press %s to open the status view and choose file",
2698 get_key(REQ_VIEW_STATUS));
2699 break;
2700 }
2701 open_view(view, request, OPEN_DEFAULT);
2702 break;
2704 case REQ_VIEW_STATUS:
2705 if (opt_is_inside_work_tree == FALSE) {
2706 report("The status view requires a working tree");
2707 break;
2708 }
2709 open_view(view, request, OPEN_DEFAULT);
2710 break;
2712 case REQ_VIEW_MAIN:
2713 case REQ_VIEW_DIFF:
2714 case REQ_VIEW_LOG:
2715 case REQ_VIEW_TREE:
2716 case REQ_VIEW_HELP:
2717 open_view(view, request, OPEN_DEFAULT);
2718 break;
2720 case REQ_NEXT:
2721 case REQ_PREVIOUS:
2722 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2724 if ((view == VIEW(REQ_VIEW_DIFF) &&
2725 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2726 (view == VIEW(REQ_VIEW_DIFF) &&
2727 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2728 (view == VIEW(REQ_VIEW_STAGE) &&
2729 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2730 (view == VIEW(REQ_VIEW_BLOB) &&
2731 view->parent == VIEW(REQ_VIEW_TREE))) {
2732 int line;
2734 view = view->parent;
2735 line = view->lineno;
2736 move_view(view, request);
2737 if (view_is_displayed(view))
2738 update_view_title(view);
2739 if (line != view->lineno)
2740 view->ops->request(view, REQ_ENTER,
2741 &view->line[view->lineno]);
2743 } else {
2744 move_view(view, request);
2745 }
2746 break;
2748 case REQ_VIEW_NEXT:
2749 {
2750 int nviews = displayed_views();
2751 int next_view = (current_view + 1) % nviews;
2753 if (next_view == current_view) {
2754 report("Only one view is displayed");
2755 break;
2756 }
2758 current_view = next_view;
2759 /* Blur out the title of the previous view. */
2760 update_view_title(view);
2761 report("");
2762 break;
2763 }
2764 case REQ_REFRESH:
2765 report("Refreshing is not yet supported for the %s view", view->name);
2766 break;
2768 case REQ_MAXIMIZE:
2769 if (displayed_views() == 2)
2770 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2771 break;
2773 case REQ_TOGGLE_LINENO:
2774 opt_line_number = !opt_line_number;
2775 redraw_display();
2776 break;
2778 case REQ_TOGGLE_DATE:
2779 opt_date = !opt_date;
2780 redraw_display();
2781 break;
2783 case REQ_TOGGLE_AUTHOR:
2784 opt_author = !opt_author;
2785 redraw_display();
2786 break;
2788 case REQ_TOGGLE_REV_GRAPH:
2789 opt_rev_graph = !opt_rev_graph;
2790 redraw_display();
2791 break;
2793 case REQ_TOGGLE_REFS:
2794 opt_show_refs = !opt_show_refs;
2795 redraw_display();
2796 break;
2798 case REQ_PROMPT:
2799 /* Always reload^Wrerun commands from the prompt. */
2800 open_view(view, opt_request, OPEN_RELOAD);
2801 break;
2803 case REQ_SEARCH:
2804 case REQ_SEARCH_BACK:
2805 search_view(view, request);
2806 break;
2808 case REQ_FIND_NEXT:
2809 case REQ_FIND_PREV:
2810 find_next(view, request);
2811 break;
2813 case REQ_STOP_LOADING:
2814 for (i = 0; i < ARRAY_SIZE(views); i++) {
2815 view = &views[i];
2816 if (view->pipe)
2817 report("Stopped loading the %s view", view->name),
2818 end_update(view, TRUE);
2819 }
2820 break;
2822 case REQ_SHOW_VERSION:
2823 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2824 return TRUE;
2826 case REQ_SCREEN_RESIZE:
2827 resize_display();
2828 /* Fall-through */
2829 case REQ_SCREEN_REDRAW:
2830 redraw_display();
2831 break;
2833 case REQ_EDIT:
2834 report("Nothing to edit");
2835 break;
2838 case REQ_ENTER:
2839 report("Nothing to enter");
2840 break;
2843 case REQ_VIEW_CLOSE:
2844 /* XXX: Mark closed views by letting view->parent point to the
2845 * view itself. Parents to closed view should never be
2846 * followed. */
2847 if (view->parent &&
2848 view->parent->parent != view->parent) {
2849 memset(display, 0, sizeof(display));
2850 current_view = 0;
2851 display[current_view] = view->parent;
2852 view->parent = view;
2853 resize_display();
2854 redraw_display();
2855 break;
2856 }
2857 /* Fall-through */
2858 case REQ_QUIT:
2859 return FALSE;
2861 default:
2862 /* An unknown key will show most commonly used commands. */
2863 report("Unknown key, press 'h' for help");
2864 return TRUE;
2865 }
2867 return TRUE;
2868 }
2871 /*
2872 * Pager backend
2873 */
2875 static bool
2876 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2877 {
2878 char *text = line->data;
2880 if (opt_line_number && draw_lineno(view, lineno))
2881 return TRUE;
2883 draw_text(view, line->type, text, TRUE);
2884 return TRUE;
2885 }
2887 static bool
2888 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2889 {
2890 char refbuf[SIZEOF_STR];
2891 char *ref = NULL;
2892 FILE *pipe;
2894 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2895 return TRUE;
2897 pipe = popen(refbuf, "r");
2898 if (!pipe)
2899 return TRUE;
2901 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2902 ref = chomp_string(ref);
2903 pclose(pipe);
2905 if (!ref || !*ref)
2906 return TRUE;
2908 /* This is the only fatal call, since it can "corrupt" the buffer. */
2909 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2910 return FALSE;
2912 return TRUE;
2913 }
2915 static void
2916 add_pager_refs(struct view *view, struct line *line)
2917 {
2918 char buf[SIZEOF_STR];
2919 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2920 struct ref **refs;
2921 size_t bufpos = 0, refpos = 0;
2922 const char *sep = "Refs: ";
2923 bool is_tag = FALSE;
2925 assert(line->type == LINE_COMMIT);
2927 refs = get_refs(commit_id);
2928 if (!refs) {
2929 if (view == VIEW(REQ_VIEW_DIFF))
2930 goto try_add_describe_ref;
2931 return;
2932 }
2934 do {
2935 struct ref *ref = refs[refpos];
2936 char *fmt = ref->tag ? "%s[%s]" :
2937 ref->remote ? "%s<%s>" : "%s%s";
2939 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2940 return;
2941 sep = ", ";
2942 if (ref->tag)
2943 is_tag = TRUE;
2944 } while (refs[refpos++]->next);
2946 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2947 try_add_describe_ref:
2948 /* Add <tag>-g<commit_id> "fake" reference. */
2949 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2950 return;
2951 }
2953 if (bufpos == 0)
2954 return;
2956 if (!realloc_lines(view, view->line_size + 1))
2957 return;
2959 add_line_text(view, buf, LINE_PP_REFS);
2960 }
2962 static bool
2963 pager_read(struct view *view, char *data)
2964 {
2965 struct line *line;
2967 if (!data)
2968 return TRUE;
2970 line = add_line_text(view, data, get_line_type(data));
2971 if (!line)
2972 return FALSE;
2974 if (line->type == LINE_COMMIT &&
2975 (view == VIEW(REQ_VIEW_DIFF) ||
2976 view == VIEW(REQ_VIEW_LOG)))
2977 add_pager_refs(view, line);
2979 return TRUE;
2980 }
2982 static enum request
2983 pager_request(struct view *view, enum request request, struct line *line)
2984 {
2985 int split = 0;
2987 if (request != REQ_ENTER)
2988 return request;
2990 if (line->type == LINE_COMMIT &&
2991 (view == VIEW(REQ_VIEW_LOG) ||
2992 view == VIEW(REQ_VIEW_PAGER))) {
2993 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2994 split = 1;
2995 }
2997 /* Always scroll the view even if it was split. That way
2998 * you can use Enter to scroll through the log view and
2999 * split open each commit diff. */
3000 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3002 /* FIXME: A minor workaround. Scrolling the view will call report("")
3003 * but if we are scrolling a non-current view this won't properly
3004 * update the view title. */
3005 if (split)
3006 update_view_title(view);
3008 return REQ_NONE;
3009 }
3011 static bool
3012 pager_grep(struct view *view, struct line *line)
3013 {
3014 regmatch_t pmatch;
3015 char *text = line->data;
3017 if (!*text)
3018 return FALSE;
3020 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3021 return FALSE;
3023 return TRUE;
3024 }
3026 static void
3027 pager_select(struct view *view, struct line *line)
3028 {
3029 if (line->type == LINE_COMMIT) {
3030 char *text = (char *)line->data + STRING_SIZE("commit ");
3032 if (view != VIEW(REQ_VIEW_PAGER))
3033 string_copy_rev(view->ref, text);
3034 string_copy_rev(ref_commit, text);
3035 }
3036 }
3038 static struct view_ops pager_ops = {
3039 "line",
3040 NULL,
3041 pager_read,
3042 pager_draw,
3043 pager_request,
3044 pager_grep,
3045 pager_select,
3046 };
3049 /*
3050 * Help backend
3051 */
3053 static bool
3054 help_open(struct view *view)
3055 {
3056 char buf[BUFSIZ];
3057 int lines = ARRAY_SIZE(req_info) + 2;
3058 int i;
3060 if (view->lines > 0)
3061 return TRUE;
3063 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3064 if (!req_info[i].request)
3065 lines++;
3067 lines += run_requests + 1;
3069 view->line = calloc(lines, sizeof(*view->line));
3070 if (!view->line)
3071 return FALSE;
3073 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3075 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3076 char *key;
3078 if (req_info[i].request == REQ_NONE)
3079 continue;
3081 if (!req_info[i].request) {
3082 add_line_text(view, "", LINE_DEFAULT);
3083 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3084 continue;
3085 }
3087 key = get_key(req_info[i].request);
3088 if (!*key)
3089 key = "(no key defined)";
3091 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3092 continue;
3094 add_line_text(view, buf, LINE_DEFAULT);
3095 }
3097 if (run_requests) {
3098 add_line_text(view, "", LINE_DEFAULT);
3099 add_line_text(view, "External commands:", LINE_DEFAULT);
3100 }
3102 for (i = 0; i < run_requests; i++) {
3103 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3104 char *key;
3106 if (!req)
3107 continue;
3109 key = get_key_name(req->key);
3110 if (!*key)
3111 key = "(no key defined)";
3113 if (!string_format(buf, " %-10s %-14s `%s`",
3114 keymap_table[req->keymap].name,
3115 key, req->cmd))
3116 continue;
3118 add_line_text(view, buf, LINE_DEFAULT);
3119 }
3121 return TRUE;
3122 }
3124 static struct view_ops help_ops = {
3125 "line",
3126 help_open,
3127 NULL,
3128 pager_draw,
3129 pager_request,
3130 pager_grep,
3131 pager_select,
3132 };
3135 /*
3136 * Tree backend
3137 */
3139 struct tree_stack_entry {
3140 struct tree_stack_entry *prev; /* Entry below this in the stack */
3141 unsigned long lineno; /* Line number to restore */
3142 char *name; /* Position of name in opt_path */
3143 };
3145 /* The top of the path stack. */
3146 static struct tree_stack_entry *tree_stack = NULL;
3147 unsigned long tree_lineno = 0;
3149 static void
3150 pop_tree_stack_entry(void)
3151 {
3152 struct tree_stack_entry *entry = tree_stack;
3154 tree_lineno = entry->lineno;
3155 entry->name[0] = 0;
3156 tree_stack = entry->prev;
3157 free(entry);
3158 }
3160 static void
3161 push_tree_stack_entry(char *name, unsigned long lineno)
3162 {
3163 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3164 size_t pathlen = strlen(opt_path);
3166 if (!entry)
3167 return;
3169 entry->prev = tree_stack;
3170 entry->name = opt_path + pathlen;
3171 tree_stack = entry;
3173 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3174 pop_tree_stack_entry();
3175 return;
3176 }
3178 /* Move the current line to the first tree entry. */
3179 tree_lineno = 1;
3180 entry->lineno = lineno;
3181 }
3183 /* Parse output from git-ls-tree(1):
3184 *
3185 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3186 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3187 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3188 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3189 */
3191 #define SIZEOF_TREE_ATTR \
3192 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3194 #define TREE_UP_FORMAT "040000 tree %s\t.."
3196 static int
3197 tree_compare_entry(enum line_type type1, char *name1,
3198 enum line_type type2, char *name2)
3199 {
3200 if (type1 != type2) {
3201 if (type1 == LINE_TREE_DIR)
3202 return -1;
3203 return 1;
3204 }
3206 return strcmp(name1, name2);
3207 }
3209 static char *
3210 tree_path(struct line *line)
3211 {
3212 char *path = line->data;
3214 return path + SIZEOF_TREE_ATTR;
3215 }
3217 static bool
3218 tree_read(struct view *view, char *text)
3219 {
3220 size_t textlen = text ? strlen(text) : 0;
3221 char buf[SIZEOF_STR];
3222 unsigned long pos;
3223 enum line_type type;
3224 bool first_read = view->lines == 0;
3226 if (!text)
3227 return TRUE;
3228 if (textlen <= SIZEOF_TREE_ATTR)
3229 return FALSE;
3231 type = text[STRING_SIZE("100644 ")] == 't'
3232 ? LINE_TREE_DIR : LINE_TREE_FILE;
3234 if (first_read) {
3235 /* Add path info line */
3236 if (!string_format(buf, "Directory path /%s", opt_path) ||
3237 !realloc_lines(view, view->line_size + 1) ||
3238 !add_line_text(view, buf, LINE_DEFAULT))
3239 return FALSE;
3241 /* Insert "link" to parent directory. */
3242 if (*opt_path) {
3243 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3244 !realloc_lines(view, view->line_size + 1) ||
3245 !add_line_text(view, buf, LINE_TREE_DIR))
3246 return FALSE;
3247 }
3248 }
3250 /* Strip the path part ... */
3251 if (*opt_path) {
3252 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3253 size_t striplen = strlen(opt_path);
3254 char *path = text + SIZEOF_TREE_ATTR;
3256 if (pathlen > striplen)
3257 memmove(path, path + striplen,
3258 pathlen - striplen + 1);
3259 }
3261 /* Skip "Directory ..." and ".." line. */
3262 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3263 struct line *line = &view->line[pos];
3264 char *path1 = tree_path(line);
3265 char *path2 = text + SIZEOF_TREE_ATTR;
3266 int cmp = tree_compare_entry(line->type, path1, type, path2);
3268 if (cmp <= 0)
3269 continue;
3271 text = strdup(text);
3272 if (!text)
3273 return FALSE;
3275 if (view->lines > pos)
3276 memmove(&view->line[pos + 1], &view->line[pos],
3277 (view->lines - pos) * sizeof(*line));
3279 line = &view->line[pos];
3280 line->data = text;
3281 line->type = type;
3282 view->lines++;
3283 return TRUE;
3284 }
3286 if (!add_line_text(view, text, type))
3287 return FALSE;
3289 if (tree_lineno > view->lineno) {
3290 view->lineno = tree_lineno;
3291 tree_lineno = 0;
3292 }
3294 return TRUE;
3295 }
3297 static enum request
3298 tree_request(struct view *view, enum request request, struct line *line)
3299 {
3300 enum open_flags flags;
3302 if (request == REQ_VIEW_BLAME) {
3303 char *filename = tree_path(line);
3305 if (line->type == LINE_TREE_DIR) {
3306 report("Cannot show blame for directory %s", opt_path);
3307 return REQ_NONE;
3308 }
3310 string_copy(opt_ref, view->vid);
3311 string_format(opt_file, "%s%s", opt_path, filename);
3312 return request;
3313 }
3314 if (request == REQ_TREE_PARENT) {
3315 if (*opt_path) {
3316 /* fake 'cd ..' */
3317 request = REQ_ENTER;
3318 line = &view->line[1];
3319 } else {
3320 /* quit view if at top of tree */
3321 return REQ_VIEW_CLOSE;
3322 }
3323 }
3324 if (request != REQ_ENTER)
3325 return request;
3327 /* Cleanup the stack if the tree view is at a different tree. */
3328 while (!*opt_path && tree_stack)
3329 pop_tree_stack_entry();
3331 switch (line->type) {
3332 case LINE_TREE_DIR:
3333 /* Depending on whether it is a subdir or parent (updir?) link
3334 * mangle the path buffer. */
3335 if (line == &view->line[1] && *opt_path) {
3336 pop_tree_stack_entry();
3338 } else {
3339 char *basename = tree_path(line);
3341 push_tree_stack_entry(basename, view->lineno);
3342 }
3344 /* Trees and subtrees share the same ID, so they are not not
3345 * unique like blobs. */
3346 flags = OPEN_RELOAD;
3347 request = REQ_VIEW_TREE;
3348 break;
3350 case LINE_TREE_FILE:
3351 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3352 request = REQ_VIEW_BLOB;
3353 break;
3355 default:
3356 return TRUE;
3357 }
3359 open_view(view, request, flags);
3360 if (request == REQ_VIEW_TREE) {
3361 view->lineno = tree_lineno;
3362 }
3364 return REQ_NONE;
3365 }
3367 static void
3368 tree_select(struct view *view, struct line *line)
3369 {
3370 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3372 if (line->type == LINE_TREE_FILE) {
3373 string_copy_rev(ref_blob, text);
3375 } else if (line->type != LINE_TREE_DIR) {
3376 return;
3377 }
3379 string_copy_rev(view->ref, text);
3380 }
3382 static struct view_ops tree_ops = {
3383 "file",
3384 NULL,
3385 tree_read,
3386 pager_draw,
3387 tree_request,
3388 pager_grep,
3389 tree_select,
3390 };
3392 static bool
3393 blob_read(struct view *view, char *line)
3394 {
3395 if (!line)
3396 return TRUE;
3397 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3398 }
3400 static struct view_ops blob_ops = {
3401 "line",
3402 NULL,
3403 blob_read,
3404 pager_draw,
3405 pager_request,
3406 pager_grep,
3407 pager_select,
3408 };
3410 /*
3411 * Blame backend
3412 *
3413 * Loading the blame view is a two phase job:
3414 *
3415 * 1. File content is read either using opt_file from the
3416 * filesystem or using git-cat-file.
3417 * 2. Then blame information is incrementally added by
3418 * reading output from git-blame.
3419 */
3421 struct blame_commit {
3422 char id[SIZEOF_REV]; /* SHA1 ID. */
3423 char title[128]; /* First line of the commit message. */
3424 char author[75]; /* Author of the commit. */
3425 struct tm time; /* Date from the author ident. */
3426 char filename[128]; /* Name of file. */
3427 };
3429 struct blame {
3430 struct blame_commit *commit;
3431 unsigned int header:1;
3432 char text[1];
3433 };
3435 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3436 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3438 static bool
3439 blame_open(struct view *view)
3440 {
3441 char path[SIZEOF_STR];
3442 char ref[SIZEOF_STR] = "";
3444 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3445 return FALSE;
3447 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3448 return FALSE;
3450 if (*opt_ref) {
3451 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3452 return FALSE;
3453 } else {
3454 view->pipe = fopen(opt_file, "r");
3455 if (!view->pipe &&
3456 !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3457 return FALSE;
3458 }
3460 if (!view->pipe)
3461 view->pipe = popen(view->cmd, "r");
3462 if (!view->pipe)
3463 return FALSE;
3465 if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3466 return FALSE;
3468 string_format(view->ref, "%s ...", opt_file);
3469 string_copy_rev(view->vid, opt_file);
3470 set_nonblocking_input(TRUE);
3472 if (view->line) {
3473 int i;
3475 for (i = 0; i < view->lines; i++)
3476 free(view->line[i].data);
3477 free(view->line);
3478 }
3480 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3481 view->offset = view->lines = view->lineno = 0;
3482 view->line = NULL;
3483 view->start_time = time(NULL);
3485 return TRUE;
3486 }
3488 static struct blame_commit *
3489 get_blame_commit(struct view *view, const char *id)
3490 {
3491 size_t i;
3493 for (i = 0; i < view->lines; i++) {
3494 struct blame *blame = view->line[i].data;
3496 if (!blame->commit)
3497 continue;
3499 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3500 return blame->commit;
3501 }
3503 {
3504 struct blame_commit *commit = calloc(1, sizeof(*commit));
3506 if (commit)
3507 string_ncopy(commit->id, id, SIZEOF_REV);
3508 return commit;
3509 }
3510 }
3512 static bool
3513 parse_number(char **posref, size_t *number, size_t min, size_t max)
3514 {
3515 char *pos = *posref;
3517 *posref = NULL;
3518 pos = strchr(pos + 1, ' ');
3519 if (!pos || !isdigit(pos[1]))
3520 return FALSE;
3521 *number = atoi(pos + 1);
3522 if (*number < min || *number > max)
3523 return FALSE;
3525 *posref = pos;
3526 return TRUE;
3527 }
3529 static struct blame_commit *
3530 parse_blame_commit(struct view *view, char *text, int *blamed)
3531 {
3532 struct blame_commit *commit;
3533 struct blame *blame;
3534 char *pos = text + SIZEOF_REV - 1;
3535 size_t lineno;
3536 size_t group;
3538 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3539 return NULL;
3541 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3542 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3543 return NULL;
3545 commit = get_blame_commit(view, text);
3546 if (!commit)
3547 return NULL;
3549 *blamed += group;
3550 while (group--) {
3551 struct line *line = &view->line[lineno + group - 1];
3553 blame = line->data;
3554 blame->commit = commit;
3555 blame->header = !group;
3556 line->dirty = 1;
3557 }
3559 return commit;
3560 }
3562 static bool
3563 blame_read_file(struct view *view, char *line)
3564 {
3565 if (!line) {
3566 FILE *pipe = NULL;
3568 if (view->lines > 0)
3569 pipe = popen(view->cmd, "r");
3570 else if (!view->parent)
3571 die("No blame exist for %s", view->vid);
3572 view->cmd[0] = 0;
3573 if (!pipe) {
3574 report("Failed to load blame data");
3575 return TRUE;
3576 }
3578 fclose(view->pipe);
3579 view->pipe = pipe;
3580 return FALSE;
3582 } else {
3583 size_t linelen = strlen(line);
3584 struct blame *blame = malloc(sizeof(*blame) + linelen);
3586 blame->commit = NULL;
3587 strncpy(blame->text, line, linelen);
3588 blame->text[linelen] = 0;
3589 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3590 }
3591 }
3593 static bool
3594 match_blame_header(const char *name, char **line)
3595 {
3596 size_t namelen = strlen(name);
3597 bool matched = !strncmp(name, *line, namelen);
3599 if (matched)
3600 *line += namelen;
3602 return matched;
3603 }
3605 static bool
3606 blame_read(struct view *view, char *line)
3607 {
3608 static struct blame_commit *commit = NULL;
3609 static int blamed = 0;
3610 static time_t author_time;
3612 if (*view->cmd)
3613 return blame_read_file(view, line);
3615 if (!line) {
3616 /* Reset all! */
3617 commit = NULL;
3618 blamed = 0;
3619 string_format(view->ref, "%s", view->vid);
3620 if (view_is_displayed(view)) {
3621 update_view_title(view);
3622 redraw_view_from(view, 0);
3623 }
3624 return TRUE;
3625 }
3627 if (!commit) {
3628 commit = parse_blame_commit(view, line, &blamed);
3629 string_format(view->ref, "%s %2d%%", view->vid,
3630 blamed * 100 / view->lines);
3632 } else if (match_blame_header("author ", &line)) {
3633 string_ncopy(commit->author, line, strlen(line));
3635 } else if (match_blame_header("author-time ", &line)) {
3636 author_time = (time_t) atol(line);
3638 } else if (match_blame_header("author-tz ", &line)) {
3639 long tz;
3641 tz = ('0' - line[1]) * 60 * 60 * 10;
3642 tz += ('0' - line[2]) * 60 * 60;
3643 tz += ('0' - line[3]) * 60;
3644 tz += ('0' - line[4]) * 60;
3646 if (line[0] == '-')
3647 tz = -tz;
3649 author_time -= tz;
3650 gmtime_r(&author_time, &commit->time);
3652 } else if (match_blame_header("summary ", &line)) {
3653 string_ncopy(commit->title, line, strlen(line));
3655 } else if (match_blame_header("filename ", &line)) {
3656 string_ncopy(commit->filename, line, strlen(line));
3657 commit = NULL;
3658 }
3660 return TRUE;
3661 }
3663 static bool
3664 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3665 {
3666 struct blame *blame = line->data;
3667 struct tm *time = NULL;
3668 char *id = NULL, *author = NULL;
3670 if (blame->commit && *blame->commit->filename) {
3671 id = blame->commit->id;
3672 author = blame->commit->author;
3673 time = &blame->commit->time;
3674 }
3676 if (opt_date && draw_date(view, time))
3677 return TRUE;
3679 if (opt_author &&
3680 draw_field(view, LINE_MAIN_AUTHOR, author, AUTHOR_COLS, TRUE))
3681 return TRUE;
3683 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3684 return TRUE;
3686 if (draw_lineno(view, lineno))
3687 return TRUE;
3689 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3690 return TRUE;
3691 }
3693 static enum request
3694 blame_request(struct view *view, enum request request, struct line *line)
3695 {
3696 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3697 struct blame *blame = line->data;
3699 switch (request) {
3700 case REQ_ENTER:
3701 if (!blame->commit) {
3702 report("No commit loaded yet");
3703 break;
3704 }
3706 if (!strcmp(blame->commit->id, NULL_ID)) {
3707 char path[SIZEOF_STR];
3709 if (sq_quote(path, 0, view->vid) >= sizeof(path))
3710 break;
3711 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3712 }
3714 open_view(view, REQ_VIEW_DIFF, flags);
3715 break;
3717 default:
3718 return request;
3719 }
3721 return REQ_NONE;
3722 }
3724 static bool
3725 blame_grep(struct view *view, struct line *line)
3726 {
3727 struct blame *blame = line->data;
3728 struct blame_commit *commit = blame->commit;
3729 regmatch_t pmatch;
3731 #define MATCH(text, on) \
3732 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3734 if (commit) {
3735 char buf[DATE_COLS + 1];
3737 if (MATCH(commit->title, 1) ||
3738 MATCH(commit->author, opt_author) ||
3739 MATCH(commit->id, opt_date))
3740 return TRUE;
3742 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3743 MATCH(buf, 1))
3744 return TRUE;
3745 }
3747 return MATCH(blame->text, 1);
3749 #undef MATCH
3750 }
3752 static void
3753 blame_select(struct view *view, struct line *line)
3754 {
3755 struct blame *blame = line->data;
3756 struct blame_commit *commit = blame->commit;
3758 if (!commit)
3759 return;
3761 if (!strcmp(commit->id, NULL_ID))
3762 string_ncopy(ref_commit, "HEAD", 4);
3763 else
3764 string_copy_rev(ref_commit, commit->id);
3765 }
3767 static struct view_ops blame_ops = {
3768 "line",
3769 blame_open,
3770 blame_read,
3771 blame_draw,
3772 blame_request,
3773 blame_grep,
3774 blame_select,
3775 };
3777 /*
3778 * Status backend
3779 */
3781 struct status {
3782 char status;
3783 struct {
3784 mode_t mode;
3785 char rev[SIZEOF_REV];
3786 char name[SIZEOF_STR];
3787 } old;
3788 struct {
3789 mode_t mode;
3790 char rev[SIZEOF_REV];
3791 char name[SIZEOF_STR];
3792 } new;
3793 };
3795 static char status_onbranch[SIZEOF_STR];
3796 static struct status stage_status;
3797 static enum line_type stage_line_type;
3798 static size_t stage_chunks;
3799 static int *stage_chunk;
3801 /* Get fields from the diff line:
3802 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3803 */
3804 static inline bool
3805 status_get_diff(struct status *file, char *buf, size_t bufsize)
3806 {
3807 char *old_mode = buf + 1;
3808 char *new_mode = buf + 8;
3809 char *old_rev = buf + 15;
3810 char *new_rev = buf + 56;
3811 char *status = buf + 97;
3813 if (bufsize < 99 ||
3814 old_mode[-1] != ':' ||
3815 new_mode[-1] != ' ' ||
3816 old_rev[-1] != ' ' ||
3817 new_rev[-1] != ' ' ||
3818 status[-1] != ' ')
3819 return FALSE;
3821 file->status = *status;
3823 string_copy_rev(file->old.rev, old_rev);
3824 string_copy_rev(file->new.rev, new_rev);
3826 file->old.mode = strtoul(old_mode, NULL, 8);
3827 file->new.mode = strtoul(new_mode, NULL, 8);
3829 file->old.name[0] = file->new.name[0] = 0;
3831 return TRUE;
3832 }
3834 static bool
3835 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3836 {
3837 struct status *file = NULL;
3838 struct status *unmerged = NULL;
3839 char buf[SIZEOF_STR * 4];
3840 size_t bufsize = 0;
3841 FILE *pipe;
3843 pipe = popen(cmd, "r");
3844 if (!pipe)
3845 return FALSE;
3847 add_line_data(view, NULL, type);
3849 while (!feof(pipe) && !ferror(pipe)) {
3850 char *sep;
3851 size_t readsize;
3853 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3854 if (!readsize)
3855 break;
3856 bufsize += readsize;
3858 /* Process while we have NUL chars. */
3859 while ((sep = memchr(buf, 0, bufsize))) {
3860 size_t sepsize = sep - buf + 1;
3862 if (!file) {
3863 if (!realloc_lines(view, view->line_size + 1))
3864 goto error_out;
3866 file = calloc(1, sizeof(*file));
3867 if (!file)
3868 goto error_out;
3870 add_line_data(view, file, type);
3871 }
3873 /* Parse diff info part. */
3874 if (status) {
3875 file->status = status;
3876 if (status == 'A')
3877 string_copy(file->old.rev, NULL_ID);
3879 } else if (!file->status) {
3880 if (!status_get_diff(file, buf, sepsize))
3881 goto error_out;
3883 bufsize -= sepsize;
3884 memmove(buf, sep + 1, bufsize);
3886 sep = memchr(buf, 0, bufsize);
3887 if (!sep)
3888 break;
3889 sepsize = sep - buf + 1;
3891 /* Collapse all 'M'odified entries that
3892 * follow a associated 'U'nmerged entry.
3893 */
3894 if (file->status == 'U') {
3895 unmerged = file;
3897 } else if (unmerged) {
3898 int collapse = !strcmp(buf, unmerged->new.name);
3900 unmerged = NULL;
3901 if (collapse) {
3902 free(file);
3903 view->lines--;
3904 continue;
3905 }
3906 }
3907 }
3909 /* Grab the old name for rename/copy. */
3910 if (!*file->old.name &&
3911 (file->status == 'R' || file->status == 'C')) {
3912 sepsize = sep - buf + 1;
3913 string_ncopy(file->old.name, buf, sepsize);
3914 bufsize -= sepsize;
3915 memmove(buf, sep + 1, bufsize);
3917 sep = memchr(buf, 0, bufsize);
3918 if (!sep)
3919 break;
3920 sepsize = sep - buf + 1;
3921 }
3923 /* git-ls-files just delivers a NUL separated
3924 * list of file names similar to the second half
3925 * of the git-diff-* output. */
3926 string_ncopy(file->new.name, buf, sepsize);
3927 if (!*file->old.name)
3928 string_copy(file->old.name, file->new.name);
3929 bufsize -= sepsize;
3930 memmove(buf, sep + 1, bufsize);
3931 file = NULL;
3932 }
3933 }
3935 if (ferror(pipe)) {
3936 error_out:
3937 pclose(pipe);
3938 return FALSE;
3939 }
3941 if (!view->line[view->lines - 1].data)
3942 add_line_data(view, NULL, LINE_STAT_NONE);
3944 pclose(pipe);
3945 return TRUE;
3946 }
3948 /* Don't show unmerged entries in the staged section. */
3949 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3950 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3951 #define STATUS_LIST_OTHER_CMD \
3952 "git ls-files -z --others --exclude-per-directory=.gitignore"
3953 #define STATUS_LIST_NO_HEAD_CMD \
3954 "git ls-files -z --cached --exclude-per-directory=.gitignore"
3956 #define STATUS_DIFF_INDEX_SHOW_CMD \
3957 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3959 #define STATUS_DIFF_FILES_SHOW_CMD \
3960 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3962 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3963 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3965 /* First parse staged info using git-diff-index(1), then parse unstaged
3966 * info using git-diff-files(1), and finally untracked files using
3967 * git-ls-files(1). */
3968 static bool
3969 status_open(struct view *view)
3970 {
3971 struct stat statbuf;
3972 char exclude[SIZEOF_STR];
3973 char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3974 char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3975 unsigned long prev_lineno = view->lineno;
3976 char indexstatus = 0;
3977 size_t i;
3979 for (i = 0; i < view->lines; i++)
3980 free(view->line[i].data);
3981 free(view->line);
3982 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3983 view->line = NULL;
3985 if (!realloc_lines(view, view->line_size + 7))
3986 return FALSE;
3988 add_line_data(view, NULL, LINE_STAT_HEAD);
3989 if (opt_no_head)
3990 string_copy(status_onbranch, "Initial commit");
3991 else if (!*opt_head)
3992 string_copy(status_onbranch, "Not currently on any branch");
3993 else if (!string_format(status_onbranch, "On branch %s", opt_head))
3994 return FALSE;
3996 if (opt_no_head) {
3997 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
3998 indexstatus = 'A';
3999 }
4001 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
4002 return FALSE;
4004 if (stat(exclude, &statbuf) >= 0) {
4005 size_t cmdsize = strlen(othercmd);
4007 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
4008 sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
4009 return FALSE;
4011 cmdsize = strlen(indexcmd);
4012 if (opt_no_head &&
4013 (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
4014 sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
4015 return FALSE;
4016 }
4018 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4020 if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
4021 !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4022 !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
4023 return FALSE;
4025 /* If all went well restore the previous line number to stay in
4026 * the context or select a line with something that can be
4027 * updated. */
4028 if (prev_lineno >= view->lines)
4029 prev_lineno = view->lines - 1;
4030 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4031 prev_lineno++;
4032 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4033 prev_lineno--;
4035 /* If the above fails, always skip the "On branch" line. */
4036 if (prev_lineno < view->lines)
4037 view->lineno = prev_lineno;
4038 else
4039 view->lineno = 1;
4041 if (view->lineno < view->offset)
4042 view->offset = view->lineno;
4043 else if (view->offset + view->height <= view->lineno)
4044 view->offset = view->lineno - view->height + 1;
4046 return TRUE;
4047 }
4049 static bool
4050 status_draw(struct view *view, struct line *line, unsigned int lineno)
4051 {
4052 struct status *status = line->data;
4053 enum line_type type;
4054 char *text;
4056 if (!status) {
4057 switch (line->type) {
4058 case LINE_STAT_STAGED:
4059 type = LINE_STAT_SECTION;
4060 text = "Changes to be committed:";
4061 break;
4063 case LINE_STAT_UNSTAGED:
4064 type = LINE_STAT_SECTION;
4065 text = "Changed but not updated:";
4066 break;
4068 case LINE_STAT_UNTRACKED:
4069 type = LINE_STAT_SECTION;
4070 text = "Untracked files:";
4071 break;
4073 case LINE_STAT_NONE:
4074 type = LINE_DEFAULT;
4075 text = " (no files)";
4076 break;
4078 case LINE_STAT_HEAD:
4079 type = LINE_STAT_HEAD;
4080 text = status_onbranch;
4081 break;
4083 default:
4084 return FALSE;
4085 }
4086 } else {
4087 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4089 buf[0] = status->status;
4090 if (draw_text(view, line->type, buf, TRUE))
4091 return TRUE;
4092 type = LINE_DEFAULT;
4093 text = status->new.name;
4094 }
4096 draw_text(view, type, text, TRUE);
4097 return TRUE;
4098 }
4100 static enum request
4101 status_enter(struct view *view, struct line *line)
4102 {
4103 struct status *status = line->data;
4104 char oldpath[SIZEOF_STR] = "";
4105 char newpath[SIZEOF_STR] = "";
4106 char *info;
4107 size_t cmdsize = 0;
4108 enum open_flags split;
4110 if (line->type == LINE_STAT_NONE ||
4111 (!status && line[1].type == LINE_STAT_NONE)) {
4112 report("No file to diff");
4113 return REQ_NONE;
4114 }
4116 if (status) {
4117 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4118 return REQ_QUIT;
4119 /* Diffs for unmerged entries are empty when pasing the
4120 * new path, so leave it empty. */
4121 if (status->status != 'U' &&
4122 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4123 return REQ_QUIT;
4124 }
4126 if (opt_cdup[0] &&
4127 line->type != LINE_STAT_UNTRACKED &&
4128 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4129 return REQ_QUIT;
4131 switch (line->type) {
4132 case LINE_STAT_STAGED:
4133 if (opt_no_head) {
4134 if (!string_format_from(opt_cmd, &cmdsize,
4135 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4136 newpath))
4137 return REQ_QUIT;
4138 } else {
4139 if (!string_format_from(opt_cmd, &cmdsize,
4140 STATUS_DIFF_INDEX_SHOW_CMD,
4141 oldpath, newpath))
4142 return REQ_QUIT;
4143 }
4145 if (status)
4146 info = "Staged changes to %s";
4147 else
4148 info = "Staged changes";
4149 break;
4151 case LINE_STAT_UNSTAGED:
4152 if (!string_format_from(opt_cmd, &cmdsize,
4153 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4154 return REQ_QUIT;
4155 if (status)
4156 info = "Unstaged changes to %s";
4157 else
4158 info = "Unstaged changes";
4159 break;
4161 case LINE_STAT_UNTRACKED:
4162 if (opt_pipe)
4163 return REQ_QUIT;
4165 if (!status) {
4166 report("No file to show");
4167 return REQ_NONE;
4168 }
4170 opt_pipe = fopen(status->new.name, "r");
4171 info = "Untracked file %s";
4172 break;
4174 case LINE_STAT_HEAD:
4175 return REQ_NONE;
4177 default:
4178 die("line type %d not handled in switch", line->type);
4179 }
4181 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4182 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4183 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4184 if (status) {
4185 stage_status = *status;
4186 } else {
4187 memset(&stage_status, 0, sizeof(stage_status));
4188 }
4190 stage_line_type = line->type;
4191 stage_chunks = 0;
4192 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4193 }
4195 return REQ_NONE;
4196 }
4198 static bool
4199 status_exists(struct status *status, enum line_type type)
4200 {
4201 struct view *view = VIEW(REQ_VIEW_STATUS);
4202 struct line *line;
4204 for (line = view->line; line < view->line + view->lines; line++) {
4205 struct status *pos = line->data;
4207 if (line->type == type && pos &&
4208 !strcmp(status->new.name, pos->new.name))
4209 return TRUE;
4210 }
4212 return FALSE;
4213 }
4216 static FILE *
4217 status_update_prepare(enum line_type type)
4218 {
4219 char cmd[SIZEOF_STR];
4220 size_t cmdsize = 0;
4222 if (opt_cdup[0] &&
4223 type != LINE_STAT_UNTRACKED &&
4224 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4225 return NULL;
4227 switch (type) {
4228 case LINE_STAT_STAGED:
4229 string_add(cmd, cmdsize, "git update-index -z --index-info");
4230 break;
4232 case LINE_STAT_UNSTAGED:
4233 case LINE_STAT_UNTRACKED:
4234 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4235 break;
4237 default:
4238 die("line type %d not handled in switch", type);
4239 }
4241 return popen(cmd, "w");
4242 }
4244 static bool
4245 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4246 {
4247 char buf[SIZEOF_STR];
4248 size_t bufsize = 0;
4249 size_t written = 0;
4251 switch (type) {
4252 case LINE_STAT_STAGED:
4253 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4254 status->old.mode,
4255 status->old.rev,
4256 status->old.name, 0))
4257 return FALSE;
4258 break;
4260 case LINE_STAT_UNSTAGED:
4261 case LINE_STAT_UNTRACKED:
4262 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4263 return FALSE;
4264 break;
4266 default:
4267 die("line type %d not handled in switch", type);
4268 }
4270 while (!ferror(pipe) && written < bufsize) {
4271 written += fwrite(buf + written, 1, bufsize - written, pipe);
4272 }
4274 return written == bufsize;
4275 }
4277 static bool
4278 status_update_file(struct status *status, enum line_type type)
4279 {
4280 FILE *pipe = status_update_prepare(type);
4281 bool result;
4283 if (!pipe)
4284 return FALSE;
4286 result = status_update_write(pipe, status, type);
4287 pclose(pipe);
4288 return result;
4289 }
4291 static bool
4292 status_update_files(struct view *view, struct line *line)
4293 {
4294 FILE *pipe = status_update_prepare(line->type);
4295 bool result = TRUE;
4296 struct line *pos = view->line + view->lines;
4297 int files = 0;
4298 int file, done;
4300 if (!pipe)
4301 return FALSE;
4303 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4304 files++;
4306 for (file = 0, done = 0; result && file < files; line++, file++) {
4307 int almost_done = file * 100 / files;
4309 if (almost_done > done) {
4310 done = almost_done;
4311 string_format(view->ref, "updating file %u of %u (%d%% done)",
4312 file, files, done);
4313 update_view_title(view);
4314 }
4315 result = status_update_write(pipe, line->data, line->type);
4316 }
4318 pclose(pipe);
4319 return result;
4320 }
4322 static bool
4323 status_update(struct view *view)
4324 {
4325 struct line *line = &view->line[view->lineno];
4327 assert(view->lines);
4329 if (!line->data) {
4330 /* This should work even for the "On branch" line. */
4331 if (line < view->line + view->lines && !line[1].data) {
4332 report("Nothing to update");
4333 return FALSE;
4334 }
4336 if (!status_update_files(view, line + 1)) {
4337 report("Failed to update file status");
4338 return FALSE;
4339 }
4341 } else if (!status_update_file(line->data, line->type)) {
4342 report("Failed to update file status");
4343 return FALSE;
4344 }
4346 return TRUE;
4347 }
4349 static enum request
4350 status_request(struct view *view, enum request request, struct line *line)
4351 {
4352 struct status *status = line->data;
4354 switch (request) {
4355 case REQ_STATUS_UPDATE:
4356 if (!status_update(view))
4357 return REQ_NONE;
4358 break;
4360 case REQ_STATUS_MERGE:
4361 if (!status || status->status != 'U') {
4362 report("Merging only possible for files with unmerged status ('U').");
4363 return REQ_NONE;
4364 }
4365 open_mergetool(status->new.name);
4366 break;
4368 case REQ_EDIT:
4369 if (!status)
4370 return request;
4372 open_editor(status->status != '?', status->new.name);
4373 break;
4375 case REQ_VIEW_BLAME:
4376 if (status) {
4377 string_copy(opt_file, status->new.name);
4378 opt_ref[0] = 0;
4379 }
4380 return request;
4382 case REQ_ENTER:
4383 /* After returning the status view has been split to
4384 * show the stage view. No further reloading is
4385 * necessary. */
4386 status_enter(view, line);
4387 return REQ_NONE;
4389 case REQ_REFRESH:
4390 /* Simply reload the view. */
4391 break;
4393 default:
4394 return request;
4395 }
4397 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4399 return REQ_NONE;
4400 }
4402 static void
4403 status_select(struct view *view, struct line *line)
4404 {
4405 struct status *status = line->data;
4406 char file[SIZEOF_STR] = "all files";
4407 char *text;
4408 char *key;
4410 if (status && !string_format(file, "'%s'", status->new.name))
4411 return;
4413 if (!status && line[1].type == LINE_STAT_NONE)
4414 line++;
4416 switch (line->type) {
4417 case LINE_STAT_STAGED:
4418 text = "Press %s to unstage %s for commit";
4419 break;
4421 case LINE_STAT_UNSTAGED:
4422 text = "Press %s to stage %s for commit";
4423 break;
4425 case LINE_STAT_UNTRACKED:
4426 text = "Press %s to stage %s for addition";
4427 break;
4429 case LINE_STAT_HEAD:
4430 case LINE_STAT_NONE:
4431 text = "Nothing to update";
4432 break;
4434 default:
4435 die("line type %d not handled in switch", line->type);
4436 }
4438 if (status && status->status == 'U') {
4439 text = "Press %s to resolve conflict in %s";
4440 key = get_key(REQ_STATUS_MERGE);
4442 } else {
4443 key = get_key(REQ_STATUS_UPDATE);
4444 }
4446 string_format(view->ref, text, key, file);
4447 }
4449 static bool
4450 status_grep(struct view *view, struct line *line)
4451 {
4452 struct status *status = line->data;
4453 enum { S_STATUS, S_NAME, S_END } state;
4454 char buf[2] = "?";
4455 regmatch_t pmatch;
4457 if (!status)
4458 return FALSE;
4460 for (state = S_STATUS; state < S_END; state++) {
4461 char *text;
4463 switch (state) {
4464 case S_NAME: text = status->new.name; break;
4465 case S_STATUS:
4466 buf[0] = status->status;
4467 text = buf;
4468 break;
4470 default:
4471 return FALSE;
4472 }
4474 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4475 return TRUE;
4476 }
4478 return FALSE;
4479 }
4481 static struct view_ops status_ops = {
4482 "file",
4483 status_open,
4484 NULL,
4485 status_draw,
4486 status_request,
4487 status_grep,
4488 status_select,
4489 };
4492 static bool
4493 stage_diff_line(FILE *pipe, struct line *line)
4494 {
4495 char *buf = line->data;
4496 size_t bufsize = strlen(buf);
4497 size_t written = 0;
4499 while (!ferror(pipe) && written < bufsize) {
4500 written += fwrite(buf + written, 1, bufsize - written, pipe);
4501 }
4503 fputc('\n', pipe);
4505 return written == bufsize;
4506 }
4508 static bool
4509 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4510 {
4511 while (line < end) {
4512 if (!stage_diff_line(pipe, line++))
4513 return FALSE;
4514 if (line->type == LINE_DIFF_CHUNK ||
4515 line->type == LINE_DIFF_HEADER)
4516 break;
4517 }
4519 return TRUE;
4520 }
4522 static struct line *
4523 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4524 {
4525 for (; view->line < line; line--)
4526 if (line->type == type)
4527 return line;
4529 return NULL;
4530 }
4532 static bool
4533 stage_update_chunk(struct view *view, struct line *chunk)
4534 {
4535 char cmd[SIZEOF_STR];
4536 size_t cmdsize = 0;
4537 struct line *diff_hdr;
4538 FILE *pipe;
4540 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4541 if (!diff_hdr)
4542 return FALSE;
4544 if (opt_cdup[0] &&
4545 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4546 return FALSE;
4548 if (!string_format_from(cmd, &cmdsize,
4549 "git apply --whitespace=nowarn --cached %s - && "
4550 "git update-index -q --unmerged --refresh 2>/dev/null",
4551 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4552 return FALSE;
4554 pipe = popen(cmd, "w");
4555 if (!pipe)
4556 return FALSE;
4558 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4559 !stage_diff_write(pipe, chunk, view->line + view->lines))
4560 chunk = NULL;
4562 pclose(pipe);
4564 return chunk ? TRUE : FALSE;
4565 }
4567 static bool
4568 stage_update(struct view *view, struct line *line)
4569 {
4570 struct line *chunk = NULL;
4572 if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4573 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4575 if (chunk) {
4576 if (!stage_update_chunk(view, chunk)) {
4577 report("Failed to apply chunk");
4578 return FALSE;
4579 }
4581 } else if (!stage_status.status) {
4582 view = VIEW(REQ_VIEW_STATUS);
4584 for (line = view->line; line < view->line + view->lines; line++)
4585 if (line->type == stage_line_type)
4586 break;
4588 if (!status_update_files(view, line + 1)) {
4589 report("Failed to update files");
4590 return FALSE;
4591 }
4593 } else if (!status_update_file(&stage_status, stage_line_type)) {
4594 report("Failed to update file");
4595 return FALSE;
4596 }
4598 return TRUE;
4599 }
4601 static void
4602 stage_next(struct view *view, struct line *line)
4603 {
4604 int i;
4606 if (!stage_chunks) {
4607 static size_t alloc = 0;
4608 int *tmp;
4610 for (line = view->line; line < view->line + view->lines; line++) {
4611 if (line->type != LINE_DIFF_CHUNK)
4612 continue;
4614 tmp = realloc_items(stage_chunk, &alloc,
4615 stage_chunks, sizeof(*tmp));
4616 if (!tmp) {
4617 report("Allocation failure");
4618 return;
4619 }
4621 stage_chunk = tmp;
4622 stage_chunk[stage_chunks++] = line - view->line;
4623 }
4624 }
4626 for (i = 0; i < stage_chunks; i++) {
4627 if (stage_chunk[i] > view->lineno) {
4628 do_scroll_view(view, stage_chunk[i] - view->lineno);
4629 report("Chunk %d of %d", i + 1, stage_chunks);
4630 return;
4631 }
4632 }
4634 report("No next chunk found");
4635 }
4637 static enum request
4638 stage_request(struct view *view, enum request request, struct line *line)
4639 {
4640 switch (request) {
4641 case REQ_STATUS_UPDATE:
4642 if (!stage_update(view, line))
4643 return REQ_NONE;
4644 break;
4646 case REQ_STAGE_NEXT:
4647 if (stage_line_type == LINE_STAT_UNTRACKED) {
4648 report("File is untracked; press %s to add",
4649 get_key(REQ_STATUS_UPDATE));
4650 return REQ_NONE;
4651 }
4652 stage_next(view, line);
4653 return REQ_NONE;
4655 case REQ_EDIT:
4656 if (!stage_status.new.name[0])
4657 return request;
4659 open_editor(stage_status.status != '?', stage_status.new.name);
4660 break;
4662 case REQ_REFRESH:
4663 /* Reload everything ... */
4664 break;
4666 case REQ_VIEW_BLAME:
4667 if (stage_status.new.name[0]) {
4668 string_copy(opt_file, stage_status.new.name);
4669 opt_ref[0] = 0;
4670 }
4671 return request;
4673 case REQ_ENTER:
4674 return pager_request(view, request, line);
4676 default:
4677 return request;
4678 }
4680 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4682 /* Check whether the staged entry still exists, and close the
4683 * stage view if it doesn't. */
4684 if (!status_exists(&stage_status, stage_line_type))
4685 return REQ_VIEW_CLOSE;
4687 if (stage_line_type == LINE_STAT_UNTRACKED)
4688 opt_pipe = fopen(stage_status.new.name, "r");
4689 else
4690 string_copy(opt_cmd, view->cmd);
4691 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4693 return REQ_NONE;
4694 }
4696 static struct view_ops stage_ops = {
4697 "line",
4698 NULL,
4699 pager_read,
4700 pager_draw,
4701 stage_request,
4702 pager_grep,
4703 pager_select,
4704 };
4707 /*
4708 * Revision graph
4709 */
4711 struct commit {
4712 char id[SIZEOF_REV]; /* SHA1 ID. */
4713 char title[128]; /* First line of the commit message. */
4714 char author[75]; /* Author of the commit. */
4715 struct tm time; /* Date from the author ident. */
4716 struct ref **refs; /* Repository references. */
4717 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
4718 size_t graph_size; /* The width of the graph array. */
4719 bool has_parents; /* Rewritten --parents seen. */
4720 };
4722 /* Size of rev graph with no "padding" columns */
4723 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4725 struct rev_graph {
4726 struct rev_graph *prev, *next, *parents;
4727 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4728 size_t size;
4729 struct commit *commit;
4730 size_t pos;
4731 unsigned int boundary:1;
4732 };
4734 /* Parents of the commit being visualized. */
4735 static struct rev_graph graph_parents[4];
4737 /* The current stack of revisions on the graph. */
4738 static struct rev_graph graph_stacks[4] = {
4739 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4740 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4741 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4742 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4743 };
4745 static inline bool
4746 graph_parent_is_merge(struct rev_graph *graph)
4747 {
4748 return graph->parents->size > 1;
4749 }
4751 static inline void
4752 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4753 {
4754 struct commit *commit = graph->commit;
4756 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4757 commit->graph[commit->graph_size++] = symbol;
4758 }
4760 static void
4761 done_rev_graph(struct rev_graph *graph)
4762 {
4763 if (graph_parent_is_merge(graph) &&
4764 graph->pos < graph->size - 1 &&
4765 graph->next->size == graph->size + graph->parents->size - 1) {
4766 size_t i = graph->pos + graph->parents->size - 1;
4768 graph->commit->graph_size = i * 2;
4769 while (i < graph->next->size - 1) {
4770 append_to_rev_graph(graph, ' ');
4771 append_to_rev_graph(graph, '\\');
4772 i++;
4773 }
4774 }
4776 graph->size = graph->pos = 0;
4777 graph->commit = NULL;
4778 memset(graph->parents, 0, sizeof(*graph->parents));
4779 }
4781 static void
4782 push_rev_graph(struct rev_graph *graph, char *parent)
4783 {
4784 int i;
4786 /* "Collapse" duplicate parents lines.
4787 *
4788 * FIXME: This needs to also update update the drawn graph but
4789 * for now it just serves as a method for pruning graph lines. */
4790 for (i = 0; i < graph->size; i++)
4791 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4792 return;
4794 if (graph->size < SIZEOF_REVITEMS) {
4795 string_copy_rev(graph->rev[graph->size++], parent);
4796 }
4797 }
4799 static chtype
4800 get_rev_graph_symbol(struct rev_graph *graph)
4801 {
4802 chtype symbol;
4804 if (graph->boundary)
4805 symbol = REVGRAPH_BOUND;
4806 else if (graph->parents->size == 0)
4807 symbol = REVGRAPH_INIT;
4808 else if (graph_parent_is_merge(graph))
4809 symbol = REVGRAPH_MERGE;
4810 else if (graph->pos >= graph->size)
4811 symbol = REVGRAPH_BRANCH;
4812 else
4813 symbol = REVGRAPH_COMMIT;
4815 return symbol;
4816 }
4818 static void
4819 draw_rev_graph(struct rev_graph *graph)
4820 {
4821 struct rev_filler {
4822 chtype separator, line;
4823 };
4824 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4825 static struct rev_filler fillers[] = {
4826 { ' ', '|' },
4827 { '`', '.' },
4828 { '\'', ' ' },
4829 { '/', ' ' },
4830 };
4831 chtype symbol = get_rev_graph_symbol(graph);
4832 struct rev_filler *filler;
4833 size_t i;
4835 if (opt_line_graphics)
4836 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4838 filler = &fillers[DEFAULT];
4840 for (i = 0; i < graph->pos; i++) {
4841 append_to_rev_graph(graph, filler->line);
4842 if (graph_parent_is_merge(graph->prev) &&
4843 graph->prev->pos == i)
4844 filler = &fillers[RSHARP];
4846 append_to_rev_graph(graph, filler->separator);
4847 }
4849 /* Place the symbol for this revision. */
4850 append_to_rev_graph(graph, symbol);
4852 if (graph->prev->size > graph->size)
4853 filler = &fillers[RDIAG];
4854 else
4855 filler = &fillers[DEFAULT];
4857 i++;
4859 for (; i < graph->size; i++) {
4860 append_to_rev_graph(graph, filler->separator);
4861 append_to_rev_graph(graph, filler->line);
4862 if (graph_parent_is_merge(graph->prev) &&
4863 i < graph->prev->pos + graph->parents->size)
4864 filler = &fillers[RSHARP];
4865 if (graph->prev->size > graph->size)
4866 filler = &fillers[LDIAG];
4867 }
4869 if (graph->prev->size > graph->size) {
4870 append_to_rev_graph(graph, filler->separator);
4871 if (filler->line != ' ')
4872 append_to_rev_graph(graph, filler->line);
4873 }
4874 }
4876 /* Prepare the next rev graph */
4877 static void
4878 prepare_rev_graph(struct rev_graph *graph)
4879 {
4880 size_t i;
4882 /* First, traverse all lines of revisions up to the active one. */
4883 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4884 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4885 break;
4887 push_rev_graph(graph->next, graph->rev[graph->pos]);
4888 }
4890 /* Interleave the new revision parent(s). */
4891 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4892 push_rev_graph(graph->next, graph->parents->rev[i]);
4894 /* Lastly, put any remaining revisions. */
4895 for (i = graph->pos + 1; i < graph->size; i++)
4896 push_rev_graph(graph->next, graph->rev[i]);
4897 }
4899 static void
4900 update_rev_graph(struct rev_graph *graph)
4901 {
4902 /* If this is the finalizing update ... */
4903 if (graph->commit)
4904 prepare_rev_graph(graph);
4906 /* Graph visualization needs a one rev look-ahead,
4907 * so the first update doesn't visualize anything. */
4908 if (!graph->prev->commit)
4909 return;
4911 draw_rev_graph(graph->prev);
4912 done_rev_graph(graph->prev->prev);
4913 }
4916 /*
4917 * Main view backend
4918 */
4920 static bool
4921 main_draw(struct view *view, struct line *line, unsigned int lineno)
4922 {
4923 struct commit *commit = line->data;
4925 if (!*commit->author)
4926 return FALSE;
4928 if (opt_date && draw_date(view, &commit->time))
4929 return TRUE;
4931 if (opt_author &&
4932 draw_field(view, LINE_MAIN_AUTHOR, commit->author, AUTHOR_COLS, TRUE))
4933 return TRUE;
4935 if (opt_rev_graph && commit->graph_size &&
4936 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
4937 return TRUE;
4939 if (opt_show_refs && commit->refs) {
4940 size_t i = 0;
4942 do {
4943 enum line_type type;
4945 if (commit->refs[i]->head)
4946 type = LINE_MAIN_HEAD;
4947 else if (commit->refs[i]->ltag)
4948 type = LINE_MAIN_LOCAL_TAG;
4949 else if (commit->refs[i]->tag)
4950 type = LINE_MAIN_TAG;
4951 else if (commit->refs[i]->tracked)
4952 type = LINE_MAIN_TRACKED;
4953 else if (commit->refs[i]->remote)
4954 type = LINE_MAIN_REMOTE;
4955 else
4956 type = LINE_MAIN_REF;
4958 if (draw_text(view, type, "[", TRUE) ||
4959 draw_text(view, type, commit->refs[i]->name, TRUE) ||
4960 draw_text(view, type, "]", TRUE))
4961 return TRUE;
4963 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
4964 return TRUE;
4965 } while (commit->refs[i++]->next);
4966 }
4968 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
4969 return TRUE;
4970 }
4972 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4973 static bool
4974 main_read(struct view *view, char *line)
4975 {
4976 static struct rev_graph *graph = graph_stacks;
4977 enum line_type type;
4978 struct commit *commit;
4980 if (!line) {
4981 if (!view->lines && !view->parent)
4982 die("No revisions match the given arguments.");
4983 update_rev_graph(graph);
4984 return TRUE;
4985 }
4987 type = get_line_type(line);
4988 if (type == LINE_COMMIT) {
4989 commit = calloc(1, sizeof(struct commit));
4990 if (!commit)
4991 return FALSE;
4993 line += STRING_SIZE("commit ");
4994 if (*line == '-') {
4995 graph->boundary = 1;
4996 line++;
4997 }
4999 string_copy_rev(commit->id, line);
5000 commit->refs = get_refs(commit->id);
5001 graph->commit = commit;
5002 add_line_data(view, commit, LINE_MAIN_COMMIT);
5004 while ((line = strchr(line, ' '))) {
5005 line++;
5006 push_rev_graph(graph->parents, line);
5007 commit->has_parents = TRUE;
5008 }
5009 return TRUE;
5010 }
5012 if (!view->lines)
5013 return TRUE;
5014 commit = view->line[view->lines - 1].data;
5016 switch (type) {
5017 case LINE_PARENT:
5018 if (commit->has_parents)
5019 break;
5020 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5021 break;
5023 case LINE_AUTHOR:
5024 {
5025 /* Parse author lines where the name may be empty:
5026 * author <email@address.tld> 1138474660 +0100
5027 */
5028 char *ident = line + STRING_SIZE("author ");
5029 char *nameend = strchr(ident, '<');
5030 char *emailend = strchr(ident, '>');
5032 if (!nameend || !emailend)
5033 break;
5035 update_rev_graph(graph);
5036 graph = graph->next;
5038 *nameend = *emailend = 0;
5039 ident = chomp_string(ident);
5040 if (!*ident) {
5041 ident = chomp_string(nameend + 1);
5042 if (!*ident)
5043 ident = "Unknown";
5044 }
5046 string_ncopy(commit->author, ident, strlen(ident));
5048 /* Parse epoch and timezone */
5049 if (emailend[1] == ' ') {
5050 char *secs = emailend + 2;
5051 char *zone = strchr(secs, ' ');
5052 time_t time = (time_t) atol(secs);
5054 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5055 long tz;
5057 zone++;
5058 tz = ('0' - zone[1]) * 60 * 60 * 10;
5059 tz += ('0' - zone[2]) * 60 * 60;
5060 tz += ('0' - zone[3]) * 60;
5061 tz += ('0' - zone[4]) * 60;
5063 if (zone[0] == '-')
5064 tz = -tz;
5066 time -= tz;
5067 }
5069 gmtime_r(&time, &commit->time);
5070 }
5071 break;
5072 }
5073 default:
5074 /* Fill in the commit title if it has not already been set. */
5075 if (commit->title[0])
5076 break;
5078 /* Require titles to start with a non-space character at the
5079 * offset used by git log. */
5080 if (strncmp(line, " ", 4))
5081 break;
5082 line += 4;
5083 /* Well, if the title starts with a whitespace character,
5084 * try to be forgiving. Otherwise we end up with no title. */
5085 while (isspace(*line))
5086 line++;
5087 if (*line == '\0')
5088 break;
5089 /* FIXME: More graceful handling of titles; append "..." to
5090 * shortened titles, etc. */
5092 string_ncopy(commit->title, line, strlen(line));
5093 }
5095 return TRUE;
5096 }
5098 static enum request
5099 main_request(struct view *view, enum request request, struct line *line)
5100 {
5101 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5103 if (request == REQ_ENTER)
5104 open_view(view, REQ_VIEW_DIFF, flags);
5105 else
5106 return request;
5108 return REQ_NONE;
5109 }
5111 static bool
5112 grep_refs(struct ref **refs, regex_t *regex)
5113 {
5114 regmatch_t pmatch;
5115 size_t i = 0;
5117 if (!refs)
5118 return FALSE;
5119 do {
5120 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5121 return TRUE;
5122 } while (refs[i++]->next);
5124 return FALSE;
5125 }
5127 static bool
5128 main_grep(struct view *view, struct line *line)
5129 {
5130 struct commit *commit = line->data;
5131 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5132 char buf[DATE_COLS + 1];
5133 regmatch_t pmatch;
5135 for (state = S_TITLE; state < S_END; state++) {
5136 char *text;
5138 switch (state) {
5139 case S_TITLE: text = commit->title; break;
5140 case S_AUTHOR:
5141 if (!opt_author)
5142 continue;
5143 text = commit->author;
5144 break;
5145 case S_DATE:
5146 if (!opt_date)
5147 continue;
5148 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5149 continue;
5150 text = buf;
5151 break;
5152 case S_REFS:
5153 if (!opt_show_refs)
5154 continue;
5155 if (grep_refs(commit->refs, view->regex) == TRUE)
5156 return TRUE;
5157 continue;
5158 default:
5159 return FALSE;
5160 }
5162 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5163 return TRUE;
5164 }
5166 return FALSE;
5167 }
5169 static void
5170 main_select(struct view *view, struct line *line)
5171 {
5172 struct commit *commit = line->data;
5174 string_copy_rev(view->ref, commit->id);
5175 string_copy_rev(ref_commit, view->ref);
5176 }
5178 static struct view_ops main_ops = {
5179 "commit",
5180 NULL,
5181 main_read,
5182 main_draw,
5183 main_request,
5184 main_grep,
5185 main_select,
5186 };
5189 /*
5190 * Unicode / UTF-8 handling
5191 *
5192 * NOTE: Much of the following code for dealing with unicode is derived from
5193 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5194 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5195 */
5197 /* I've (over)annotated a lot of code snippets because I am not entirely
5198 * confident that the approach taken by this small UTF-8 interface is correct.
5199 * --jonas */
5201 static inline int
5202 unicode_width(unsigned long c)
5203 {
5204 if (c >= 0x1100 &&
5205 (c <= 0x115f /* Hangul Jamo */
5206 || c == 0x2329
5207 || c == 0x232a
5208 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5209 /* CJK ... Yi */
5210 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5211 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5212 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5213 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5214 || (c >= 0xffe0 && c <= 0xffe6)
5215 || (c >= 0x20000 && c <= 0x2fffd)
5216 || (c >= 0x30000 && c <= 0x3fffd)))
5217 return 2;
5219 if (c == '\t')
5220 return opt_tab_size;
5222 return 1;
5223 }
5225 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5226 * Illegal bytes are set one. */
5227 static const unsigned char utf8_bytes[256] = {
5228 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,
5229 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,
5230 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,
5231 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,
5232 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,
5233 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,
5234 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,
5235 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,
5236 };
5238 /* Decode UTF-8 multi-byte representation into a unicode character. */
5239 static inline unsigned long
5240 utf8_to_unicode(const char *string, size_t length)
5241 {
5242 unsigned long unicode;
5244 switch (length) {
5245 case 1:
5246 unicode = string[0];
5247 break;
5248 case 2:
5249 unicode = (string[0] & 0x1f) << 6;
5250 unicode += (string[1] & 0x3f);
5251 break;
5252 case 3:
5253 unicode = (string[0] & 0x0f) << 12;
5254 unicode += ((string[1] & 0x3f) << 6);
5255 unicode += (string[2] & 0x3f);
5256 break;
5257 case 4:
5258 unicode = (string[0] & 0x0f) << 18;
5259 unicode += ((string[1] & 0x3f) << 12);
5260 unicode += ((string[2] & 0x3f) << 6);
5261 unicode += (string[3] & 0x3f);
5262 break;
5263 case 5:
5264 unicode = (string[0] & 0x0f) << 24;
5265 unicode += ((string[1] & 0x3f) << 18);
5266 unicode += ((string[2] & 0x3f) << 12);
5267 unicode += ((string[3] & 0x3f) << 6);
5268 unicode += (string[4] & 0x3f);
5269 break;
5270 case 6:
5271 unicode = (string[0] & 0x01) << 30;
5272 unicode += ((string[1] & 0x3f) << 24);
5273 unicode += ((string[2] & 0x3f) << 18);
5274 unicode += ((string[3] & 0x3f) << 12);
5275 unicode += ((string[4] & 0x3f) << 6);
5276 unicode += (string[5] & 0x3f);
5277 break;
5278 default:
5279 die("Invalid unicode length");
5280 }
5282 /* Invalid characters could return the special 0xfffd value but NUL
5283 * should be just as good. */
5284 return unicode > 0xffff ? 0 : unicode;
5285 }
5287 /* Calculates how much of string can be shown within the given maximum width
5288 * and sets trimmed parameter to non-zero value if all of string could not be
5289 * shown. If the reserve flag is TRUE, it will reserve at least one
5290 * trailing character, which can be useful when drawing a delimiter.
5291 *
5292 * Returns the number of bytes to output from string to satisfy max_width. */
5293 static size_t
5294 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5295 {
5296 const char *start = string;
5297 const char *end = strchr(string, '\0');
5298 unsigned char last_bytes = 0;
5299 size_t last_ucwidth = 0;
5301 *width = 0;
5302 *trimmed = 0;
5304 while (string < end) {
5305 int c = *(unsigned char *) string;
5306 unsigned char bytes = utf8_bytes[c];
5307 size_t ucwidth;
5308 unsigned long unicode;
5310 if (string + bytes > end)
5311 break;
5313 /* Change representation to figure out whether
5314 * it is a single- or double-width character. */
5316 unicode = utf8_to_unicode(string, bytes);
5317 /* FIXME: Graceful handling of invalid unicode character. */
5318 if (!unicode)
5319 break;
5321 ucwidth = unicode_width(unicode);
5322 *width += ucwidth;
5323 if (*width > max_width) {
5324 *trimmed = 1;
5325 *width -= ucwidth;
5326 if (reserve && *width == max_width) {
5327 string -= last_bytes;
5328 *width -= last_ucwidth;
5329 }
5330 break;
5331 }
5333 string += bytes;
5334 last_bytes = bytes;
5335 last_ucwidth = ucwidth;
5336 }
5338 return string - start;
5339 }
5342 /*
5343 * Status management
5344 */
5346 /* Whether or not the curses interface has been initialized. */
5347 static bool cursed = FALSE;
5349 /* The status window is used for polling keystrokes. */
5350 static WINDOW *status_win;
5352 static bool status_empty = TRUE;
5354 /* Update status and title window. */
5355 static void
5356 report(const char *msg, ...)
5357 {
5358 struct view *view = display[current_view];
5360 if (input_mode)
5361 return;
5363 if (!view) {
5364 char buf[SIZEOF_STR];
5365 va_list args;
5367 va_start(args, msg);
5368 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5369 buf[sizeof(buf) - 1] = 0;
5370 buf[sizeof(buf) - 2] = '.';
5371 buf[sizeof(buf) - 3] = '.';
5372 buf[sizeof(buf) - 4] = '.';
5373 }
5374 va_end(args);
5375 die("%s", buf);
5376 }
5378 if (!status_empty || *msg) {
5379 va_list args;
5381 va_start(args, msg);
5383 wmove(status_win, 0, 0);
5384 if (*msg) {
5385 vwprintw(status_win, msg, args);
5386 status_empty = FALSE;
5387 } else {
5388 status_empty = TRUE;
5389 }
5390 wclrtoeol(status_win);
5391 wrefresh(status_win);
5393 va_end(args);
5394 }
5396 update_view_title(view);
5397 update_display_cursor(view);
5398 }
5400 /* Controls when nodelay should be in effect when polling user input. */
5401 static void
5402 set_nonblocking_input(bool loading)
5403 {
5404 static unsigned int loading_views;
5406 if ((loading == FALSE && loading_views-- == 1) ||
5407 (loading == TRUE && loading_views++ == 0))
5408 nodelay(status_win, loading);
5409 }
5411 static void
5412 init_display(void)
5413 {
5414 int x, y;
5416 /* Initialize the curses library */
5417 if (isatty(STDIN_FILENO)) {
5418 cursed = !!initscr();
5419 } else {
5420 /* Leave stdin and stdout alone when acting as a pager. */
5421 FILE *io = fopen("/dev/tty", "r+");
5423 if (!io)
5424 die("Failed to open /dev/tty");
5425 cursed = !!newterm(NULL, io, io);
5426 }
5428 if (!cursed)
5429 die("Failed to initialize curses");
5431 nonl(); /* Tell curses not to do NL->CR/NL on output */
5432 cbreak(); /* Take input chars one at a time, no wait for \n */
5433 noecho(); /* Don't echo input */
5434 leaveok(stdscr, TRUE);
5436 if (has_colors())
5437 init_colors();
5439 getmaxyx(stdscr, y, x);
5440 status_win = newwin(1, 0, y - 1, 0);
5441 if (!status_win)
5442 die("Failed to create status window");
5444 /* Enable keyboard mapping */
5445 keypad(status_win, TRUE);
5446 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5448 TABSIZE = opt_tab_size;
5449 if (opt_line_graphics) {
5450 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5451 }
5452 }
5454 static char *
5455 read_prompt(const char *prompt)
5456 {
5457 enum { READING, STOP, CANCEL } status = READING;
5458 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5459 int pos = 0;
5461 while (status == READING) {
5462 struct view *view;
5463 int i, key;
5465 input_mode = TRUE;
5467 foreach_view (view, i)
5468 update_view(view);
5470 input_mode = FALSE;
5472 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5473 wclrtoeol(status_win);
5475 /* Refresh, accept single keystroke of input */
5476 key = wgetch(status_win);
5477 switch (key) {
5478 case KEY_RETURN:
5479 case KEY_ENTER:
5480 case '\n':
5481 status = pos ? STOP : CANCEL;
5482 break;
5484 case KEY_BACKSPACE:
5485 if (pos > 0)
5486 pos--;
5487 else
5488 status = CANCEL;
5489 break;
5491 case KEY_ESC:
5492 status = CANCEL;
5493 break;
5495 case ERR:
5496 break;
5498 default:
5499 if (pos >= sizeof(buf)) {
5500 report("Input string too long");
5501 return NULL;
5502 }
5504 if (isprint(key))
5505 buf[pos++] = (char) key;
5506 }
5507 }
5509 /* Clear the status window */
5510 status_empty = FALSE;
5511 report("");
5513 if (status == CANCEL)
5514 return NULL;
5516 buf[pos++] = 0;
5518 return buf;
5519 }
5521 /*
5522 * Repository references
5523 */
5525 static struct ref *refs = NULL;
5526 static size_t refs_alloc = 0;
5527 static size_t refs_size = 0;
5529 /* Id <-> ref store */
5530 static struct ref ***id_refs = NULL;
5531 static size_t id_refs_alloc = 0;
5532 static size_t id_refs_size = 0;
5534 static struct ref **
5535 get_refs(char *id)
5536 {
5537 struct ref ***tmp_id_refs;
5538 struct ref **ref_list = NULL;
5539 size_t ref_list_alloc = 0;
5540 size_t ref_list_size = 0;
5541 size_t i;
5543 for (i = 0; i < id_refs_size; i++)
5544 if (!strcmp(id, id_refs[i][0]->id))
5545 return id_refs[i];
5547 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5548 sizeof(*id_refs));
5549 if (!tmp_id_refs)
5550 return NULL;
5552 id_refs = tmp_id_refs;
5554 for (i = 0; i < refs_size; i++) {
5555 struct ref **tmp;
5557 if (strcmp(id, refs[i].id))
5558 continue;
5560 tmp = realloc_items(ref_list, &ref_list_alloc,
5561 ref_list_size + 1, sizeof(*ref_list));
5562 if (!tmp) {
5563 if (ref_list)
5564 free(ref_list);
5565 return NULL;
5566 }
5568 ref_list = tmp;
5569 if (ref_list_size > 0)
5570 ref_list[ref_list_size - 1]->next = 1;
5571 ref_list[ref_list_size] = &refs[i];
5573 /* XXX: The properties of the commit chains ensures that we can
5574 * safely modify the shared ref. The repo references will
5575 * always be similar for the same id. */
5576 ref_list[ref_list_size]->next = 0;
5577 ref_list_size++;
5578 }
5580 if (ref_list)
5581 id_refs[id_refs_size++] = ref_list;
5583 return ref_list;
5584 }
5586 static int
5587 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5588 {
5589 struct ref *ref;
5590 bool tag = FALSE;
5591 bool ltag = FALSE;
5592 bool remote = FALSE;
5593 bool tracked = FALSE;
5594 bool check_replace = FALSE;
5595 bool head = FALSE;
5597 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5598 if (!strcmp(name + namelen - 3, "^{}")) {
5599 namelen -= 3;
5600 name[namelen] = 0;
5601 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5602 check_replace = TRUE;
5603 } else {
5604 ltag = TRUE;
5605 }
5607 tag = TRUE;
5608 namelen -= STRING_SIZE("refs/tags/");
5609 name += STRING_SIZE("refs/tags/");
5611 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5612 remote = TRUE;
5613 namelen -= STRING_SIZE("refs/remotes/");
5614 name += STRING_SIZE("refs/remotes/");
5615 tracked = !strcmp(opt_remote, name);
5617 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5618 namelen -= STRING_SIZE("refs/heads/");
5619 name += STRING_SIZE("refs/heads/");
5620 head = !strncmp(opt_head, name, namelen);
5622 } else if (!strcmp(name, "HEAD")) {
5623 opt_no_head = FALSE;
5624 return OK;
5625 }
5627 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5628 /* it's an annotated tag, replace the previous sha1 with the
5629 * resolved commit id; relies on the fact git-ls-remote lists
5630 * the commit id of an annotated tag right beofre the commit id
5631 * it points to. */
5632 refs[refs_size - 1].ltag = ltag;
5633 string_copy_rev(refs[refs_size - 1].id, id);
5635 return OK;
5636 }
5637 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5638 if (!refs)
5639 return ERR;
5641 ref = &refs[refs_size++];
5642 ref->name = malloc(namelen + 1);
5643 if (!ref->name)
5644 return ERR;
5646 strncpy(ref->name, name, namelen);
5647 ref->name[namelen] = 0;
5648 ref->head = head;
5649 ref->tag = tag;
5650 ref->ltag = ltag;
5651 ref->remote = remote;
5652 ref->tracked = tracked;
5653 string_copy_rev(ref->id, id);
5655 return OK;
5656 }
5658 static int
5659 load_refs(void)
5660 {
5661 const char *cmd_env = getenv("TIG_LS_REMOTE");
5662 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5664 return read_properties(popen(cmd, "r"), "\t", read_ref);
5665 }
5667 static int
5668 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5669 {
5670 if (!strcmp(name, "i18n.commitencoding"))
5671 string_ncopy(opt_encoding, value, valuelen);
5673 if (!strcmp(name, "core.editor"))
5674 string_ncopy(opt_editor, value, valuelen);
5676 /* branch.<head>.remote */
5677 if (*opt_head &&
5678 !strncmp(name, "branch.", 7) &&
5679 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5680 !strcmp(name + 7 + strlen(opt_head), ".remote"))
5681 string_ncopy(opt_remote, value, valuelen);
5683 if (*opt_head && *opt_remote &&
5684 !strncmp(name, "branch.", 7) &&
5685 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5686 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5687 size_t from = strlen(opt_remote);
5689 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5690 value += STRING_SIZE("refs/heads/");
5691 valuelen -= STRING_SIZE("refs/heads/");
5692 }
5694 if (!string_format_from(opt_remote, &from, "/%s", value))
5695 opt_remote[0] = 0;
5696 }
5698 return OK;
5699 }
5701 static int
5702 load_git_config(void)
5703 {
5704 return read_properties(popen(GIT_CONFIG " --list", "r"),
5705 "=", read_repo_config_option);
5706 }
5708 static int
5709 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5710 {
5711 if (!opt_git_dir[0]) {
5712 string_ncopy(opt_git_dir, name, namelen);
5714 } else if (opt_is_inside_work_tree == -1) {
5715 /* This can be 3 different values depending on the
5716 * version of git being used. If git-rev-parse does not
5717 * understand --is-inside-work-tree it will simply echo
5718 * the option else either "true" or "false" is printed.
5719 * Default to true for the unknown case. */
5720 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5722 } else if (opt_cdup[0] == ' ') {
5723 string_ncopy(opt_cdup, name, namelen);
5724 } else {
5725 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5726 namelen -= STRING_SIZE("refs/heads/");
5727 name += STRING_SIZE("refs/heads/");
5728 string_ncopy(opt_head, name, namelen);
5729 }
5730 }
5732 return OK;
5733 }
5735 static int
5736 load_repo_info(void)
5737 {
5738 int result;
5739 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5740 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5742 /* XXX: The line outputted by "--show-cdup" can be empty so
5743 * initialize it to something invalid to make it possible to
5744 * detect whether it has been set or not. */
5745 opt_cdup[0] = ' ';
5747 result = read_properties(pipe, "=", read_repo_info);
5748 if (opt_cdup[0] == ' ')
5749 opt_cdup[0] = 0;
5751 return result;
5752 }
5754 static int
5755 read_properties(FILE *pipe, const char *separators,
5756 int (*read_property)(char *, size_t, char *, size_t))
5757 {
5758 char buffer[BUFSIZ];
5759 char *name;
5760 int state = OK;
5762 if (!pipe)
5763 return ERR;
5765 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5766 char *value;
5767 size_t namelen;
5768 size_t valuelen;
5770 name = chomp_string(name);
5771 namelen = strcspn(name, separators);
5773 if (name[namelen]) {
5774 name[namelen] = 0;
5775 value = chomp_string(name + namelen + 1);
5776 valuelen = strlen(value);
5778 } else {
5779 value = "";
5780 valuelen = 0;
5781 }
5783 state = read_property(name, namelen, value, valuelen);
5784 }
5786 if (state != ERR && ferror(pipe))
5787 state = ERR;
5789 pclose(pipe);
5791 return state;
5792 }
5795 /*
5796 * Main
5797 */
5799 static void __NORETURN
5800 quit(int sig)
5801 {
5802 /* XXX: Restore tty modes and let the OS cleanup the rest! */
5803 if (cursed)
5804 endwin();
5805 exit(0);
5806 }
5808 static void __NORETURN
5809 die(const char *err, ...)
5810 {
5811 va_list args;
5813 endwin();
5815 va_start(args, err);
5816 fputs("tig: ", stderr);
5817 vfprintf(stderr, err, args);
5818 fputs("\n", stderr);
5819 va_end(args);
5821 exit(1);
5822 }
5824 static void
5825 warn(const char *msg, ...)
5826 {
5827 va_list args;
5829 va_start(args, msg);
5830 fputs("tig warning: ", stderr);
5831 vfprintf(stderr, msg, args);
5832 fputs("\n", stderr);
5833 va_end(args);
5834 }
5836 int
5837 main(int argc, char *argv[])
5838 {
5839 struct view *view;
5840 enum request request;
5841 size_t i;
5843 signal(SIGINT, quit);
5845 if (setlocale(LC_ALL, "")) {
5846 char *codeset = nl_langinfo(CODESET);
5848 string_ncopy(opt_codeset, codeset, strlen(codeset));
5849 }
5851 if (load_repo_info() == ERR)
5852 die("Failed to load repo info.");
5854 if (load_options() == ERR)
5855 die("Failed to load user config.");
5857 if (load_git_config() == ERR)
5858 die("Failed to load repo config.");
5860 if (!parse_options(argc, argv))
5861 return 0;
5863 /* Require a git repository unless when running in pager mode. */
5864 if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5865 die("Not a git repository");
5867 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5868 opt_utf8 = FALSE;
5870 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5871 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5872 if (opt_iconv == ICONV_NONE)
5873 die("Failed to initialize character set conversion");
5874 }
5876 if (*opt_git_dir && load_refs() == ERR)
5877 die("Failed to load refs.");
5879 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5880 view->cmd_env = getenv(view->cmd_env);
5882 request = opt_request;
5884 init_display();
5886 while (view_driver(display[current_view], request)) {
5887 int key;
5888 int i;
5890 foreach_view (view, i)
5891 update_view(view);
5893 /* Refresh, accept single keystroke of input */
5894 key = wgetch(status_win);
5896 /* wgetch() with nodelay() enabled returns ERR when there's no
5897 * input. */
5898 if (key == ERR) {
5899 request = REQ_NONE;
5900 continue;
5901 }
5903 request = get_keybinding(display[current_view]->keymap, key);
5905 /* Some low-level request handling. This keeps access to
5906 * status_win restricted. */
5907 switch (request) {
5908 case REQ_PROMPT:
5909 {
5910 char *cmd = read_prompt(":");
5912 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5913 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5914 opt_request = REQ_VIEW_DIFF;
5915 } else {
5916 opt_request = REQ_VIEW_PAGER;
5917 }
5918 break;
5919 }
5921 request = REQ_NONE;
5922 break;
5923 }
5924 case REQ_SEARCH:
5925 case REQ_SEARCH_BACK:
5926 {
5927 const char *prompt = request == REQ_SEARCH
5928 ? "/" : "?";
5929 char *search = read_prompt(prompt);
5931 if (search)
5932 string_ncopy(opt_search, search, strlen(search));
5933 else
5934 request = REQ_NONE;
5935 break;
5936 }
5937 case REQ_SCREEN_RESIZE:
5938 {
5939 int height, width;
5941 getmaxyx(stdscr, height, width);
5943 /* Resize the status view and let the view driver take
5944 * care of resizing the displayed views. */
5945 wresize(status_win, 1, width);
5946 mvwin(status_win, height - 1, 0);
5947 wrefresh(status_win);
5948 break;
5949 }
5950 default:
5951 break;
5952 }
5953 }
5955 quit(0);
5957 return 0;
5958 }