67d0693f5f99dbee4913d0ca79b51b15712b6570
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 #include <curses.h>
50 #if __GNUC__ >= 3
51 #define __NORETURN __attribute__((__noreturn__))
52 #else
53 #define __NORETURN
54 #endif
56 static void __NORETURN die(const char *err, ...);
57 static void warn(const char *msg, ...);
58 static void report(const char *msg, ...);
59 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
60 static void set_nonblocking_input(bool loading);
61 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
63 #define ABS(x) ((x) >= 0 ? (x) : -(x))
64 #define MIN(x, y) ((x) < (y) ? (x) : (y))
66 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
67 #define STRING_SIZE(x) (sizeof(x) - 1)
69 #define SIZEOF_STR 1024 /* Default string size. */
70 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
71 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
73 /* Revision graph */
75 #define REVGRAPH_INIT 'I'
76 #define REVGRAPH_MERGE 'M'
77 #define REVGRAPH_BRANCH '+'
78 #define REVGRAPH_COMMIT '*'
79 #define REVGRAPH_BOUND '^'
81 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
83 /* This color name can be used to refer to the default term colors. */
84 #define COLOR_DEFAULT (-1)
86 #define ICONV_NONE ((iconv_t) -1)
87 #ifndef ICONV_CONST
88 #define ICONV_CONST /* nothing */
89 #endif
91 /* The format and size of the date column in the main view. */
92 #define DATE_FORMAT "%Y-%m-%d %H:%M"
93 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
95 #define AUTHOR_COLS 20
96 #define ID_COLS 8
98 /* The default interval between line numbers. */
99 #define NUMBER_INTERVAL 5
101 #define TAB_SIZE 8
103 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
105 #define NULL_ID "0000000000000000000000000000000000000000"
107 #ifndef GIT_CONFIG
108 #define GIT_CONFIG "git config"
109 #endif
111 #define TIG_LS_REMOTE \
112 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
114 #define TIG_DIFF_CMD \
115 "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
117 #define TIG_LOG_CMD \
118 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
120 #define TIG_MAIN_CMD \
121 "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
123 #define TIG_TREE_CMD \
124 "git ls-tree %s %s"
126 #define TIG_BLOB_CMD \
127 "git cat-file blob %s"
129 /* XXX: Needs to be defined to the empty string. */
130 #define TIG_HELP_CMD ""
131 #define TIG_PAGER_CMD ""
132 #define TIG_STATUS_CMD ""
133 #define TIG_STAGE_CMD ""
134 #define TIG_BLAME_CMD ""
136 /* Some ascii-shorthands fitted into the ncurses namespace. */
137 #define KEY_TAB '\t'
138 #define KEY_RETURN '\r'
139 #define KEY_ESC 27
142 struct ref {
143 char *name; /* Ref name; tag or head names are shortened. */
144 char id[SIZEOF_REV]; /* Commit SHA1 ID */
145 unsigned int head:1; /* Is it the current HEAD? */
146 unsigned int tag:1; /* Is it a tag? */
147 unsigned int ltag:1; /* If so, is the tag local? */
148 unsigned int remote:1; /* Is it a remote ref? */
149 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
150 unsigned int next:1; /* For ref lists: are there more refs? */
151 };
153 static struct ref **get_refs(char *id);
155 struct int_map {
156 const char *name;
157 int namelen;
158 int value;
159 };
161 static int
162 set_from_int_map(struct int_map *map, size_t map_size,
163 int *value, const char *name, int namelen)
164 {
166 int i;
168 for (i = 0; i < map_size; i++)
169 if (namelen == map[i].namelen &&
170 !strncasecmp(name, map[i].name, namelen)) {
171 *value = map[i].value;
172 return OK;
173 }
175 return ERR;
176 }
179 /*
180 * String helpers
181 */
183 static inline void
184 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
185 {
186 if (srclen > dstlen - 1)
187 srclen = dstlen - 1;
189 strncpy(dst, src, srclen);
190 dst[srclen] = 0;
191 }
193 /* Shorthands for safely copying into a fixed buffer. */
195 #define string_copy(dst, src) \
196 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
198 #define string_ncopy(dst, src, srclen) \
199 string_ncopy_do(dst, sizeof(dst), src, srclen)
201 #define string_copy_rev(dst, src) \
202 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
204 #define string_add(dst, from, src) \
205 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
207 static char *
208 chomp_string(char *name)
209 {
210 int namelen;
212 while (isspace(*name))
213 name++;
215 namelen = strlen(name) - 1;
216 while (namelen > 0 && isspace(name[namelen]))
217 name[namelen--] = 0;
219 return name;
220 }
222 static bool
223 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
224 {
225 va_list args;
226 size_t pos = bufpos ? *bufpos : 0;
228 va_start(args, fmt);
229 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
230 va_end(args);
232 if (bufpos)
233 *bufpos = pos;
235 return pos >= bufsize ? FALSE : TRUE;
236 }
238 #define string_format(buf, fmt, args...) \
239 string_nformat(buf, sizeof(buf), NULL, fmt, args)
241 #define string_format_from(buf, from, fmt, args...) \
242 string_nformat(buf, sizeof(buf), from, fmt, args)
244 static int
245 string_enum_compare(const char *str1, const char *str2, int len)
246 {
247 size_t i;
249 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
251 /* Diff-Header == DIFF_HEADER */
252 for (i = 0; i < len; i++) {
253 if (toupper(str1[i]) == toupper(str2[i]))
254 continue;
256 if (string_enum_sep(str1[i]) &&
257 string_enum_sep(str2[i]))
258 continue;
260 return str1[i] - str2[i];
261 }
263 return 0;
264 }
266 /* Shell quoting
267 *
268 * NOTE: The following is a slightly modified copy of the git project's shell
269 * quoting routines found in the quote.c file.
270 *
271 * Help to copy the thing properly quoted for the shell safety. any single
272 * quote is replaced with '\'', any exclamation point is replaced with '\!',
273 * and the whole thing is enclosed in a
274 *
275 * E.g.
276 * original sq_quote result
277 * name ==> name ==> 'name'
278 * a b ==> a b ==> 'a b'
279 * a'b ==> a'\''b ==> 'a'\''b'
280 * a!b ==> a'\!'b ==> 'a'\!'b'
281 */
283 static size_t
284 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
285 {
286 char c;
288 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
290 BUFPUT('\'');
291 while ((c = *src++)) {
292 if (c == '\'' || c == '!') {
293 BUFPUT('\'');
294 BUFPUT('\\');
295 BUFPUT(c);
296 BUFPUT('\'');
297 } else {
298 BUFPUT(c);
299 }
300 }
301 BUFPUT('\'');
303 if (bufsize < SIZEOF_STR)
304 buf[bufsize] = 0;
306 return bufsize;
307 }
310 /*
311 * User requests
312 */
314 #define REQ_INFO \
315 /* XXX: Keep the view request first and in sync with views[]. */ \
316 REQ_GROUP("View switching") \
317 REQ_(VIEW_MAIN, "Show main view"), \
318 REQ_(VIEW_DIFF, "Show diff view"), \
319 REQ_(VIEW_LOG, "Show log view"), \
320 REQ_(VIEW_TREE, "Show tree view"), \
321 REQ_(VIEW_BLOB, "Show blob view"), \
322 REQ_(VIEW_BLAME, "Show blame view"), \
323 REQ_(VIEW_HELP, "Show help page"), \
324 REQ_(VIEW_PAGER, "Show pager view"), \
325 REQ_(VIEW_STATUS, "Show status view"), \
326 REQ_(VIEW_STAGE, "Show stage view"), \
327 \
328 REQ_GROUP("View manipulation") \
329 REQ_(ENTER, "Enter current line and scroll"), \
330 REQ_(NEXT, "Move to next"), \
331 REQ_(PREVIOUS, "Move to previous"), \
332 REQ_(VIEW_NEXT, "Move focus to next view"), \
333 REQ_(REFRESH, "Reload and refresh"), \
334 REQ_(MAXIMIZE, "Maximize the current view"), \
335 REQ_(VIEW_CLOSE, "Close the current view"), \
336 REQ_(QUIT, "Close all views and quit"), \
337 \
338 REQ_GROUP("Cursor navigation") \
339 REQ_(MOVE_UP, "Move cursor one line up"), \
340 REQ_(MOVE_DOWN, "Move cursor one line down"), \
341 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
342 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
343 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
344 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
345 \
346 REQ_GROUP("Scrolling") \
347 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
348 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
349 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
350 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
351 \
352 REQ_GROUP("Searching") \
353 REQ_(SEARCH, "Search the view"), \
354 REQ_(SEARCH_BACK, "Search backwards in the view"), \
355 REQ_(FIND_NEXT, "Find next search match"), \
356 REQ_(FIND_PREV, "Find previous search match"), \
357 \
358 REQ_GROUP("Misc") \
359 REQ_(PROMPT, "Bring up the prompt"), \
360 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
361 REQ_(SCREEN_RESIZE, "Resize the screen"), \
362 REQ_(SHOW_VERSION, "Show version information"), \
363 REQ_(STOP_LOADING, "Stop all loading views"), \
364 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
365 REQ_(TOGGLE_DATE, "Toggle date display"), \
366 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
367 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
368 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
369 REQ_(STATUS_UPDATE, "Update file status"), \
370 REQ_(STATUS_MERGE, "Merge file using external tool"), \
371 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
372 REQ_(EDIT, "Open in editor"), \
373 REQ_(NONE, "Do nothing")
376 /* User action requests. */
377 enum request {
378 #define REQ_GROUP(help)
379 #define REQ_(req, help) REQ_##req
381 /* Offset all requests to avoid conflicts with ncurses getch values. */
382 REQ_OFFSET = KEY_MAX + 1,
383 REQ_INFO
385 #undef REQ_GROUP
386 #undef REQ_
387 };
389 struct request_info {
390 enum request request;
391 char *name;
392 int namelen;
393 char *help;
394 };
396 static struct request_info req_info[] = {
397 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
398 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
399 REQ_INFO
400 #undef REQ_GROUP
401 #undef REQ_
402 };
404 static enum request
405 get_request(const char *name)
406 {
407 int namelen = strlen(name);
408 int i;
410 for (i = 0; i < ARRAY_SIZE(req_info); i++)
411 if (req_info[i].namelen == namelen &&
412 !string_enum_compare(req_info[i].name, name, namelen))
413 return req_info[i].request;
415 return REQ_NONE;
416 }
419 /*
420 * Options
421 */
423 static const char usage[] =
424 "tig " TIG_VERSION " (" __DATE__ ")\n"
425 "\n"
426 "Usage: tig [options] [revs] [--] [paths]\n"
427 " or: tig show [options] [revs] [--] [paths]\n"
428 " or: tig blame [rev] path\n"
429 " or: tig status\n"
430 " or: tig < [git command output]\n"
431 "\n"
432 "Options:\n"
433 " -v, --version Show version and exit\n"
434 " -h, --help Show help message and exit";
436 /* Option and state variables. */
437 static bool opt_date = TRUE;
438 static bool opt_author = TRUE;
439 static bool opt_line_number = FALSE;
440 static bool opt_line_graphics = TRUE;
441 static bool opt_rev_graph = FALSE;
442 static bool opt_show_refs = TRUE;
443 static int opt_num_interval = NUMBER_INTERVAL;
444 static int opt_tab_size = TAB_SIZE;
445 static enum request opt_request = REQ_VIEW_MAIN;
446 static char opt_cmd[SIZEOF_STR] = "";
447 static char opt_path[SIZEOF_STR] = "";
448 static char opt_file[SIZEOF_STR] = "";
449 static char opt_ref[SIZEOF_REF] = "";
450 static char opt_head[SIZEOF_REF] = "";
451 static char opt_remote[SIZEOF_REF] = "";
452 static bool opt_no_head = TRUE;
453 static FILE *opt_pipe = NULL;
454 static char opt_encoding[20] = "UTF-8";
455 static bool opt_utf8 = TRUE;
456 static char opt_codeset[20] = "UTF-8";
457 static iconv_t opt_iconv = ICONV_NONE;
458 static char opt_search[SIZEOF_STR] = "";
459 static char opt_cdup[SIZEOF_STR] = "";
460 static char opt_git_dir[SIZEOF_STR] = "";
461 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
462 static char opt_editor[SIZEOF_STR] = "";
464 static bool
465 parse_options(int argc, char *argv[])
466 {
467 size_t buf_size;
468 char *subcommand;
469 bool seen_dashdash = FALSE;
470 int i;
472 if (!isatty(STDIN_FILENO)) {
473 opt_request = REQ_VIEW_PAGER;
474 opt_pipe = stdin;
475 return TRUE;
476 }
478 if (argc <= 1)
479 return TRUE;
481 subcommand = argv[1];
482 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
483 opt_request = REQ_VIEW_STATUS;
484 if (!strcmp(subcommand, "-S"))
485 warn("`-S' has been deprecated; use `tig status' instead");
486 if (argc > 2)
487 warn("ignoring arguments after `%s'", subcommand);
488 return TRUE;
490 } else if (!strcmp(subcommand, "blame")) {
491 opt_request = REQ_VIEW_BLAME;
492 if (argc <= 2 || argc > 4)
493 die("invalid number of options to blame\n\n%s", usage);
495 i = 2;
496 if (argc == 4) {
497 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
498 i++;
499 }
501 string_ncopy(opt_file, argv[i], strlen(argv[i]));
502 return TRUE;
504 } else if (!strcmp(subcommand, "show")) {
505 opt_request = REQ_VIEW_DIFF;
507 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
508 opt_request = subcommand[0] == 'l'
509 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
510 warn("`tig %s' has been deprecated", subcommand);
512 } else {
513 subcommand = NULL;
514 }
516 if (!subcommand)
517 /* XXX: This is vulnerable to the user overriding
518 * options required for the main view parser. */
519 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
520 else
521 string_format(opt_cmd, "git %s", subcommand);
523 buf_size = strlen(opt_cmd);
525 for (i = 1 + !!subcommand; i < argc; i++) {
526 char *opt = argv[i];
528 if (seen_dashdash || !strcmp(opt, "--")) {
529 seen_dashdash = TRUE;
531 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
532 printf("tig version %s\n", TIG_VERSION);
533 return FALSE;
535 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
536 printf("%s\n", usage);
537 return FALSE;
538 }
540 opt_cmd[buf_size++] = ' ';
541 buf_size = sq_quote(opt_cmd, buf_size, opt);
542 if (buf_size >= sizeof(opt_cmd))
543 die("command too long");
544 }
546 opt_cmd[buf_size] = 0;
548 return TRUE;
549 }
552 /*
553 * Line-oriented content detection.
554 */
556 #define LINE_INFO \
557 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
558 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
559 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
560 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
561 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
562 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
563 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
564 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
565 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
566 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
567 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
568 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
569 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
570 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
571 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
572 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
573 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
574 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
575 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
576 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
577 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
578 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
579 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
580 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
581 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
582 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
583 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
584 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
585 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
586 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
587 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
588 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
589 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
590 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
591 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
592 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
593 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
594 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
595 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
596 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
597 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
598 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
599 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
600 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
601 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
602 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
603 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
604 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
605 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
606 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
607 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
608 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
609 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
610 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
612 enum line_type {
613 #define LINE(type, line, fg, bg, attr) \
614 LINE_##type
615 LINE_INFO,
616 LINE_NONE
617 #undef LINE
618 };
620 struct line_info {
621 const char *name; /* Option name. */
622 int namelen; /* Size of option name. */
623 const char *line; /* The start of line to match. */
624 int linelen; /* Size of string to match. */
625 int fg, bg, attr; /* Color and text attributes for the lines. */
626 };
628 static struct line_info line_info[] = {
629 #define LINE(type, line, fg, bg, attr) \
630 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
631 LINE_INFO
632 #undef LINE
633 };
635 static enum line_type
636 get_line_type(char *line)
637 {
638 int linelen = strlen(line);
639 enum line_type type;
641 for (type = 0; type < ARRAY_SIZE(line_info); type++)
642 /* Case insensitive search matches Signed-off-by lines better. */
643 if (linelen >= line_info[type].linelen &&
644 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
645 return type;
647 return LINE_DEFAULT;
648 }
650 static inline int
651 get_line_attr(enum line_type type)
652 {
653 assert(type < ARRAY_SIZE(line_info));
654 return COLOR_PAIR(type) | line_info[type].attr;
655 }
657 static struct line_info *
658 get_line_info(char *name)
659 {
660 size_t namelen = strlen(name);
661 enum line_type type;
663 for (type = 0; type < ARRAY_SIZE(line_info); type++)
664 if (namelen == line_info[type].namelen &&
665 !string_enum_compare(line_info[type].name, name, namelen))
666 return &line_info[type];
668 return NULL;
669 }
671 static void
672 init_colors(void)
673 {
674 int default_bg = line_info[LINE_DEFAULT].bg;
675 int default_fg = line_info[LINE_DEFAULT].fg;
676 enum line_type type;
678 start_color();
680 if (assume_default_colors(default_fg, default_bg) == ERR) {
681 default_bg = COLOR_BLACK;
682 default_fg = COLOR_WHITE;
683 }
685 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
686 struct line_info *info = &line_info[type];
687 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
688 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
690 init_pair(type, fg, bg);
691 }
692 }
694 struct line {
695 enum line_type type;
697 /* State flags */
698 unsigned int selected:1;
699 unsigned int dirty:1;
701 void *data; /* User data */
702 };
705 /*
706 * Keys
707 */
709 struct keybinding {
710 int alias;
711 enum request request;
712 struct keybinding *next;
713 };
715 static struct keybinding default_keybindings[] = {
716 /* View switching */
717 { 'm', REQ_VIEW_MAIN },
718 { 'd', REQ_VIEW_DIFF },
719 { 'l', REQ_VIEW_LOG },
720 { 't', REQ_VIEW_TREE },
721 { 'f', REQ_VIEW_BLOB },
722 { 'B', REQ_VIEW_BLAME },
723 { 'p', REQ_VIEW_PAGER },
724 { 'h', REQ_VIEW_HELP },
725 { 'S', REQ_VIEW_STATUS },
726 { 'c', REQ_VIEW_STAGE },
728 /* View manipulation */
729 { 'q', REQ_VIEW_CLOSE },
730 { KEY_TAB, REQ_VIEW_NEXT },
731 { KEY_RETURN, REQ_ENTER },
732 { KEY_UP, REQ_PREVIOUS },
733 { KEY_DOWN, REQ_NEXT },
734 { 'R', REQ_REFRESH },
735 { KEY_F(5), REQ_REFRESH },
736 { 'O', REQ_MAXIMIZE },
738 /* Cursor navigation */
739 { 'k', REQ_MOVE_UP },
740 { 'j', REQ_MOVE_DOWN },
741 { KEY_HOME, REQ_MOVE_FIRST_LINE },
742 { KEY_END, REQ_MOVE_LAST_LINE },
743 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
744 { ' ', REQ_MOVE_PAGE_DOWN },
745 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
746 { 'b', REQ_MOVE_PAGE_UP },
747 { '-', REQ_MOVE_PAGE_UP },
749 /* Scrolling */
750 { KEY_IC, REQ_SCROLL_LINE_UP },
751 { KEY_DC, REQ_SCROLL_LINE_DOWN },
752 { 'w', REQ_SCROLL_PAGE_UP },
753 { 's', REQ_SCROLL_PAGE_DOWN },
755 /* Searching */
756 { '/', REQ_SEARCH },
757 { '?', REQ_SEARCH_BACK },
758 { 'n', REQ_FIND_NEXT },
759 { 'N', REQ_FIND_PREV },
761 /* Misc */
762 { 'Q', REQ_QUIT },
763 { 'z', REQ_STOP_LOADING },
764 { 'v', REQ_SHOW_VERSION },
765 { 'r', REQ_SCREEN_REDRAW },
766 { '.', REQ_TOGGLE_LINENO },
767 { 'D', REQ_TOGGLE_DATE },
768 { 'A', REQ_TOGGLE_AUTHOR },
769 { 'g', REQ_TOGGLE_REV_GRAPH },
770 { 'F', REQ_TOGGLE_REFS },
771 { ':', REQ_PROMPT },
772 { 'u', REQ_STATUS_UPDATE },
773 { 'M', REQ_STATUS_MERGE },
774 { ',', REQ_TREE_PARENT },
775 { 'e', REQ_EDIT },
777 /* Using the ncurses SIGWINCH handler. */
778 { KEY_RESIZE, REQ_SCREEN_RESIZE },
779 };
781 #define KEYMAP_INFO \
782 KEYMAP_(GENERIC), \
783 KEYMAP_(MAIN), \
784 KEYMAP_(DIFF), \
785 KEYMAP_(LOG), \
786 KEYMAP_(TREE), \
787 KEYMAP_(BLOB), \
788 KEYMAP_(BLAME), \
789 KEYMAP_(PAGER), \
790 KEYMAP_(HELP), \
791 KEYMAP_(STATUS), \
792 KEYMAP_(STAGE)
794 enum keymap {
795 #define KEYMAP_(name) KEYMAP_##name
796 KEYMAP_INFO
797 #undef KEYMAP_
798 };
800 static struct int_map keymap_table[] = {
801 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
802 KEYMAP_INFO
803 #undef KEYMAP_
804 };
806 #define set_keymap(map, name) \
807 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
809 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
811 static void
812 add_keybinding(enum keymap keymap, enum request request, int key)
813 {
814 struct keybinding *keybinding;
816 keybinding = calloc(1, sizeof(*keybinding));
817 if (!keybinding)
818 die("Failed to allocate keybinding");
820 keybinding->alias = key;
821 keybinding->request = request;
822 keybinding->next = keybindings[keymap];
823 keybindings[keymap] = keybinding;
824 }
826 /* Looks for a key binding first in the given map, then in the generic map, and
827 * lastly in the default keybindings. */
828 static enum request
829 get_keybinding(enum keymap keymap, int key)
830 {
831 struct keybinding *kbd;
832 int i;
834 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
835 if (kbd->alias == key)
836 return kbd->request;
838 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
839 if (kbd->alias == key)
840 return kbd->request;
842 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
843 if (default_keybindings[i].alias == key)
844 return default_keybindings[i].request;
846 return (enum request) key;
847 }
850 struct key {
851 char *name;
852 int value;
853 };
855 static struct key key_table[] = {
856 { "Enter", KEY_RETURN },
857 { "Space", ' ' },
858 { "Backspace", KEY_BACKSPACE },
859 { "Tab", KEY_TAB },
860 { "Escape", KEY_ESC },
861 { "Left", KEY_LEFT },
862 { "Right", KEY_RIGHT },
863 { "Up", KEY_UP },
864 { "Down", KEY_DOWN },
865 { "Insert", KEY_IC },
866 { "Delete", KEY_DC },
867 { "Hash", '#' },
868 { "Home", KEY_HOME },
869 { "End", KEY_END },
870 { "PageUp", KEY_PPAGE },
871 { "PageDown", KEY_NPAGE },
872 { "F1", KEY_F(1) },
873 { "F2", KEY_F(2) },
874 { "F3", KEY_F(3) },
875 { "F4", KEY_F(4) },
876 { "F5", KEY_F(5) },
877 { "F6", KEY_F(6) },
878 { "F7", KEY_F(7) },
879 { "F8", KEY_F(8) },
880 { "F9", KEY_F(9) },
881 { "F10", KEY_F(10) },
882 { "F11", KEY_F(11) },
883 { "F12", KEY_F(12) },
884 };
886 static int
887 get_key_value(const char *name)
888 {
889 int i;
891 for (i = 0; i < ARRAY_SIZE(key_table); i++)
892 if (!strcasecmp(key_table[i].name, name))
893 return key_table[i].value;
895 if (strlen(name) == 1 && isprint(*name))
896 return (int) *name;
898 return ERR;
899 }
901 static char *
902 get_key_name(int key_value)
903 {
904 static char key_char[] = "'X'";
905 char *seq = NULL;
906 int key;
908 for (key = 0; key < ARRAY_SIZE(key_table); key++)
909 if (key_table[key].value == key_value)
910 seq = key_table[key].name;
912 if (seq == NULL &&
913 key_value < 127 &&
914 isprint(key_value)) {
915 key_char[1] = (char) key_value;
916 seq = key_char;
917 }
919 return seq ? seq : "'?'";
920 }
922 static char *
923 get_key(enum request request)
924 {
925 static char buf[BUFSIZ];
926 size_t pos = 0;
927 char *sep = "";
928 int i;
930 buf[pos] = 0;
932 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
933 struct keybinding *keybinding = &default_keybindings[i];
935 if (keybinding->request != request)
936 continue;
938 if (!string_format_from(buf, &pos, "%s%s", sep,
939 get_key_name(keybinding->alias)))
940 return "Too many keybindings!";
941 sep = ", ";
942 }
944 return buf;
945 }
947 struct run_request {
948 enum keymap keymap;
949 int key;
950 char cmd[SIZEOF_STR];
951 };
953 static struct run_request *run_request;
954 static size_t run_requests;
956 static enum request
957 add_run_request(enum keymap keymap, int key, int argc, char **argv)
958 {
959 struct run_request *req;
960 char cmd[SIZEOF_STR];
961 size_t bufpos;
963 for (bufpos = 0; argc > 0; argc--, argv++)
964 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
965 return REQ_NONE;
967 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
968 if (!req)
969 return REQ_NONE;
971 run_request = req;
972 req = &run_request[run_requests++];
973 string_copy(req->cmd, cmd);
974 req->keymap = keymap;
975 req->key = key;
977 return REQ_NONE + run_requests;
978 }
980 static struct run_request *
981 get_run_request(enum request request)
982 {
983 if (request <= REQ_NONE)
984 return NULL;
985 return &run_request[request - REQ_NONE - 1];
986 }
988 static void
989 add_builtin_run_requests(void)
990 {
991 struct {
992 enum keymap keymap;
993 int key;
994 char *argv[1];
995 } reqs[] = {
996 { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
997 { KEYMAP_GENERIC, 'G', { "git gc" } },
998 };
999 int i;
1001 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1002 enum request req;
1004 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1005 if (req != REQ_NONE)
1006 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1007 }
1008 }
1010 /*
1011 * User config file handling.
1012 */
1014 static struct int_map color_map[] = {
1015 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1016 COLOR_MAP(DEFAULT),
1017 COLOR_MAP(BLACK),
1018 COLOR_MAP(BLUE),
1019 COLOR_MAP(CYAN),
1020 COLOR_MAP(GREEN),
1021 COLOR_MAP(MAGENTA),
1022 COLOR_MAP(RED),
1023 COLOR_MAP(WHITE),
1024 COLOR_MAP(YELLOW),
1025 };
1027 #define set_color(color, name) \
1028 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1030 static struct int_map attr_map[] = {
1031 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1032 ATTR_MAP(NORMAL),
1033 ATTR_MAP(BLINK),
1034 ATTR_MAP(BOLD),
1035 ATTR_MAP(DIM),
1036 ATTR_MAP(REVERSE),
1037 ATTR_MAP(STANDOUT),
1038 ATTR_MAP(UNDERLINE),
1039 };
1041 #define set_attribute(attr, name) \
1042 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1044 static int config_lineno;
1045 static bool config_errors;
1046 static char *config_msg;
1048 /* Wants: object fgcolor bgcolor [attr] */
1049 static int
1050 option_color_command(int argc, char *argv[])
1051 {
1052 struct line_info *info;
1054 if (argc != 3 && argc != 4) {
1055 config_msg = "Wrong number of arguments given to color command";
1056 return ERR;
1057 }
1059 info = get_line_info(argv[0]);
1060 if (!info) {
1061 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1062 info = get_line_info("delimiter");
1064 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1065 info = get_line_info("date");
1067 } else {
1068 config_msg = "Unknown color name";
1069 return ERR;
1070 }
1071 }
1073 if (set_color(&info->fg, argv[1]) == ERR ||
1074 set_color(&info->bg, argv[2]) == ERR) {
1075 config_msg = "Unknown color";
1076 return ERR;
1077 }
1079 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1080 config_msg = "Unknown attribute";
1081 return ERR;
1082 }
1084 return OK;
1085 }
1087 static bool parse_bool(const char *s)
1088 {
1089 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1090 !strcmp(s, "yes")) ? TRUE : FALSE;
1091 }
1093 /* Wants: name = value */
1094 static int
1095 option_set_command(int argc, char *argv[])
1096 {
1097 if (argc != 3) {
1098 config_msg = "Wrong number of arguments given to set command";
1099 return ERR;
1100 }
1102 if (strcmp(argv[1], "=")) {
1103 config_msg = "No value assigned";
1104 return ERR;
1105 }
1107 if (!strcmp(argv[0], "show-author")) {
1108 opt_author = parse_bool(argv[2]);
1109 return OK;
1110 }
1112 if (!strcmp(argv[0], "show-date")) {
1113 opt_date = parse_bool(argv[2]);
1114 return OK;
1115 }
1117 if (!strcmp(argv[0], "show-rev-graph")) {
1118 opt_rev_graph = parse_bool(argv[2]);
1119 return OK;
1120 }
1122 if (!strcmp(argv[0], "show-refs")) {
1123 opt_show_refs = parse_bool(argv[2]);
1124 return OK;
1125 }
1127 if (!strcmp(argv[0], "show-line-numbers")) {
1128 opt_line_number = parse_bool(argv[2]);
1129 return OK;
1130 }
1132 if (!strcmp(argv[0], "line-graphics")) {
1133 opt_line_graphics = parse_bool(argv[2]);
1134 return OK;
1135 }
1137 if (!strcmp(argv[0], "line-number-interval")) {
1138 opt_num_interval = atoi(argv[2]);
1139 return OK;
1140 }
1142 if (!strcmp(argv[0], "tab-size")) {
1143 opt_tab_size = atoi(argv[2]);
1144 return OK;
1145 }
1147 if (!strcmp(argv[0], "commit-encoding")) {
1148 char *arg = argv[2];
1149 int delimiter = *arg;
1150 int i;
1152 switch (delimiter) {
1153 case '"':
1154 case '\'':
1155 for (arg++, i = 0; arg[i]; i++)
1156 if (arg[i] == delimiter) {
1157 arg[i] = 0;
1158 break;
1159 }
1160 default:
1161 string_ncopy(opt_encoding, arg, strlen(arg));
1162 return OK;
1163 }
1164 }
1166 config_msg = "Unknown variable name";
1167 return ERR;
1168 }
1170 /* Wants: mode request key */
1171 static int
1172 option_bind_command(int argc, char *argv[])
1173 {
1174 enum request request;
1175 int keymap;
1176 int key;
1178 if (argc < 3) {
1179 config_msg = "Wrong number of arguments given to bind command";
1180 return ERR;
1181 }
1183 if (set_keymap(&keymap, argv[0]) == ERR) {
1184 config_msg = "Unknown key map";
1185 return ERR;
1186 }
1188 key = get_key_value(argv[1]);
1189 if (key == ERR) {
1190 config_msg = "Unknown key";
1191 return ERR;
1192 }
1194 request = get_request(argv[2]);
1195 if (request == REQ_NONE) {
1196 const char *obsolete[] = { "cherry-pick" };
1197 size_t namelen = strlen(argv[2]);
1198 int i;
1200 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1201 if (namelen == strlen(obsolete[i]) &&
1202 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1203 config_msg = "Obsolete request name";
1204 return ERR;
1205 }
1206 }
1207 }
1208 if (request == REQ_NONE && *argv[2]++ == '!')
1209 request = add_run_request(keymap, key, argc - 2, argv + 2);
1210 if (request == REQ_NONE) {
1211 config_msg = "Unknown request name";
1212 return ERR;
1213 }
1215 add_keybinding(keymap, request, key);
1217 return OK;
1218 }
1220 static int
1221 set_option(char *opt, char *value)
1222 {
1223 char *argv[16];
1224 int valuelen;
1225 int argc = 0;
1227 /* Tokenize */
1228 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1229 argv[argc++] = value;
1230 value += valuelen;
1232 /* Nothing more to tokenize or last available token. */
1233 if (!*value || argc >= ARRAY_SIZE(argv))
1234 break;
1236 *value++ = 0;
1237 while (isspace(*value))
1238 value++;
1239 }
1241 if (!strcmp(opt, "color"))
1242 return option_color_command(argc, argv);
1244 if (!strcmp(opt, "set"))
1245 return option_set_command(argc, argv);
1247 if (!strcmp(opt, "bind"))
1248 return option_bind_command(argc, argv);
1250 config_msg = "Unknown option command";
1251 return ERR;
1252 }
1254 static int
1255 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1256 {
1257 int status = OK;
1259 config_lineno++;
1260 config_msg = "Internal error";
1262 /* Check for comment markers, since read_properties() will
1263 * only ensure opt and value are split at first " \t". */
1264 optlen = strcspn(opt, "#");
1265 if (optlen == 0)
1266 return OK;
1268 if (opt[optlen] != 0) {
1269 config_msg = "No option value";
1270 status = ERR;
1272 } else {
1273 /* Look for comment endings in the value. */
1274 size_t len = strcspn(value, "#");
1276 if (len < valuelen) {
1277 valuelen = len;
1278 value[valuelen] = 0;
1279 }
1281 status = set_option(opt, value);
1282 }
1284 if (status == ERR) {
1285 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1286 config_lineno, (int) optlen, opt, config_msg);
1287 config_errors = TRUE;
1288 }
1290 /* Always keep going if errors are encountered. */
1291 return OK;
1292 }
1294 static void
1295 load_option_file(const char *path)
1296 {
1297 FILE *file;
1299 /* It's ok that the file doesn't exist. */
1300 file = fopen(path, "r");
1301 if (!file)
1302 return;
1304 config_lineno = 0;
1305 config_errors = FALSE;
1307 if (read_properties(file, " \t", read_option) == ERR ||
1308 config_errors == TRUE)
1309 fprintf(stderr, "Errors while loading %s.\n", path);
1310 }
1312 static int
1313 load_options(void)
1314 {
1315 char *home = getenv("HOME");
1316 char *tigrc_user = getenv("TIGRC_USER");
1317 char *tigrc_system = getenv("TIGRC_SYSTEM");
1318 char buf[SIZEOF_STR];
1320 add_builtin_run_requests();
1322 if (!tigrc_system) {
1323 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1324 return ERR;
1325 tigrc_system = buf;
1326 }
1327 load_option_file(tigrc_system);
1329 if (!tigrc_user) {
1330 if (!home || !string_format(buf, "%s/.tigrc", home))
1331 return ERR;
1332 tigrc_user = buf;
1333 }
1334 load_option_file(tigrc_user);
1336 return OK;
1337 }
1340 /*
1341 * The viewer
1342 */
1344 struct view;
1345 struct view_ops;
1347 /* The display array of active views and the index of the current view. */
1348 static struct view *display[2];
1349 static unsigned int current_view;
1351 /* Reading from the prompt? */
1352 static bool input_mode = FALSE;
1354 #define foreach_displayed_view(view, i) \
1355 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1357 #define displayed_views() (display[1] != NULL ? 2 : 1)
1359 /* Current head and commit ID */
1360 static char ref_blob[SIZEOF_REF] = "";
1361 static char ref_commit[SIZEOF_REF] = "HEAD";
1362 static char ref_head[SIZEOF_REF] = "HEAD";
1364 struct view {
1365 const char *name; /* View name */
1366 const char *cmd_fmt; /* Default command line format */
1367 const char *cmd_env; /* Command line set via environment */
1368 const char *id; /* Points to either of ref_{head,commit,blob} */
1370 struct view_ops *ops; /* View operations */
1372 enum keymap keymap; /* What keymap does this view have */
1373 bool git_dir; /* Whether the view requires a git directory. */
1375 char cmd[SIZEOF_STR]; /* Command buffer */
1376 char ref[SIZEOF_REF]; /* Hovered commit reference */
1377 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1379 int height, width; /* The width and height of the main window */
1380 WINDOW *win; /* The main window */
1381 WINDOW *title; /* The title window living below the main window */
1383 /* Navigation */
1384 unsigned long offset; /* Offset of the window top */
1385 unsigned long lineno; /* Current line number */
1387 /* Searching */
1388 char grep[SIZEOF_STR]; /* Search string */
1389 regex_t *regex; /* Pre-compiled regex */
1391 /* If non-NULL, points to the view that opened this view. If this view
1392 * is closed tig will switch back to the parent view. */
1393 struct view *parent;
1395 /* Buffering */
1396 size_t lines; /* Total number of lines */
1397 struct line *line; /* Line index */
1398 size_t line_alloc; /* Total number of allocated lines */
1399 size_t line_size; /* Total number of used lines */
1400 unsigned int digits; /* Number of digits in the lines member. */
1402 /* Drawing */
1403 struct line *curline; /* Line currently being drawn. */
1404 enum line_type curtype; /* Attribute currently used for drawing. */
1405 unsigned long col; /* Column when drawing. */
1407 /* Loading */
1408 FILE *pipe;
1409 time_t start_time;
1410 };
1412 struct view_ops {
1413 /* What type of content being displayed. Used in the title bar. */
1414 const char *type;
1415 /* Open and reads in all view content. */
1416 bool (*open)(struct view *view);
1417 /* Read one line; updates view->line. */
1418 bool (*read)(struct view *view, char *data);
1419 /* Draw one line; @lineno must be < view->height. */
1420 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1421 /* Depending on view handle a special requests. */
1422 enum request (*request)(struct view *view, enum request request, struct line *line);
1423 /* Search for regex in a line. */
1424 bool (*grep)(struct view *view, struct line *line);
1425 /* Select line */
1426 void (*select)(struct view *view, struct line *line);
1427 };
1429 static struct view_ops pager_ops;
1430 static struct view_ops main_ops;
1431 static struct view_ops tree_ops;
1432 static struct view_ops blob_ops;
1433 static struct view_ops blame_ops;
1434 static struct view_ops help_ops;
1435 static struct view_ops status_ops;
1436 static struct view_ops stage_ops;
1438 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1439 { name, cmd, #env, ref, ops, map, git }
1441 #define VIEW_(id, name, ops, git, ref) \
1442 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1445 static struct view views[] = {
1446 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1447 VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit),
1448 VIEW_(LOG, "log", &pager_ops, TRUE, ref_head),
1449 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1450 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1451 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1452 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1453 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1454 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1455 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1456 };
1458 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1459 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1461 #define foreach_view(view, i) \
1462 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1464 #define view_is_displayed(view) \
1465 (view == display[0] || view == display[1])
1468 enum line_graphic {
1469 LINE_GRAPHIC_VLINE
1470 };
1472 static int line_graphics[] = {
1473 /* LINE_GRAPHIC_VLINE: */ '|'
1474 };
1476 static inline void
1477 set_view_attr(struct view *view, enum line_type type)
1478 {
1479 if (!view->curline->selected && view->curtype != type) {
1480 wattrset(view->win, get_line_attr(type));
1481 wchgat(view->win, -1, 0, type, NULL);
1482 view->curtype = type;
1483 }
1484 }
1486 static int
1487 draw_chars(struct view *view, enum line_type type, const char *string,
1488 int max_len, bool use_tilde)
1489 {
1490 int len = 0;
1491 int col = 0;
1492 int trimmed = FALSE;
1494 if (max_len <= 0)
1495 return 0;
1497 if (opt_utf8) {
1498 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1499 } else {
1500 col = len = strlen(string);
1501 if (len > max_len) {
1502 if (use_tilde) {
1503 max_len -= 1;
1504 }
1505 col = len = max_len;
1506 trimmed = TRUE;
1507 }
1508 }
1510 set_view_attr(view, type);
1511 waddnstr(view->win, string, len);
1512 if (trimmed && use_tilde) {
1513 set_view_attr(view, LINE_DELIMITER);
1514 waddch(view->win, '~');
1515 col++;
1516 }
1518 return col;
1519 }
1521 static int
1522 draw_space(struct view *view, enum line_type type, int max, int spaces)
1523 {
1524 static char space[] = " ";
1525 int col = 0;
1527 spaces = MIN(max, spaces);
1529 while (spaces > 0) {
1530 int len = MIN(spaces, sizeof(space) - 1);
1532 col += draw_chars(view, type, space, spaces, FALSE);
1533 spaces -= len;
1534 }
1536 return col;
1537 }
1539 static bool
1540 draw_lineno(struct view *view, unsigned int lineno)
1541 {
1542 char number[10];
1543 int digits3 = view->digits < 3 ? 3 : view->digits;
1544 int max_number = MIN(digits3, STRING_SIZE(number));
1545 int max = view->width - view->col;
1546 int col;
1548 if (max < max_number)
1549 max_number = max;
1551 lineno += view->offset + 1;
1552 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1553 static char fmt[] = "%1ld";
1555 if (view->digits <= 9)
1556 fmt[1] = '0' + digits3;
1558 if (!string_format(number, fmt, lineno))
1559 number[0] = 0;
1560 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1561 } else {
1562 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1563 }
1565 if (col < max) {
1566 set_view_attr(view, LINE_DEFAULT);
1567 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1568 col++;
1569 }
1571 if (col < max)
1572 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1573 view->col += col;
1575 return view->width - view->col <= 0;
1576 }
1578 static bool
1579 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1580 {
1581 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1582 return view->width - view->col <= 0;
1583 }
1585 static bool
1586 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1587 {
1588 int max = view->width - view->col;
1589 int i;
1591 if (max < size)
1592 size = max;
1594 set_view_attr(view, type);
1595 /* Using waddch() instead of waddnstr() ensures that
1596 * they'll be rendered correctly for the cursor line. */
1597 for (i = 0; i < size; i++)
1598 waddch(view->win, graphic[i]);
1600 view->col += size;
1601 if (size < max) {
1602 waddch(view->win, ' ');
1603 view->col++;
1604 }
1606 return view->width - view->col <= 0;
1607 }
1609 static bool
1610 draw_field(struct view *view, enum line_type type, char *text, int len, bool trim)
1611 {
1612 int max = MIN(view->width - view->col, len);
1613 int col;
1615 if (text)
1616 col = draw_chars(view, type, text, max - 1, trim);
1617 else
1618 col = draw_space(view, type, max - 1, max - 1);
1620 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1621 return view->width - view->col <= 0;
1622 }
1624 static bool
1625 draw_date(struct view *view, struct tm *time)
1626 {
1627 char buf[DATE_COLS];
1628 char *date;
1629 int timelen = 0;
1631 if (time)
1632 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1633 date = timelen ? buf : NULL;
1635 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1636 }
1638 static bool
1639 draw_view_line(struct view *view, unsigned int lineno)
1640 {
1641 struct line *line;
1642 bool selected = (view->offset + lineno == view->lineno);
1643 bool draw_ok;
1645 assert(view_is_displayed(view));
1647 if (view->offset + lineno >= view->lines)
1648 return FALSE;
1650 line = &view->line[view->offset + lineno];
1652 wmove(view->win, lineno, 0);
1653 view->col = 0;
1654 view->curline = line;
1655 view->curtype = LINE_NONE;
1656 line->selected = FALSE;
1658 if (selected) {
1659 set_view_attr(view, LINE_CURSOR);
1660 line->selected = TRUE;
1661 view->ops->select(view, line);
1662 } else if (line->selected) {
1663 wclrtoeol(view->win);
1664 }
1666 scrollok(view->win, FALSE);
1667 draw_ok = view->ops->draw(view, line, lineno);
1668 scrollok(view->win, TRUE);
1670 return draw_ok;
1671 }
1673 static void
1674 redraw_view_dirty(struct view *view)
1675 {
1676 bool dirty = FALSE;
1677 int lineno;
1679 for (lineno = 0; lineno < view->height; lineno++) {
1680 struct line *line = &view->line[view->offset + lineno];
1682 if (!line->dirty)
1683 continue;
1684 line->dirty = 0;
1685 dirty = TRUE;
1686 if (!draw_view_line(view, lineno))
1687 break;
1688 }
1690 if (!dirty)
1691 return;
1692 redrawwin(view->win);
1693 if (input_mode)
1694 wnoutrefresh(view->win);
1695 else
1696 wrefresh(view->win);
1697 }
1699 static void
1700 redraw_view_from(struct view *view, int lineno)
1701 {
1702 assert(0 <= lineno && lineno < view->height);
1704 for (; lineno < view->height; lineno++) {
1705 if (!draw_view_line(view, lineno))
1706 break;
1707 }
1709 redrawwin(view->win);
1710 if (input_mode)
1711 wnoutrefresh(view->win);
1712 else
1713 wrefresh(view->win);
1714 }
1716 static void
1717 redraw_view(struct view *view)
1718 {
1719 wclear(view->win);
1720 redraw_view_from(view, 0);
1721 }
1724 static void
1725 update_view_title(struct view *view)
1726 {
1727 char buf[SIZEOF_STR];
1728 char state[SIZEOF_STR];
1729 size_t bufpos = 0, statelen = 0;
1731 assert(view_is_displayed(view));
1733 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1734 unsigned int view_lines = view->offset + view->height;
1735 unsigned int lines = view->lines
1736 ? MIN(view_lines, view->lines) * 100 / view->lines
1737 : 0;
1739 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1740 view->ops->type,
1741 view->lineno + 1,
1742 view->lines,
1743 lines);
1745 if (view->pipe) {
1746 time_t secs = time(NULL) - view->start_time;
1748 /* Three git seconds are a long time ... */
1749 if (secs > 2)
1750 string_format_from(state, &statelen, " %lds", secs);
1751 }
1752 }
1754 string_format_from(buf, &bufpos, "[%s]", view->name);
1755 if (*view->ref && bufpos < view->width) {
1756 size_t refsize = strlen(view->ref);
1757 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1759 if (minsize < view->width)
1760 refsize = view->width - minsize + 7;
1761 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1762 }
1764 if (statelen && bufpos < view->width) {
1765 string_format_from(buf, &bufpos, " %s", state);
1766 }
1768 if (view == display[current_view])
1769 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1770 else
1771 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1773 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1774 wclrtoeol(view->title);
1775 wmove(view->title, 0, view->width - 1);
1777 if (input_mode)
1778 wnoutrefresh(view->title);
1779 else
1780 wrefresh(view->title);
1781 }
1783 static void
1784 resize_display(void)
1785 {
1786 int offset, i;
1787 struct view *base = display[0];
1788 struct view *view = display[1] ? display[1] : display[0];
1790 /* Setup window dimensions */
1792 getmaxyx(stdscr, base->height, base->width);
1794 /* Make room for the status window. */
1795 base->height -= 1;
1797 if (view != base) {
1798 /* Horizontal split. */
1799 view->width = base->width;
1800 view->height = SCALE_SPLIT_VIEW(base->height);
1801 base->height -= view->height;
1803 /* Make room for the title bar. */
1804 view->height -= 1;
1805 }
1807 /* Make room for the title bar. */
1808 base->height -= 1;
1810 offset = 0;
1812 foreach_displayed_view (view, i) {
1813 if (!view->win) {
1814 view->win = newwin(view->height, 0, offset, 0);
1815 if (!view->win)
1816 die("Failed to create %s view", view->name);
1818 scrollok(view->win, TRUE);
1820 view->title = newwin(1, 0, offset + view->height, 0);
1821 if (!view->title)
1822 die("Failed to create title window");
1824 } else {
1825 wresize(view->win, view->height, view->width);
1826 mvwin(view->win, offset, 0);
1827 mvwin(view->title, offset + view->height, 0);
1828 }
1830 offset += view->height + 1;
1831 }
1832 }
1834 static void
1835 redraw_display(void)
1836 {
1837 struct view *view;
1838 int i;
1840 foreach_displayed_view (view, i) {
1841 redraw_view(view);
1842 update_view_title(view);
1843 }
1844 }
1846 static void
1847 update_display_cursor(struct view *view)
1848 {
1849 /* Move the cursor to the right-most column of the cursor line.
1850 *
1851 * XXX: This could turn out to be a bit expensive, but it ensures that
1852 * the cursor does not jump around. */
1853 if (view->lines) {
1854 wmove(view->win, view->lineno - view->offset, view->width - 1);
1855 wrefresh(view->win);
1856 }
1857 }
1859 /*
1860 * Navigation
1861 */
1863 /* Scrolling backend */
1864 static void
1865 do_scroll_view(struct view *view, int lines)
1866 {
1867 bool redraw_current_line = FALSE;
1869 /* The rendering expects the new offset. */
1870 view->offset += lines;
1872 assert(0 <= view->offset && view->offset < view->lines);
1873 assert(lines);
1875 /* Move current line into the view. */
1876 if (view->lineno < view->offset) {
1877 view->lineno = view->offset;
1878 redraw_current_line = TRUE;
1879 } else if (view->lineno >= view->offset + view->height) {
1880 view->lineno = view->offset + view->height - 1;
1881 redraw_current_line = TRUE;
1882 }
1884 assert(view->offset <= view->lineno && view->lineno < view->lines);
1886 /* Redraw the whole screen if scrolling is pointless. */
1887 if (view->height < ABS(lines)) {
1888 redraw_view(view);
1890 } else {
1891 int line = lines > 0 ? view->height - lines : 0;
1892 int end = line + ABS(lines);
1894 wscrl(view->win, lines);
1896 for (; line < end; line++) {
1897 if (!draw_view_line(view, line))
1898 break;
1899 }
1901 if (redraw_current_line)
1902 draw_view_line(view, view->lineno - view->offset);
1903 }
1905 redrawwin(view->win);
1906 wrefresh(view->win);
1907 report("");
1908 }
1910 /* Scroll frontend */
1911 static void
1912 scroll_view(struct view *view, enum request request)
1913 {
1914 int lines = 1;
1916 assert(view_is_displayed(view));
1918 switch (request) {
1919 case REQ_SCROLL_PAGE_DOWN:
1920 lines = view->height;
1921 case REQ_SCROLL_LINE_DOWN:
1922 if (view->offset + lines > view->lines)
1923 lines = view->lines - view->offset;
1925 if (lines == 0 || view->offset + view->height >= view->lines) {
1926 report("Cannot scroll beyond the last line");
1927 return;
1928 }
1929 break;
1931 case REQ_SCROLL_PAGE_UP:
1932 lines = view->height;
1933 case REQ_SCROLL_LINE_UP:
1934 if (lines > view->offset)
1935 lines = view->offset;
1937 if (lines == 0) {
1938 report("Cannot scroll beyond the first line");
1939 return;
1940 }
1942 lines = -lines;
1943 break;
1945 default:
1946 die("request %d not handled in switch", request);
1947 }
1949 do_scroll_view(view, lines);
1950 }
1952 /* Cursor moving */
1953 static void
1954 move_view(struct view *view, enum request request)
1955 {
1956 int scroll_steps = 0;
1957 int steps;
1959 switch (request) {
1960 case REQ_MOVE_FIRST_LINE:
1961 steps = -view->lineno;
1962 break;
1964 case REQ_MOVE_LAST_LINE:
1965 steps = view->lines - view->lineno - 1;
1966 break;
1968 case REQ_MOVE_PAGE_UP:
1969 steps = view->height > view->lineno
1970 ? -view->lineno : -view->height;
1971 break;
1973 case REQ_MOVE_PAGE_DOWN:
1974 steps = view->lineno + view->height >= view->lines
1975 ? view->lines - view->lineno - 1 : view->height;
1976 break;
1978 case REQ_MOVE_UP:
1979 steps = -1;
1980 break;
1982 case REQ_MOVE_DOWN:
1983 steps = 1;
1984 break;
1986 default:
1987 die("request %d not handled in switch", request);
1988 }
1990 if (steps <= 0 && view->lineno == 0) {
1991 report("Cannot move beyond the first line");
1992 return;
1994 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1995 report("Cannot move beyond the last line");
1996 return;
1997 }
1999 /* Move the current line */
2000 view->lineno += steps;
2001 assert(0 <= view->lineno && view->lineno < view->lines);
2003 /* Check whether the view needs to be scrolled */
2004 if (view->lineno < view->offset ||
2005 view->lineno >= view->offset + view->height) {
2006 scroll_steps = steps;
2007 if (steps < 0 && -steps > view->offset) {
2008 scroll_steps = -view->offset;
2010 } else if (steps > 0) {
2011 if (view->lineno == view->lines - 1 &&
2012 view->lines > view->height) {
2013 scroll_steps = view->lines - view->offset - 1;
2014 if (scroll_steps >= view->height)
2015 scroll_steps -= view->height - 1;
2016 }
2017 }
2018 }
2020 if (!view_is_displayed(view)) {
2021 view->offset += scroll_steps;
2022 assert(0 <= view->offset && view->offset < view->lines);
2023 view->ops->select(view, &view->line[view->lineno]);
2024 return;
2025 }
2027 /* Repaint the old "current" line if we be scrolling */
2028 if (ABS(steps) < view->height)
2029 draw_view_line(view, view->lineno - steps - view->offset);
2031 if (scroll_steps) {
2032 do_scroll_view(view, scroll_steps);
2033 return;
2034 }
2036 /* Draw the current line */
2037 draw_view_line(view, view->lineno - view->offset);
2039 redrawwin(view->win);
2040 wrefresh(view->win);
2041 report("");
2042 }
2045 /*
2046 * Searching
2047 */
2049 static void search_view(struct view *view, enum request request);
2051 static bool
2052 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2053 {
2054 assert(view_is_displayed(view));
2056 if (!view->ops->grep(view, line))
2057 return FALSE;
2059 if (lineno - view->offset >= view->height) {
2060 view->offset = lineno;
2061 view->lineno = lineno;
2062 redraw_view(view);
2064 } else {
2065 unsigned long old_lineno = view->lineno - view->offset;
2067 view->lineno = lineno;
2068 draw_view_line(view, old_lineno);
2070 draw_view_line(view, view->lineno - view->offset);
2071 redrawwin(view->win);
2072 wrefresh(view->win);
2073 }
2075 report("Line %ld matches '%s'", lineno + 1, view->grep);
2076 return TRUE;
2077 }
2079 static void
2080 find_next(struct view *view, enum request request)
2081 {
2082 unsigned long lineno = view->lineno;
2083 int direction;
2085 if (!*view->grep) {
2086 if (!*opt_search)
2087 report("No previous search");
2088 else
2089 search_view(view, request);
2090 return;
2091 }
2093 switch (request) {
2094 case REQ_SEARCH:
2095 case REQ_FIND_NEXT:
2096 direction = 1;
2097 break;
2099 case REQ_SEARCH_BACK:
2100 case REQ_FIND_PREV:
2101 direction = -1;
2102 break;
2104 default:
2105 return;
2106 }
2108 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2109 lineno += direction;
2111 /* Note, lineno is unsigned long so will wrap around in which case it
2112 * will become bigger than view->lines. */
2113 for (; lineno < view->lines; lineno += direction) {
2114 struct line *line = &view->line[lineno];
2116 if (find_next_line(view, lineno, line))
2117 return;
2118 }
2120 report("No match found for '%s'", view->grep);
2121 }
2123 static void
2124 search_view(struct view *view, enum request request)
2125 {
2126 int regex_err;
2128 if (view->regex) {
2129 regfree(view->regex);
2130 *view->grep = 0;
2131 } else {
2132 view->regex = calloc(1, sizeof(*view->regex));
2133 if (!view->regex)
2134 return;
2135 }
2137 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2138 if (regex_err != 0) {
2139 char buf[SIZEOF_STR] = "unknown error";
2141 regerror(regex_err, view->regex, buf, sizeof(buf));
2142 report("Search failed: %s", buf);
2143 return;
2144 }
2146 string_copy(view->grep, opt_search);
2148 find_next(view, request);
2149 }
2151 /*
2152 * Incremental updating
2153 */
2155 static void
2156 end_update(struct view *view)
2157 {
2158 if (!view->pipe)
2159 return;
2160 set_nonblocking_input(FALSE);
2161 if (view->pipe == stdin)
2162 fclose(view->pipe);
2163 else
2164 pclose(view->pipe);
2165 view->pipe = NULL;
2166 }
2168 static bool
2169 begin_update(struct view *view)
2170 {
2171 if (view->pipe)
2172 end_update(view);
2174 if (opt_cmd[0]) {
2175 string_copy(view->cmd, opt_cmd);
2176 opt_cmd[0] = 0;
2177 /* When running random commands, initially show the
2178 * command in the title. However, it maybe later be
2179 * overwritten if a commit line is selected. */
2180 if (view == VIEW(REQ_VIEW_PAGER))
2181 string_copy(view->ref, view->cmd);
2182 else
2183 view->ref[0] = 0;
2185 } else if (view == VIEW(REQ_VIEW_TREE)) {
2186 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2187 char path[SIZEOF_STR];
2189 if (strcmp(view->vid, view->id))
2190 opt_path[0] = path[0] = 0;
2191 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2192 return FALSE;
2194 if (!string_format(view->cmd, format, view->id, path))
2195 return FALSE;
2197 } else {
2198 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2199 const char *id = view->id;
2201 if (!string_format(view->cmd, format, id, id, id, id, id))
2202 return FALSE;
2204 /* Put the current ref_* value to the view title ref
2205 * member. This is needed by the blob view. Most other
2206 * views sets it automatically after loading because the
2207 * first line is a commit line. */
2208 string_copy_rev(view->ref, view->id);
2209 }
2211 /* Special case for the pager view. */
2212 if (opt_pipe) {
2213 view->pipe = opt_pipe;
2214 opt_pipe = NULL;
2215 } else {
2216 view->pipe = popen(view->cmd, "r");
2217 }
2219 if (!view->pipe)
2220 return FALSE;
2222 set_nonblocking_input(TRUE);
2224 view->offset = 0;
2225 view->lines = 0;
2226 view->lineno = 0;
2227 string_copy_rev(view->vid, view->id);
2229 if (view->line) {
2230 int i;
2232 for (i = 0; i < view->lines; i++)
2233 if (view->line[i].data)
2234 free(view->line[i].data);
2236 free(view->line);
2237 view->line = NULL;
2238 }
2240 view->start_time = time(NULL);
2242 return TRUE;
2243 }
2245 #define ITEM_CHUNK_SIZE 256
2246 static void *
2247 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2248 {
2249 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2250 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2252 if (mem == NULL || num_chunks != num_chunks_new) {
2253 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2254 mem = realloc(mem, *size * item_size);
2255 }
2257 return mem;
2258 }
2260 static struct line *
2261 realloc_lines(struct view *view, size_t line_size)
2262 {
2263 size_t alloc = view->line_alloc;
2264 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2265 sizeof(*view->line));
2267 if (!tmp)
2268 return NULL;
2270 view->line = tmp;
2271 view->line_alloc = alloc;
2272 view->line_size = line_size;
2273 return view->line;
2274 }
2276 static bool
2277 update_view(struct view *view)
2278 {
2279 char in_buffer[BUFSIZ];
2280 char out_buffer[BUFSIZ * 2];
2281 char *line;
2282 /* The number of lines to read. If too low it will cause too much
2283 * redrawing (and possible flickering), if too high responsiveness
2284 * will suffer. */
2285 unsigned long lines = view->height;
2286 int redraw_from = -1;
2288 if (!view->pipe)
2289 return TRUE;
2291 /* Only redraw if lines are visible. */
2292 if (view->offset + view->height >= view->lines)
2293 redraw_from = view->lines - view->offset;
2295 /* FIXME: This is probably not perfect for backgrounded views. */
2296 if (!realloc_lines(view, view->lines + lines))
2297 goto alloc_error;
2299 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2300 size_t linelen = strlen(line);
2302 if (linelen)
2303 line[linelen - 1] = 0;
2305 if (opt_iconv != ICONV_NONE) {
2306 ICONV_CONST char *inbuf = line;
2307 size_t inlen = linelen;
2309 char *outbuf = out_buffer;
2310 size_t outlen = sizeof(out_buffer);
2312 size_t ret;
2314 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2315 if (ret != (size_t) -1) {
2316 line = out_buffer;
2317 linelen = strlen(out_buffer);
2318 }
2319 }
2321 if (!view->ops->read(view, line))
2322 goto alloc_error;
2324 if (lines-- == 1)
2325 break;
2326 }
2328 {
2329 int digits;
2331 lines = view->lines;
2332 for (digits = 0; lines; digits++)
2333 lines /= 10;
2335 /* Keep the displayed view in sync with line number scaling. */
2336 if (digits != view->digits) {
2337 view->digits = digits;
2338 redraw_from = 0;
2339 }
2340 }
2342 if (!view_is_displayed(view))
2343 goto check_pipe;
2345 if (view == VIEW(REQ_VIEW_TREE)) {
2346 /* Clear the view and redraw everything since the tree sorting
2347 * might have rearranged things. */
2348 redraw_view(view);
2350 } else if (redraw_from >= 0) {
2351 /* If this is an incremental update, redraw the previous line
2352 * since for commits some members could have changed when
2353 * loading the main view. */
2354 if (redraw_from > 0)
2355 redraw_from--;
2357 /* Since revision graph visualization requires knowledge
2358 * about the parent commit, it causes a further one-off
2359 * needed to be redrawn for incremental updates. */
2360 if (redraw_from > 0 && opt_rev_graph)
2361 redraw_from--;
2363 /* Incrementally draw avoids flickering. */
2364 redraw_view_from(view, redraw_from);
2365 }
2367 if (view == VIEW(REQ_VIEW_BLAME))
2368 redraw_view_dirty(view);
2370 /* Update the title _after_ the redraw so that if the redraw picks up a
2371 * commit reference in view->ref it'll be available here. */
2372 update_view_title(view);
2374 check_pipe:
2375 if (ferror(view->pipe)) {
2376 report("Failed to read: %s", strerror(errno));
2377 goto end;
2379 } else if (feof(view->pipe)) {
2380 report("");
2381 goto end;
2382 }
2384 return TRUE;
2386 alloc_error:
2387 report("Allocation failure");
2389 end:
2390 if (view->ops->read(view, NULL))
2391 end_update(view);
2392 return FALSE;
2393 }
2395 static struct line *
2396 add_line_data(struct view *view, void *data, enum line_type type)
2397 {
2398 struct line *line = &view->line[view->lines++];
2400 memset(line, 0, sizeof(*line));
2401 line->type = type;
2402 line->data = data;
2404 return line;
2405 }
2407 static struct line *
2408 add_line_text(struct view *view, char *data, enum line_type type)
2409 {
2410 if (data)
2411 data = strdup(data);
2413 return data ? add_line_data(view, data, type) : NULL;
2414 }
2417 /*
2418 * View opening
2419 */
2421 enum open_flags {
2422 OPEN_DEFAULT = 0, /* Use default view switching. */
2423 OPEN_SPLIT = 1, /* Split current view. */
2424 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2425 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2426 OPEN_NOMAXIMIZE = 8 /* Do not maximize the current view. */
2427 };
2429 static void
2430 open_view(struct view *prev, enum request request, enum open_flags flags)
2431 {
2432 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2433 bool split = !!(flags & OPEN_SPLIT);
2434 bool reload = !!(flags & OPEN_RELOAD);
2435 bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2436 struct view *view = VIEW(request);
2437 int nviews = displayed_views();
2438 struct view *base_view = display[0];
2440 if (view == prev && nviews == 1 && !reload) {
2441 report("Already in %s view", view->name);
2442 return;
2443 }
2445 if (view->git_dir && !opt_git_dir[0]) {
2446 report("The %s view is disabled in pager view", view->name);
2447 return;
2448 }
2450 if (split) {
2451 display[1] = view;
2452 if (!backgrounded)
2453 current_view = 1;
2454 } else if (!nomaximize) {
2455 /* Maximize the current view. */
2456 memset(display, 0, sizeof(display));
2457 current_view = 0;
2458 display[current_view] = view;
2459 }
2461 /* Resize the view when switching between split- and full-screen,
2462 * or when switching between two different full-screen views. */
2463 if (nviews != displayed_views() ||
2464 (nviews == 1 && base_view != display[0]))
2465 resize_display();
2467 if (view->ops->open) {
2468 if (!view->ops->open(view)) {
2469 report("Failed to load %s view", view->name);
2470 return;
2471 }
2473 } else if ((reload || strcmp(view->vid, view->id)) &&
2474 !begin_update(view)) {
2475 report("Failed to load %s view", view->name);
2476 return;
2477 }
2479 if (split && prev->lineno - prev->offset >= prev->height) {
2480 /* Take the title line into account. */
2481 int lines = prev->lineno - prev->offset - prev->height + 1;
2483 /* Scroll the view that was split if the current line is
2484 * outside the new limited view. */
2485 do_scroll_view(prev, lines);
2486 }
2488 if (prev && view != prev) {
2489 if (split && !backgrounded) {
2490 /* "Blur" the previous view. */
2491 update_view_title(prev);
2492 }
2494 view->parent = prev;
2495 }
2497 if (view->pipe && view->lines == 0) {
2498 /* Clear the old view and let the incremental updating refill
2499 * the screen. */
2500 werase(view->win);
2501 report("");
2502 } else {
2503 redraw_view(view);
2504 report("");
2505 }
2507 /* If the view is backgrounded the above calls to report()
2508 * won't redraw the view title. */
2509 if (backgrounded)
2510 update_view_title(view);
2511 }
2513 static void
2514 open_external_viewer(const char *cmd)
2515 {
2516 def_prog_mode(); /* save current tty modes */
2517 endwin(); /* restore original tty modes */
2518 system(cmd);
2519 fprintf(stderr, "Press Enter to continue");
2520 getc(stdin);
2521 reset_prog_mode();
2522 redraw_display();
2523 }
2525 static void
2526 open_mergetool(const char *file)
2527 {
2528 char cmd[SIZEOF_STR];
2529 char file_sq[SIZEOF_STR];
2531 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2532 string_format(cmd, "git mergetool %s", file_sq)) {
2533 open_external_viewer(cmd);
2534 }
2535 }
2537 static void
2538 open_editor(bool from_root, const char *file)
2539 {
2540 char cmd[SIZEOF_STR];
2541 char file_sq[SIZEOF_STR];
2542 char *editor;
2543 char *prefix = from_root ? opt_cdup : "";
2545 editor = getenv("GIT_EDITOR");
2546 if (!editor && *opt_editor)
2547 editor = opt_editor;
2548 if (!editor)
2549 editor = getenv("VISUAL");
2550 if (!editor)
2551 editor = getenv("EDITOR");
2552 if (!editor)
2553 editor = "vi";
2555 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2556 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2557 open_external_viewer(cmd);
2558 }
2559 }
2561 static void
2562 open_run_request(enum request request)
2563 {
2564 struct run_request *req = get_run_request(request);
2565 char buf[SIZEOF_STR * 2];
2566 size_t bufpos;
2567 char *cmd;
2569 if (!req) {
2570 report("Unknown run request");
2571 return;
2572 }
2574 bufpos = 0;
2575 cmd = req->cmd;
2577 while (cmd) {
2578 char *next = strstr(cmd, "%(");
2579 int len = next - cmd;
2580 char *value;
2582 if (!next) {
2583 len = strlen(cmd);
2584 value = "";
2586 } else if (!strncmp(next, "%(head)", 7)) {
2587 value = ref_head;
2589 } else if (!strncmp(next, "%(commit)", 9)) {
2590 value = ref_commit;
2592 } else if (!strncmp(next, "%(blob)", 7)) {
2593 value = ref_blob;
2595 } else {
2596 report("Unknown replacement in run request: `%s`", req->cmd);
2597 return;
2598 }
2600 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2601 return;
2603 if (next)
2604 next = strchr(next, ')') + 1;
2605 cmd = next;
2606 }
2608 open_external_viewer(buf);
2609 }
2611 /*
2612 * User request switch noodle
2613 */
2615 static int
2616 view_driver(struct view *view, enum request request)
2617 {
2618 int i;
2620 if (request == REQ_NONE) {
2621 doupdate();
2622 return TRUE;
2623 }
2625 if (request > REQ_NONE) {
2626 open_run_request(request);
2627 /* FIXME: When all views can refresh always do this. */
2628 if (view == VIEW(REQ_VIEW_STATUS) ||
2629 view == VIEW(REQ_VIEW_STAGE))
2630 request = REQ_REFRESH;
2631 else
2632 return TRUE;
2633 }
2635 if (view && view->lines) {
2636 request = view->ops->request(view, request, &view->line[view->lineno]);
2637 if (request == REQ_NONE)
2638 return TRUE;
2639 }
2641 switch (request) {
2642 case REQ_MOVE_UP:
2643 case REQ_MOVE_DOWN:
2644 case REQ_MOVE_PAGE_UP:
2645 case REQ_MOVE_PAGE_DOWN:
2646 case REQ_MOVE_FIRST_LINE:
2647 case REQ_MOVE_LAST_LINE:
2648 move_view(view, request);
2649 break;
2651 case REQ_SCROLL_LINE_DOWN:
2652 case REQ_SCROLL_LINE_UP:
2653 case REQ_SCROLL_PAGE_DOWN:
2654 case REQ_SCROLL_PAGE_UP:
2655 scroll_view(view, request);
2656 break;
2658 case REQ_VIEW_BLAME:
2659 if (!opt_file[0]) {
2660 report("No file chosen, press %s to open tree view",
2661 get_key(REQ_VIEW_TREE));
2662 break;
2663 }
2664 open_view(view, request, OPEN_DEFAULT);
2665 break;
2667 case REQ_VIEW_BLOB:
2668 if (!ref_blob[0]) {
2669 report("No file chosen, press %s to open tree view",
2670 get_key(REQ_VIEW_TREE));
2671 break;
2672 }
2673 open_view(view, request, OPEN_DEFAULT);
2674 break;
2676 case REQ_VIEW_PAGER:
2677 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2678 report("No pager content, press %s to run command from prompt",
2679 get_key(REQ_PROMPT));
2680 break;
2681 }
2682 open_view(view, request, OPEN_DEFAULT);
2683 break;
2685 case REQ_VIEW_STAGE:
2686 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2687 report("No stage content, press %s to open the status view and choose file",
2688 get_key(REQ_VIEW_STATUS));
2689 break;
2690 }
2691 open_view(view, request, OPEN_DEFAULT);
2692 break;
2694 case REQ_VIEW_STATUS:
2695 if (opt_is_inside_work_tree == FALSE) {
2696 report("The status view requires a working tree");
2697 break;
2698 }
2699 open_view(view, request, OPEN_DEFAULT);
2700 break;
2702 case REQ_VIEW_MAIN:
2703 case REQ_VIEW_DIFF:
2704 case REQ_VIEW_LOG:
2705 case REQ_VIEW_TREE:
2706 case REQ_VIEW_HELP:
2707 open_view(view, request, OPEN_DEFAULT);
2708 break;
2710 case REQ_NEXT:
2711 case REQ_PREVIOUS:
2712 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2714 if ((view == VIEW(REQ_VIEW_DIFF) &&
2715 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2716 (view == VIEW(REQ_VIEW_DIFF) &&
2717 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2718 (view == VIEW(REQ_VIEW_STAGE) &&
2719 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2720 (view == VIEW(REQ_VIEW_BLOB) &&
2721 view->parent == VIEW(REQ_VIEW_TREE))) {
2722 int line;
2724 view = view->parent;
2725 line = view->lineno;
2726 move_view(view, request);
2727 if (view_is_displayed(view))
2728 update_view_title(view);
2729 if (line != view->lineno)
2730 view->ops->request(view, REQ_ENTER,
2731 &view->line[view->lineno]);
2733 } else {
2734 move_view(view, request);
2735 }
2736 break;
2738 case REQ_VIEW_NEXT:
2739 {
2740 int nviews = displayed_views();
2741 int next_view = (current_view + 1) % nviews;
2743 if (next_view == current_view) {
2744 report("Only one view is displayed");
2745 break;
2746 }
2748 current_view = next_view;
2749 /* Blur out the title of the previous view. */
2750 update_view_title(view);
2751 report("");
2752 break;
2753 }
2754 case REQ_REFRESH:
2755 report("Refreshing is not yet supported for the %s view", view->name);
2756 break;
2758 case REQ_MAXIMIZE:
2759 if (displayed_views() == 2)
2760 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2761 break;
2763 case REQ_TOGGLE_LINENO:
2764 opt_line_number = !opt_line_number;
2765 redraw_display();
2766 break;
2768 case REQ_TOGGLE_DATE:
2769 opt_date = !opt_date;
2770 redraw_display();
2771 break;
2773 case REQ_TOGGLE_AUTHOR:
2774 opt_author = !opt_author;
2775 redraw_display();
2776 break;
2778 case REQ_TOGGLE_REV_GRAPH:
2779 opt_rev_graph = !opt_rev_graph;
2780 redraw_display();
2781 break;
2783 case REQ_TOGGLE_REFS:
2784 opt_show_refs = !opt_show_refs;
2785 redraw_display();
2786 break;
2788 case REQ_PROMPT:
2789 /* Always reload^Wrerun commands from the prompt. */
2790 open_view(view, opt_request, OPEN_RELOAD);
2791 break;
2793 case REQ_SEARCH:
2794 case REQ_SEARCH_BACK:
2795 search_view(view, request);
2796 break;
2798 case REQ_FIND_NEXT:
2799 case REQ_FIND_PREV:
2800 find_next(view, request);
2801 break;
2803 case REQ_STOP_LOADING:
2804 for (i = 0; i < ARRAY_SIZE(views); i++) {
2805 view = &views[i];
2806 if (view->pipe)
2807 report("Stopped loading the %s view", view->name),
2808 end_update(view);
2809 }
2810 break;
2812 case REQ_SHOW_VERSION:
2813 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2814 return TRUE;
2816 case REQ_SCREEN_RESIZE:
2817 resize_display();
2818 /* Fall-through */
2819 case REQ_SCREEN_REDRAW:
2820 redraw_display();
2821 break;
2823 case REQ_EDIT:
2824 report("Nothing to edit");
2825 break;
2828 case REQ_ENTER:
2829 report("Nothing to enter");
2830 break;
2833 case REQ_VIEW_CLOSE:
2834 /* XXX: Mark closed views by letting view->parent point to the
2835 * view itself. Parents to closed view should never be
2836 * followed. */
2837 if (view->parent &&
2838 view->parent->parent != view->parent) {
2839 memset(display, 0, sizeof(display));
2840 current_view = 0;
2841 display[current_view] = view->parent;
2842 view->parent = view;
2843 resize_display();
2844 redraw_display();
2845 break;
2846 }
2847 /* Fall-through */
2848 case REQ_QUIT:
2849 return FALSE;
2851 default:
2852 /* An unknown key will show most commonly used commands. */
2853 report("Unknown key, press 'h' for help");
2854 return TRUE;
2855 }
2857 return TRUE;
2858 }
2861 /*
2862 * Pager backend
2863 */
2865 static bool
2866 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2867 {
2868 char *text = line->data;
2870 if (opt_line_number && draw_lineno(view, lineno))
2871 return TRUE;
2873 draw_text(view, line->type, text, TRUE);
2874 return TRUE;
2875 }
2877 static bool
2878 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2879 {
2880 char refbuf[SIZEOF_STR];
2881 char *ref = NULL;
2882 FILE *pipe;
2884 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2885 return TRUE;
2887 pipe = popen(refbuf, "r");
2888 if (!pipe)
2889 return TRUE;
2891 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2892 ref = chomp_string(ref);
2893 pclose(pipe);
2895 if (!ref || !*ref)
2896 return TRUE;
2898 /* This is the only fatal call, since it can "corrupt" the buffer. */
2899 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2900 return FALSE;
2902 return TRUE;
2903 }
2905 static void
2906 add_pager_refs(struct view *view, struct line *line)
2907 {
2908 char buf[SIZEOF_STR];
2909 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2910 struct ref **refs;
2911 size_t bufpos = 0, refpos = 0;
2912 const char *sep = "Refs: ";
2913 bool is_tag = FALSE;
2915 assert(line->type == LINE_COMMIT);
2917 refs = get_refs(commit_id);
2918 if (!refs) {
2919 if (view == VIEW(REQ_VIEW_DIFF))
2920 goto try_add_describe_ref;
2921 return;
2922 }
2924 do {
2925 struct ref *ref = refs[refpos];
2926 char *fmt = ref->tag ? "%s[%s]" :
2927 ref->remote ? "%s<%s>" : "%s%s";
2929 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2930 return;
2931 sep = ", ";
2932 if (ref->tag)
2933 is_tag = TRUE;
2934 } while (refs[refpos++]->next);
2936 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2937 try_add_describe_ref:
2938 /* Add <tag>-g<commit_id> "fake" reference. */
2939 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2940 return;
2941 }
2943 if (bufpos == 0)
2944 return;
2946 if (!realloc_lines(view, view->line_size + 1))
2947 return;
2949 add_line_text(view, buf, LINE_PP_REFS);
2950 }
2952 static bool
2953 pager_read(struct view *view, char *data)
2954 {
2955 struct line *line;
2957 if (!data)
2958 return TRUE;
2960 line = add_line_text(view, data, get_line_type(data));
2961 if (!line)
2962 return FALSE;
2964 if (line->type == LINE_COMMIT &&
2965 (view == VIEW(REQ_VIEW_DIFF) ||
2966 view == VIEW(REQ_VIEW_LOG)))
2967 add_pager_refs(view, line);
2969 return TRUE;
2970 }
2972 static enum request
2973 pager_request(struct view *view, enum request request, struct line *line)
2974 {
2975 int split = 0;
2977 if (request != REQ_ENTER)
2978 return request;
2980 if (line->type == LINE_COMMIT &&
2981 (view == VIEW(REQ_VIEW_LOG) ||
2982 view == VIEW(REQ_VIEW_PAGER))) {
2983 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2984 split = 1;
2985 }
2987 /* Always scroll the view even if it was split. That way
2988 * you can use Enter to scroll through the log view and
2989 * split open each commit diff. */
2990 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2992 /* FIXME: A minor workaround. Scrolling the view will call report("")
2993 * but if we are scrolling a non-current view this won't properly
2994 * update the view title. */
2995 if (split)
2996 update_view_title(view);
2998 return REQ_NONE;
2999 }
3001 static bool
3002 pager_grep(struct view *view, struct line *line)
3003 {
3004 regmatch_t pmatch;
3005 char *text = line->data;
3007 if (!*text)
3008 return FALSE;
3010 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3011 return FALSE;
3013 return TRUE;
3014 }
3016 static void
3017 pager_select(struct view *view, struct line *line)
3018 {
3019 if (line->type == LINE_COMMIT) {
3020 char *text = (char *)line->data + STRING_SIZE("commit ");
3022 if (view != VIEW(REQ_VIEW_PAGER))
3023 string_copy_rev(view->ref, text);
3024 string_copy_rev(ref_commit, text);
3025 }
3026 }
3028 static struct view_ops pager_ops = {
3029 "line",
3030 NULL,
3031 pager_read,
3032 pager_draw,
3033 pager_request,
3034 pager_grep,
3035 pager_select,
3036 };
3039 /*
3040 * Help backend
3041 */
3043 static bool
3044 help_open(struct view *view)
3045 {
3046 char buf[BUFSIZ];
3047 int lines = ARRAY_SIZE(req_info) + 2;
3048 int i;
3050 if (view->lines > 0)
3051 return TRUE;
3053 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3054 if (!req_info[i].request)
3055 lines++;
3057 lines += run_requests + 1;
3059 view->line = calloc(lines, sizeof(*view->line));
3060 if (!view->line)
3061 return FALSE;
3063 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3065 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3066 char *key;
3068 if (req_info[i].request == REQ_NONE)
3069 continue;
3071 if (!req_info[i].request) {
3072 add_line_text(view, "", LINE_DEFAULT);
3073 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3074 continue;
3075 }
3077 key = get_key(req_info[i].request);
3078 if (!*key)
3079 key = "(no key defined)";
3081 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3082 continue;
3084 add_line_text(view, buf, LINE_DEFAULT);
3085 }
3087 if (run_requests) {
3088 add_line_text(view, "", LINE_DEFAULT);
3089 add_line_text(view, "External commands:", LINE_DEFAULT);
3090 }
3092 for (i = 0; i < run_requests; i++) {
3093 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3094 char *key;
3096 if (!req)
3097 continue;
3099 key = get_key_name(req->key);
3100 if (!*key)
3101 key = "(no key defined)";
3103 if (!string_format(buf, " %-10s %-14s `%s`",
3104 keymap_table[req->keymap].name,
3105 key, req->cmd))
3106 continue;
3108 add_line_text(view, buf, LINE_DEFAULT);
3109 }
3111 return TRUE;
3112 }
3114 static struct view_ops help_ops = {
3115 "line",
3116 help_open,
3117 NULL,
3118 pager_draw,
3119 pager_request,
3120 pager_grep,
3121 pager_select,
3122 };
3125 /*
3126 * Tree backend
3127 */
3129 struct tree_stack_entry {
3130 struct tree_stack_entry *prev; /* Entry below this in the stack */
3131 unsigned long lineno; /* Line number to restore */
3132 char *name; /* Position of name in opt_path */
3133 };
3135 /* The top of the path stack. */
3136 static struct tree_stack_entry *tree_stack = NULL;
3137 unsigned long tree_lineno = 0;
3139 static void
3140 pop_tree_stack_entry(void)
3141 {
3142 struct tree_stack_entry *entry = tree_stack;
3144 tree_lineno = entry->lineno;
3145 entry->name[0] = 0;
3146 tree_stack = entry->prev;
3147 free(entry);
3148 }
3150 static void
3151 push_tree_stack_entry(char *name, unsigned long lineno)
3152 {
3153 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3154 size_t pathlen = strlen(opt_path);
3156 if (!entry)
3157 return;
3159 entry->prev = tree_stack;
3160 entry->name = opt_path + pathlen;
3161 tree_stack = entry;
3163 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3164 pop_tree_stack_entry();
3165 return;
3166 }
3168 /* Move the current line to the first tree entry. */
3169 tree_lineno = 1;
3170 entry->lineno = lineno;
3171 }
3173 /* Parse output from git-ls-tree(1):
3174 *
3175 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3176 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3177 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3178 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3179 */
3181 #define SIZEOF_TREE_ATTR \
3182 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3184 #define TREE_UP_FORMAT "040000 tree %s\t.."
3186 static int
3187 tree_compare_entry(enum line_type type1, char *name1,
3188 enum line_type type2, char *name2)
3189 {
3190 if (type1 != type2) {
3191 if (type1 == LINE_TREE_DIR)
3192 return -1;
3193 return 1;
3194 }
3196 return strcmp(name1, name2);
3197 }
3199 static char *
3200 tree_path(struct line *line)
3201 {
3202 char *path = line->data;
3204 return path + SIZEOF_TREE_ATTR;
3205 }
3207 static bool
3208 tree_read(struct view *view, char *text)
3209 {
3210 size_t textlen = text ? strlen(text) : 0;
3211 char buf[SIZEOF_STR];
3212 unsigned long pos;
3213 enum line_type type;
3214 bool first_read = view->lines == 0;
3216 if (!text)
3217 return TRUE;
3218 if (textlen <= SIZEOF_TREE_ATTR)
3219 return FALSE;
3221 type = text[STRING_SIZE("100644 ")] == 't'
3222 ? LINE_TREE_DIR : LINE_TREE_FILE;
3224 if (first_read) {
3225 /* Add path info line */
3226 if (!string_format(buf, "Directory path /%s", opt_path) ||
3227 !realloc_lines(view, view->line_size + 1) ||
3228 !add_line_text(view, buf, LINE_DEFAULT))
3229 return FALSE;
3231 /* Insert "link" to parent directory. */
3232 if (*opt_path) {
3233 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3234 !realloc_lines(view, view->line_size + 1) ||
3235 !add_line_text(view, buf, LINE_TREE_DIR))
3236 return FALSE;
3237 }
3238 }
3240 /* Strip the path part ... */
3241 if (*opt_path) {
3242 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3243 size_t striplen = strlen(opt_path);
3244 char *path = text + SIZEOF_TREE_ATTR;
3246 if (pathlen > striplen)
3247 memmove(path, path + striplen,
3248 pathlen - striplen + 1);
3249 }
3251 /* Skip "Directory ..." and ".." line. */
3252 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3253 struct line *line = &view->line[pos];
3254 char *path1 = tree_path(line);
3255 char *path2 = text + SIZEOF_TREE_ATTR;
3256 int cmp = tree_compare_entry(line->type, path1, type, path2);
3258 if (cmp <= 0)
3259 continue;
3261 text = strdup(text);
3262 if (!text)
3263 return FALSE;
3265 if (view->lines > pos)
3266 memmove(&view->line[pos + 1], &view->line[pos],
3267 (view->lines - pos) * sizeof(*line));
3269 line = &view->line[pos];
3270 line->data = text;
3271 line->type = type;
3272 view->lines++;
3273 return TRUE;
3274 }
3276 if (!add_line_text(view, text, type))
3277 return FALSE;
3279 if (tree_lineno > view->lineno) {
3280 view->lineno = tree_lineno;
3281 tree_lineno = 0;
3282 }
3284 return TRUE;
3285 }
3287 static enum request
3288 tree_request(struct view *view, enum request request, struct line *line)
3289 {
3290 enum open_flags flags;
3292 if (request == REQ_VIEW_BLAME) {
3293 char *filename = tree_path(line);
3295 if (line->type == LINE_TREE_DIR) {
3296 report("Cannot show blame for directory %s", opt_path);
3297 return REQ_NONE;
3298 }
3300 string_copy(opt_ref, view->vid);
3301 string_format(opt_file, "%s%s", opt_path, filename);
3302 return request;
3303 }
3304 if (request == REQ_TREE_PARENT) {
3305 if (*opt_path) {
3306 /* fake 'cd ..' */
3307 request = REQ_ENTER;
3308 line = &view->line[1];
3309 } else {
3310 /* quit view if at top of tree */
3311 return REQ_VIEW_CLOSE;
3312 }
3313 }
3314 if (request != REQ_ENTER)
3315 return request;
3317 /* Cleanup the stack if the tree view is at a different tree. */
3318 while (!*opt_path && tree_stack)
3319 pop_tree_stack_entry();
3321 switch (line->type) {
3322 case LINE_TREE_DIR:
3323 /* Depending on whether it is a subdir or parent (updir?) link
3324 * mangle the path buffer. */
3325 if (line == &view->line[1] && *opt_path) {
3326 pop_tree_stack_entry();
3328 } else {
3329 char *basename = tree_path(line);
3331 push_tree_stack_entry(basename, view->lineno);
3332 }
3334 /* Trees and subtrees share the same ID, so they are not not
3335 * unique like blobs. */
3336 flags = OPEN_RELOAD;
3337 request = REQ_VIEW_TREE;
3338 break;
3340 case LINE_TREE_FILE:
3341 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3342 request = REQ_VIEW_BLOB;
3343 break;
3345 default:
3346 return TRUE;
3347 }
3349 open_view(view, request, flags);
3350 if (request == REQ_VIEW_TREE) {
3351 view->lineno = tree_lineno;
3352 }
3354 return REQ_NONE;
3355 }
3357 static void
3358 tree_select(struct view *view, struct line *line)
3359 {
3360 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3362 if (line->type == LINE_TREE_FILE) {
3363 string_copy_rev(ref_blob, text);
3365 } else if (line->type != LINE_TREE_DIR) {
3366 return;
3367 }
3369 string_copy_rev(view->ref, text);
3370 }
3372 static struct view_ops tree_ops = {
3373 "file",
3374 NULL,
3375 tree_read,
3376 pager_draw,
3377 tree_request,
3378 pager_grep,
3379 tree_select,
3380 };
3382 static bool
3383 blob_read(struct view *view, char *line)
3384 {
3385 if (!line)
3386 return TRUE;
3387 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3388 }
3390 static struct view_ops blob_ops = {
3391 "line",
3392 NULL,
3393 blob_read,
3394 pager_draw,
3395 pager_request,
3396 pager_grep,
3397 pager_select,
3398 };
3400 /*
3401 * Blame backend
3402 *
3403 * Loading the blame view is a two phase job:
3404 *
3405 * 1. File content is read either using opt_file from the
3406 * filesystem or using git-cat-file.
3407 * 2. Then blame information is incrementally added by
3408 * reading output from git-blame.
3409 */
3411 struct blame_commit {
3412 char id[SIZEOF_REV]; /* SHA1 ID. */
3413 char title[128]; /* First line of the commit message. */
3414 char author[75]; /* Author of the commit. */
3415 struct tm time; /* Date from the author ident. */
3416 char filename[128]; /* Name of file. */
3417 };
3419 struct blame {
3420 struct blame_commit *commit;
3421 unsigned int header:1;
3422 char text[1];
3423 };
3425 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3426 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3428 static bool
3429 blame_open(struct view *view)
3430 {
3431 char path[SIZEOF_STR];
3432 char ref[SIZEOF_STR] = "";
3434 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3435 return FALSE;
3437 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3438 return FALSE;
3440 if (*opt_ref) {
3441 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3442 return FALSE;
3443 } else {
3444 view->pipe = fopen(opt_file, "r");
3445 if (!view->pipe &&
3446 !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3447 return FALSE;
3448 }
3450 if (!view->pipe)
3451 view->pipe = popen(view->cmd, "r");
3452 if (!view->pipe)
3453 return FALSE;
3455 if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3456 return FALSE;
3458 string_format(view->ref, "%s ...", opt_file);
3459 string_copy_rev(view->vid, opt_file);
3460 set_nonblocking_input(TRUE);
3462 if (view->line) {
3463 int i;
3465 for (i = 0; i < view->lines; i++)
3466 free(view->line[i].data);
3467 free(view->line);
3468 }
3470 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3471 view->offset = view->lines = view->lineno = 0;
3472 view->line = NULL;
3473 view->start_time = time(NULL);
3475 return TRUE;
3476 }
3478 static struct blame_commit *
3479 get_blame_commit(struct view *view, const char *id)
3480 {
3481 size_t i;
3483 for (i = 0; i < view->lines; i++) {
3484 struct blame *blame = view->line[i].data;
3486 if (!blame->commit)
3487 continue;
3489 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3490 return blame->commit;
3491 }
3493 {
3494 struct blame_commit *commit = calloc(1, sizeof(*commit));
3496 if (commit)
3497 string_ncopy(commit->id, id, SIZEOF_REV);
3498 return commit;
3499 }
3500 }
3502 static bool
3503 parse_number(char **posref, size_t *number, size_t min, size_t max)
3504 {
3505 char *pos = *posref;
3507 *posref = NULL;
3508 pos = strchr(pos + 1, ' ');
3509 if (!pos || !isdigit(pos[1]))
3510 return FALSE;
3511 *number = atoi(pos + 1);
3512 if (*number < min || *number > max)
3513 return FALSE;
3515 *posref = pos;
3516 return TRUE;
3517 }
3519 static struct blame_commit *
3520 parse_blame_commit(struct view *view, char *text, int *blamed)
3521 {
3522 struct blame_commit *commit;
3523 struct blame *blame;
3524 char *pos = text + SIZEOF_REV - 1;
3525 size_t lineno;
3526 size_t group;
3528 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3529 return NULL;
3531 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3532 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3533 return NULL;
3535 commit = get_blame_commit(view, text);
3536 if (!commit)
3537 return NULL;
3539 *blamed += group;
3540 while (group--) {
3541 struct line *line = &view->line[lineno + group - 1];
3543 blame = line->data;
3544 blame->commit = commit;
3545 blame->header = !group;
3546 line->dirty = 1;
3547 }
3549 return commit;
3550 }
3552 static bool
3553 blame_read_file(struct view *view, char *line)
3554 {
3555 if (!line) {
3556 FILE *pipe = NULL;
3558 if (view->lines > 0)
3559 pipe = popen(view->cmd, "r");
3560 else if (!view->parent)
3561 die("No blame exist for %s", view->vid);
3562 view->cmd[0] = 0;
3563 if (!pipe) {
3564 report("Failed to load blame data");
3565 return TRUE;
3566 }
3568 fclose(view->pipe);
3569 view->pipe = pipe;
3570 return FALSE;
3572 } else {
3573 size_t linelen = strlen(line);
3574 struct blame *blame = malloc(sizeof(*blame) + linelen);
3576 if (!line)
3577 return FALSE;
3579 blame->commit = NULL;
3580 strncpy(blame->text, line, linelen);
3581 blame->text[linelen] = 0;
3582 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3583 }
3584 }
3586 static bool
3587 match_blame_header(const char *name, char **line)
3588 {
3589 size_t namelen = strlen(name);
3590 bool matched = !strncmp(name, *line, namelen);
3592 if (matched)
3593 *line += namelen;
3595 return matched;
3596 }
3598 static bool
3599 blame_read(struct view *view, char *line)
3600 {
3601 static struct blame_commit *commit = NULL;
3602 static int blamed = 0;
3603 static time_t author_time;
3605 if (*view->cmd)
3606 return blame_read_file(view, line);
3608 if (!line) {
3609 /* Reset all! */
3610 commit = NULL;
3611 blamed = 0;
3612 string_format(view->ref, "%s", view->vid);
3613 if (view_is_displayed(view)) {
3614 update_view_title(view);
3615 redraw_view_from(view, 0);
3616 }
3617 return TRUE;
3618 }
3620 if (!commit) {
3621 commit = parse_blame_commit(view, line, &blamed);
3622 string_format(view->ref, "%s %2d%%", view->vid,
3623 blamed * 100 / view->lines);
3625 } else if (match_blame_header("author ", &line)) {
3626 string_ncopy(commit->author, line, strlen(line));
3628 } else if (match_blame_header("author-time ", &line)) {
3629 author_time = (time_t) atol(line);
3631 } else if (match_blame_header("author-tz ", &line)) {
3632 long tz;
3634 tz = ('0' - line[1]) * 60 * 60 * 10;
3635 tz += ('0' - line[2]) * 60 * 60;
3636 tz += ('0' - line[3]) * 60;
3637 tz += ('0' - line[4]) * 60;
3639 if (line[0] == '-')
3640 tz = -tz;
3642 author_time -= tz;
3643 gmtime_r(&author_time, &commit->time);
3645 } else if (match_blame_header("summary ", &line)) {
3646 string_ncopy(commit->title, line, strlen(line));
3648 } else if (match_blame_header("filename ", &line)) {
3649 string_ncopy(commit->filename, line, strlen(line));
3650 commit = NULL;
3651 }
3653 return TRUE;
3654 }
3656 static bool
3657 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3658 {
3659 struct blame *blame = line->data;
3660 struct tm *time = NULL;
3661 char *id = NULL, *author = NULL;
3663 if (blame->commit && *blame->commit->filename) {
3664 id = blame->commit->id;
3665 author = blame->commit->author;
3666 time = &blame->commit->time;
3667 }
3669 if (opt_date && draw_date(view, time))
3670 return TRUE;
3672 if (opt_author &&
3673 draw_field(view, LINE_MAIN_AUTHOR, author, AUTHOR_COLS, TRUE))
3674 return TRUE;
3676 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3677 return TRUE;
3679 if (draw_lineno(view, lineno))
3680 return TRUE;
3682 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3683 return TRUE;
3684 }
3686 static enum request
3687 blame_request(struct view *view, enum request request, struct line *line)
3688 {
3689 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3690 struct blame *blame = line->data;
3692 switch (request) {
3693 case REQ_ENTER:
3694 if (!blame->commit) {
3695 report("No commit loaded yet");
3696 break;
3697 }
3699 if (!strcmp(blame->commit->id, NULL_ID)) {
3700 char path[SIZEOF_STR];
3702 if (sq_quote(path, 0, view->vid) >= sizeof(path))
3703 break;
3704 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3705 }
3707 open_view(view, REQ_VIEW_DIFF, flags);
3708 break;
3710 default:
3711 return request;
3712 }
3714 return REQ_NONE;
3715 }
3717 static bool
3718 blame_grep(struct view *view, struct line *line)
3719 {
3720 struct blame *blame = line->data;
3721 struct blame_commit *commit = blame->commit;
3722 regmatch_t pmatch;
3724 #define MATCH(text, on) \
3725 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3727 if (commit) {
3728 char buf[DATE_COLS + 1];
3730 if (MATCH(commit->title, 1) ||
3731 MATCH(commit->author, opt_author) ||
3732 MATCH(commit->id, opt_date))
3733 return TRUE;
3735 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3736 MATCH(buf, 1))
3737 return TRUE;
3738 }
3740 return MATCH(blame->text, 1);
3742 #undef MATCH
3743 }
3745 static void
3746 blame_select(struct view *view, struct line *line)
3747 {
3748 struct blame *blame = line->data;
3749 struct blame_commit *commit = blame->commit;
3751 if (!commit)
3752 return;
3754 if (!strcmp(commit->id, NULL_ID))
3755 string_ncopy(ref_commit, "HEAD", 4);
3756 else
3757 string_copy_rev(ref_commit, commit->id);
3758 }
3760 static struct view_ops blame_ops = {
3761 "line",
3762 blame_open,
3763 blame_read,
3764 blame_draw,
3765 blame_request,
3766 blame_grep,
3767 blame_select,
3768 };
3770 /*
3771 * Status backend
3772 */
3774 struct status {
3775 char status;
3776 struct {
3777 mode_t mode;
3778 char rev[SIZEOF_REV];
3779 char name[SIZEOF_STR];
3780 } old;
3781 struct {
3782 mode_t mode;
3783 char rev[SIZEOF_REV];
3784 char name[SIZEOF_STR];
3785 } new;
3786 };
3788 static char status_onbranch[SIZEOF_STR];
3789 static struct status stage_status;
3790 static enum line_type stage_line_type;
3792 /* Get fields from the diff line:
3793 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3794 */
3795 static inline bool
3796 status_get_diff(struct status *file, char *buf, size_t bufsize)
3797 {
3798 char *old_mode = buf + 1;
3799 char *new_mode = buf + 8;
3800 char *old_rev = buf + 15;
3801 char *new_rev = buf + 56;
3802 char *status = buf + 97;
3804 if (bufsize < 99 ||
3805 old_mode[-1] != ':' ||
3806 new_mode[-1] != ' ' ||
3807 old_rev[-1] != ' ' ||
3808 new_rev[-1] != ' ' ||
3809 status[-1] != ' ')
3810 return FALSE;
3812 file->status = *status;
3814 string_copy_rev(file->old.rev, old_rev);
3815 string_copy_rev(file->new.rev, new_rev);
3817 file->old.mode = strtoul(old_mode, NULL, 8);
3818 file->new.mode = strtoul(new_mode, NULL, 8);
3820 file->old.name[0] = file->new.name[0] = 0;
3822 return TRUE;
3823 }
3825 static bool
3826 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3827 {
3828 struct status *file = NULL;
3829 struct status *unmerged = NULL;
3830 char buf[SIZEOF_STR * 4];
3831 size_t bufsize = 0;
3832 FILE *pipe;
3834 pipe = popen(cmd, "r");
3835 if (!pipe)
3836 return FALSE;
3838 add_line_data(view, NULL, type);
3840 while (!feof(pipe) && !ferror(pipe)) {
3841 char *sep;
3842 size_t readsize;
3844 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3845 if (!readsize)
3846 break;
3847 bufsize += readsize;
3849 /* Process while we have NUL chars. */
3850 while ((sep = memchr(buf, 0, bufsize))) {
3851 size_t sepsize = sep - buf + 1;
3853 if (!file) {
3854 if (!realloc_lines(view, view->line_size + 1))
3855 goto error_out;
3857 file = calloc(1, sizeof(*file));
3858 if (!file)
3859 goto error_out;
3861 add_line_data(view, file, type);
3862 }
3864 /* Parse diff info part. */
3865 if (status) {
3866 file->status = status;
3867 if (status == 'A')
3868 string_copy(file->old.rev, NULL_ID);
3870 } else if (!file->status) {
3871 if (!status_get_diff(file, buf, sepsize))
3872 goto error_out;
3874 bufsize -= sepsize;
3875 memmove(buf, sep + 1, bufsize);
3877 sep = memchr(buf, 0, bufsize);
3878 if (!sep)
3879 break;
3880 sepsize = sep - buf + 1;
3882 /* Collapse all 'M'odified entries that
3883 * follow a associated 'U'nmerged entry.
3884 */
3885 if (file->status == 'U') {
3886 unmerged = file;
3888 } else if (unmerged) {
3889 int collapse = !strcmp(buf, unmerged->new.name);
3891 unmerged = NULL;
3892 if (collapse) {
3893 free(file);
3894 view->lines--;
3895 continue;
3896 }
3897 }
3898 }
3900 /* Grab the old name for rename/copy. */
3901 if (!*file->old.name &&
3902 (file->status == 'R' || file->status == 'C')) {
3903 sepsize = sep - buf + 1;
3904 string_ncopy(file->old.name, buf, sepsize);
3905 bufsize -= sepsize;
3906 memmove(buf, sep + 1, bufsize);
3908 sep = memchr(buf, 0, bufsize);
3909 if (!sep)
3910 break;
3911 sepsize = sep - buf + 1;
3912 }
3914 /* git-ls-files just delivers a NUL separated
3915 * list of file names similar to the second half
3916 * of the git-diff-* output. */
3917 string_ncopy(file->new.name, buf, sepsize);
3918 if (!*file->old.name)
3919 string_copy(file->old.name, file->new.name);
3920 bufsize -= sepsize;
3921 memmove(buf, sep + 1, bufsize);
3922 file = NULL;
3923 }
3924 }
3926 if (ferror(pipe)) {
3927 error_out:
3928 pclose(pipe);
3929 return FALSE;
3930 }
3932 if (!view->line[view->lines - 1].data)
3933 add_line_data(view, NULL, LINE_STAT_NONE);
3935 pclose(pipe);
3936 return TRUE;
3937 }
3939 /* Don't show unmerged entries in the staged section. */
3940 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3941 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3942 #define STATUS_LIST_OTHER_CMD \
3943 "git ls-files -z --others --exclude-per-directory=.gitignore"
3944 #define STATUS_LIST_NO_HEAD_CMD \
3945 "git ls-files -z --cached --exclude-per-directory=.gitignore"
3947 #define STATUS_DIFF_INDEX_SHOW_CMD \
3948 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3950 #define STATUS_DIFF_FILES_SHOW_CMD \
3951 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3953 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3954 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3956 /* First parse staged info using git-diff-index(1), then parse unstaged
3957 * info using git-diff-files(1), and finally untracked files using
3958 * git-ls-files(1). */
3959 static bool
3960 status_open(struct view *view)
3961 {
3962 struct stat statbuf;
3963 char exclude[SIZEOF_STR];
3964 char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3965 char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3966 unsigned long prev_lineno = view->lineno;
3967 char indexstatus = 0;
3968 size_t i;
3970 for (i = 0; i < view->lines; i++)
3971 free(view->line[i].data);
3972 free(view->line);
3973 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3974 view->line = NULL;
3976 if (!realloc_lines(view, view->line_size + 7))
3977 return FALSE;
3979 add_line_data(view, NULL, LINE_STAT_HEAD);
3980 if (opt_no_head)
3981 string_copy(status_onbranch, "Initial commit");
3982 else if (!*opt_head)
3983 string_copy(status_onbranch, "Not currently on any branch");
3984 else if (!string_format(status_onbranch, "On branch %s", opt_head))
3985 return FALSE;
3987 if (opt_no_head) {
3988 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
3989 indexstatus = 'A';
3990 }
3992 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3993 return FALSE;
3995 if (stat(exclude, &statbuf) >= 0) {
3996 size_t cmdsize = strlen(othercmd);
3998 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
3999 sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
4000 return FALSE;
4002 cmdsize = strlen(indexcmd);
4003 if (opt_no_head &&
4004 (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
4005 sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
4006 return FALSE;
4007 }
4009 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4011 if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
4012 !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4013 !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
4014 return FALSE;
4016 /* If all went well restore the previous line number to stay in
4017 * the context or select a line with something that can be
4018 * updated. */
4019 if (prev_lineno >= view->lines)
4020 prev_lineno = view->lines - 1;
4021 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4022 prev_lineno++;
4023 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4024 prev_lineno--;
4026 /* If the above fails, always skip the "On branch" line. */
4027 if (prev_lineno < view->lines)
4028 view->lineno = prev_lineno;
4029 else
4030 view->lineno = 1;
4032 if (view->lineno < view->offset)
4033 view->offset = view->lineno;
4034 else if (view->offset + view->height <= view->lineno)
4035 view->offset = view->lineno - view->height + 1;
4037 return TRUE;
4038 }
4040 static bool
4041 status_draw(struct view *view, struct line *line, unsigned int lineno)
4042 {
4043 struct status *status = line->data;
4044 enum line_type type;
4045 char *text;
4047 if (!status) {
4048 switch (line->type) {
4049 case LINE_STAT_STAGED:
4050 type = LINE_STAT_SECTION;
4051 text = "Changes to be committed:";
4052 break;
4054 case LINE_STAT_UNSTAGED:
4055 type = LINE_STAT_SECTION;
4056 text = "Changed but not updated:";
4057 break;
4059 case LINE_STAT_UNTRACKED:
4060 type = LINE_STAT_SECTION;
4061 text = "Untracked files:";
4062 break;
4064 case LINE_STAT_NONE:
4065 type = LINE_DEFAULT;
4066 text = " (no files)";
4067 break;
4069 case LINE_STAT_HEAD:
4070 type = LINE_STAT_HEAD;
4071 text = status_onbranch;
4072 break;
4074 default:
4075 return FALSE;
4076 }
4077 } else {
4078 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4080 buf[0] = status->status;
4081 if (draw_text(view, line->type, buf, TRUE))
4082 return TRUE;
4083 type = LINE_DEFAULT;
4084 text = status->new.name;
4085 }
4087 draw_text(view, type, text, TRUE);
4088 return TRUE;
4089 }
4091 static enum request
4092 status_enter(struct view *view, struct line *line)
4093 {
4094 struct status *status = line->data;
4095 char oldpath[SIZEOF_STR] = "";
4096 char newpath[SIZEOF_STR] = "";
4097 char *info;
4098 size_t cmdsize = 0;
4099 enum open_flags split;
4101 if (line->type == LINE_STAT_NONE ||
4102 (!status && line[1].type == LINE_STAT_NONE)) {
4103 report("No file to diff");
4104 return REQ_NONE;
4105 }
4107 if (status) {
4108 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4109 return REQ_QUIT;
4110 /* Diffs for unmerged entries are empty when pasing the
4111 * new path, so leave it empty. */
4112 if (status->status != 'U' &&
4113 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4114 return REQ_QUIT;
4115 }
4117 if (opt_cdup[0] &&
4118 line->type != LINE_STAT_UNTRACKED &&
4119 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4120 return REQ_QUIT;
4122 switch (line->type) {
4123 case LINE_STAT_STAGED:
4124 if (opt_no_head) {
4125 if (!string_format_from(opt_cmd, &cmdsize,
4126 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4127 newpath))
4128 return REQ_QUIT;
4129 } else {
4130 if (!string_format_from(opt_cmd, &cmdsize,
4131 STATUS_DIFF_INDEX_SHOW_CMD,
4132 oldpath, newpath))
4133 return REQ_QUIT;
4134 }
4136 if (status)
4137 info = "Staged changes to %s";
4138 else
4139 info = "Staged changes";
4140 break;
4142 case LINE_STAT_UNSTAGED:
4143 if (!string_format_from(opt_cmd, &cmdsize,
4144 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4145 return REQ_QUIT;
4146 if (status)
4147 info = "Unstaged changes to %s";
4148 else
4149 info = "Unstaged changes";
4150 break;
4152 case LINE_STAT_UNTRACKED:
4153 if (opt_pipe)
4154 return REQ_QUIT;
4156 if (!status) {
4157 report("No file to show");
4158 return REQ_NONE;
4159 }
4161 opt_pipe = fopen(status->new.name, "r");
4162 info = "Untracked file %s";
4163 break;
4165 case LINE_STAT_HEAD:
4166 return REQ_NONE;
4168 default:
4169 die("line type %d not handled in switch", line->type);
4170 }
4172 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4173 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4174 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4175 if (status) {
4176 stage_status = *status;
4177 } else {
4178 memset(&stage_status, 0, sizeof(stage_status));
4179 }
4181 stage_line_type = line->type;
4182 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4183 }
4185 return REQ_NONE;
4186 }
4188 static bool
4189 status_exists(struct status *status, enum line_type type)
4190 {
4191 struct view *view = VIEW(REQ_VIEW_STATUS);
4192 struct line *line;
4194 for (line = view->line; line < view->line + view->lines; line++) {
4195 struct status *pos = line->data;
4197 if (line->type == type && pos &&
4198 !strcmp(status->new.name, pos->new.name))
4199 return TRUE;
4200 }
4202 return FALSE;
4203 }
4206 static FILE *
4207 status_update_prepare(enum line_type type)
4208 {
4209 char cmd[SIZEOF_STR];
4210 size_t cmdsize = 0;
4212 if (opt_cdup[0] &&
4213 type != LINE_STAT_UNTRACKED &&
4214 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4215 return NULL;
4217 switch (type) {
4218 case LINE_STAT_STAGED:
4219 string_add(cmd, cmdsize, "git update-index -z --index-info");
4220 break;
4222 case LINE_STAT_UNSTAGED:
4223 case LINE_STAT_UNTRACKED:
4224 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4225 break;
4227 default:
4228 die("line type %d not handled in switch", type);
4229 }
4231 return popen(cmd, "w");
4232 }
4234 static bool
4235 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4236 {
4237 char buf[SIZEOF_STR];
4238 size_t bufsize = 0;
4239 size_t written = 0;
4241 switch (type) {
4242 case LINE_STAT_STAGED:
4243 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4244 status->old.mode,
4245 status->old.rev,
4246 status->old.name, 0))
4247 return FALSE;
4248 break;
4250 case LINE_STAT_UNSTAGED:
4251 case LINE_STAT_UNTRACKED:
4252 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4253 return FALSE;
4254 break;
4256 default:
4257 die("line type %d not handled in switch", type);
4258 }
4260 while (!ferror(pipe) && written < bufsize) {
4261 written += fwrite(buf + written, 1, bufsize - written, pipe);
4262 }
4264 return written == bufsize;
4265 }
4267 static bool
4268 status_update_file(struct status *status, enum line_type type)
4269 {
4270 FILE *pipe = status_update_prepare(type);
4271 bool result;
4273 if (!pipe)
4274 return FALSE;
4276 result = status_update_write(pipe, status, type);
4277 pclose(pipe);
4278 return result;
4279 }
4281 static bool
4282 status_update_files(struct view *view, struct line *line)
4283 {
4284 FILE *pipe = status_update_prepare(line->type);
4285 bool result = TRUE;
4286 struct line *pos = view->line + view->lines;
4287 int files = 0;
4288 int file, done;
4290 if (!pipe)
4291 return FALSE;
4293 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4294 files++;
4296 for (file = 0, done = 0; result && file < files; line++, file++) {
4297 int almost_done = file * 100 / files;
4299 if (almost_done > done) {
4300 done = almost_done;
4301 string_format(view->ref, "updating file %u of %u (%d%% done)",
4302 file, files, done);
4303 update_view_title(view);
4304 }
4305 result = status_update_write(pipe, line->data, line->type);
4306 }
4308 pclose(pipe);
4309 return result;
4310 }
4312 static bool
4313 status_update(struct view *view)
4314 {
4315 struct line *line = &view->line[view->lineno];
4317 assert(view->lines);
4319 if (!line->data) {
4320 /* This should work even for the "On branch" line. */
4321 if (line < view->line + view->lines && !line[1].data) {
4322 report("Nothing to update");
4323 return FALSE;
4324 }
4326 if (!status_update_files(view, line + 1)) {
4327 report("Failed to update file status");
4328 return FALSE;
4329 }
4331 } else if (!status_update_file(line->data, line->type)) {
4332 report("Failed to update file status");
4333 return FALSE;
4334 }
4336 return TRUE;
4337 }
4339 static enum request
4340 status_request(struct view *view, enum request request, struct line *line)
4341 {
4342 struct status *status = line->data;
4344 switch (request) {
4345 case REQ_STATUS_UPDATE:
4346 if (!status_update(view))
4347 return REQ_NONE;
4348 break;
4350 case REQ_STATUS_MERGE:
4351 if (!status || status->status != 'U') {
4352 report("Merging only possible for files with unmerged status ('U').");
4353 return REQ_NONE;
4354 }
4355 open_mergetool(status->new.name);
4356 break;
4358 case REQ_EDIT:
4359 if (!status)
4360 return request;
4362 open_editor(status->status != '?', status->new.name);
4363 break;
4365 case REQ_VIEW_BLAME:
4366 if (status) {
4367 string_copy(opt_file, status->new.name);
4368 opt_ref[0] = 0;
4369 }
4370 return request;
4372 case REQ_ENTER:
4373 /* After returning the status view has been split to
4374 * show the stage view. No further reloading is
4375 * necessary. */
4376 status_enter(view, line);
4377 return REQ_NONE;
4379 case REQ_REFRESH:
4380 /* Simply reload the view. */
4381 break;
4383 default:
4384 return request;
4385 }
4387 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4389 return REQ_NONE;
4390 }
4392 static void
4393 status_select(struct view *view, struct line *line)
4394 {
4395 struct status *status = line->data;
4396 char file[SIZEOF_STR] = "all files";
4397 char *text;
4398 char *key;
4400 if (status && !string_format(file, "'%s'", status->new.name))
4401 return;
4403 if (!status && line[1].type == LINE_STAT_NONE)
4404 line++;
4406 switch (line->type) {
4407 case LINE_STAT_STAGED:
4408 text = "Press %s to unstage %s for commit";
4409 break;
4411 case LINE_STAT_UNSTAGED:
4412 text = "Press %s to stage %s for commit";
4413 break;
4415 case LINE_STAT_UNTRACKED:
4416 text = "Press %s to stage %s for addition";
4417 break;
4419 case LINE_STAT_HEAD:
4420 case LINE_STAT_NONE:
4421 text = "Nothing to update";
4422 break;
4424 default:
4425 die("line type %d not handled in switch", line->type);
4426 }
4428 if (status && status->status == 'U') {
4429 text = "Press %s to resolve conflict in %s";
4430 key = get_key(REQ_STATUS_MERGE);
4432 } else {
4433 key = get_key(REQ_STATUS_UPDATE);
4434 }
4436 string_format(view->ref, text, key, file);
4437 }
4439 static bool
4440 status_grep(struct view *view, struct line *line)
4441 {
4442 struct status *status = line->data;
4443 enum { S_STATUS, S_NAME, S_END } state;
4444 char buf[2] = "?";
4445 regmatch_t pmatch;
4447 if (!status)
4448 return FALSE;
4450 for (state = S_STATUS; state < S_END; state++) {
4451 char *text;
4453 switch (state) {
4454 case S_NAME: text = status->new.name; break;
4455 case S_STATUS:
4456 buf[0] = status->status;
4457 text = buf;
4458 break;
4460 default:
4461 return FALSE;
4462 }
4464 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4465 return TRUE;
4466 }
4468 return FALSE;
4469 }
4471 static struct view_ops status_ops = {
4472 "file",
4473 status_open,
4474 NULL,
4475 status_draw,
4476 status_request,
4477 status_grep,
4478 status_select,
4479 };
4482 static bool
4483 stage_diff_line(FILE *pipe, struct line *line)
4484 {
4485 char *buf = line->data;
4486 size_t bufsize = strlen(buf);
4487 size_t written = 0;
4489 while (!ferror(pipe) && written < bufsize) {
4490 written += fwrite(buf + written, 1, bufsize - written, pipe);
4491 }
4493 fputc('\n', pipe);
4495 return written == bufsize;
4496 }
4498 static bool
4499 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4500 {
4501 while (line < end) {
4502 if (!stage_diff_line(pipe, line++))
4503 return FALSE;
4504 if (line->type == LINE_DIFF_CHUNK ||
4505 line->type == LINE_DIFF_HEADER)
4506 break;
4507 }
4509 return TRUE;
4510 }
4512 static struct line *
4513 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4514 {
4515 for (; view->line < line; line--)
4516 if (line->type == type)
4517 return line;
4519 return NULL;
4520 }
4522 static bool
4523 stage_update_chunk(struct view *view, struct line *chunk)
4524 {
4525 char cmd[SIZEOF_STR];
4526 size_t cmdsize = 0;
4527 struct line *diff_hdr;
4528 FILE *pipe;
4530 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4531 if (!diff_hdr)
4532 return FALSE;
4534 if (opt_cdup[0] &&
4535 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4536 return FALSE;
4538 if (!string_format_from(cmd, &cmdsize,
4539 "git apply --whitespace=nowarn --cached %s - && "
4540 "git update-index -q --unmerged --refresh 2>/dev/null",
4541 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4542 return FALSE;
4544 pipe = popen(cmd, "w");
4545 if (!pipe)
4546 return FALSE;
4548 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4549 !stage_diff_write(pipe, chunk, view->line + view->lines))
4550 chunk = NULL;
4552 pclose(pipe);
4554 return chunk ? TRUE : FALSE;
4555 }
4557 static bool
4558 stage_update(struct view *view, struct line *line)
4559 {
4560 struct line *chunk = NULL;
4562 if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4563 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4565 if (chunk) {
4566 if (!stage_update_chunk(view, chunk)) {
4567 report("Failed to apply chunk");
4568 return FALSE;
4569 }
4571 } else if (!stage_status.status) {
4572 view = VIEW(REQ_VIEW_STATUS);
4574 for (line = view->line; line < view->line + view->lines; line++)
4575 if (line->type == stage_line_type)
4576 break;
4578 if (!status_update_files(view, line + 1)) {
4579 report("Failed to update files");
4580 return FALSE;
4581 }
4583 } else if (!status_update_file(&stage_status, stage_line_type)) {
4584 report("Failed to update file");
4585 return FALSE;
4586 }
4588 return TRUE;
4589 }
4591 static enum request
4592 stage_request(struct view *view, enum request request, struct line *line)
4593 {
4594 switch (request) {
4595 case REQ_STATUS_UPDATE:
4596 if (!stage_update(view, line))
4597 return REQ_NONE;
4598 break;
4600 case REQ_EDIT:
4601 if (!stage_status.new.name[0])
4602 return request;
4604 open_editor(stage_status.status != '?', stage_status.new.name);
4605 break;
4607 case REQ_REFRESH:
4608 /* Reload everything ... */
4609 break;
4611 case REQ_VIEW_BLAME:
4612 if (stage_status.new.name[0]) {
4613 string_copy(opt_file, stage_status.new.name);
4614 opt_ref[0] = 0;
4615 }
4616 return request;
4618 case REQ_ENTER:
4619 return pager_request(view, request, line);
4621 default:
4622 return request;
4623 }
4625 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4627 /* Check whether the staged entry still exists, and close the
4628 * stage view if it doesn't. */
4629 if (!status_exists(&stage_status, stage_line_type))
4630 return REQ_VIEW_CLOSE;
4632 if (stage_line_type == LINE_STAT_UNTRACKED)
4633 opt_pipe = fopen(stage_status.new.name, "r");
4634 else
4635 string_copy(opt_cmd, view->cmd);
4636 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4638 return REQ_NONE;
4639 }
4641 static struct view_ops stage_ops = {
4642 "line",
4643 NULL,
4644 pager_read,
4645 pager_draw,
4646 stage_request,
4647 pager_grep,
4648 pager_select,
4649 };
4652 /*
4653 * Revision graph
4654 */
4656 struct commit {
4657 char id[SIZEOF_REV]; /* SHA1 ID. */
4658 char title[128]; /* First line of the commit message. */
4659 char author[75]; /* Author of the commit. */
4660 struct tm time; /* Date from the author ident. */
4661 struct ref **refs; /* Repository references. */
4662 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
4663 size_t graph_size; /* The width of the graph array. */
4664 bool has_parents; /* Rewritten --parents seen. */
4665 };
4667 /* Size of rev graph with no "padding" columns */
4668 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4670 struct rev_graph {
4671 struct rev_graph *prev, *next, *parents;
4672 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4673 size_t size;
4674 struct commit *commit;
4675 size_t pos;
4676 unsigned int boundary:1;
4677 };
4679 /* Parents of the commit being visualized. */
4680 static struct rev_graph graph_parents[4];
4682 /* The current stack of revisions on the graph. */
4683 static struct rev_graph graph_stacks[4] = {
4684 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4685 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4686 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4687 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4688 };
4690 static inline bool
4691 graph_parent_is_merge(struct rev_graph *graph)
4692 {
4693 return graph->parents->size > 1;
4694 }
4696 static inline void
4697 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4698 {
4699 struct commit *commit = graph->commit;
4701 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4702 commit->graph[commit->graph_size++] = symbol;
4703 }
4705 static void
4706 done_rev_graph(struct rev_graph *graph)
4707 {
4708 if (graph_parent_is_merge(graph) &&
4709 graph->pos < graph->size - 1 &&
4710 graph->next->size == graph->size + graph->parents->size - 1) {
4711 size_t i = graph->pos + graph->parents->size - 1;
4713 graph->commit->graph_size = i * 2;
4714 while (i < graph->next->size - 1) {
4715 append_to_rev_graph(graph, ' ');
4716 append_to_rev_graph(graph, '\\');
4717 i++;
4718 }
4719 }
4721 graph->size = graph->pos = 0;
4722 graph->commit = NULL;
4723 memset(graph->parents, 0, sizeof(*graph->parents));
4724 }
4726 static void
4727 push_rev_graph(struct rev_graph *graph, char *parent)
4728 {
4729 int i;
4731 /* "Collapse" duplicate parents lines.
4732 *
4733 * FIXME: This needs to also update update the drawn graph but
4734 * for now it just serves as a method for pruning graph lines. */
4735 for (i = 0; i < graph->size; i++)
4736 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4737 return;
4739 if (graph->size < SIZEOF_REVITEMS) {
4740 string_copy_rev(graph->rev[graph->size++], parent);
4741 }
4742 }
4744 static chtype
4745 get_rev_graph_symbol(struct rev_graph *graph)
4746 {
4747 chtype symbol;
4749 if (graph->boundary)
4750 symbol = REVGRAPH_BOUND;
4751 else if (graph->parents->size == 0)
4752 symbol = REVGRAPH_INIT;
4753 else if (graph_parent_is_merge(graph))
4754 symbol = REVGRAPH_MERGE;
4755 else if (graph->pos >= graph->size)
4756 symbol = REVGRAPH_BRANCH;
4757 else
4758 symbol = REVGRAPH_COMMIT;
4760 return symbol;
4761 }
4763 static void
4764 draw_rev_graph(struct rev_graph *graph)
4765 {
4766 struct rev_filler {
4767 chtype separator, line;
4768 };
4769 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4770 static struct rev_filler fillers[] = {
4771 { ' ', '|' },
4772 { '`', '.' },
4773 { '\'', ' ' },
4774 { '/', ' ' },
4775 };
4776 chtype symbol = get_rev_graph_symbol(graph);
4777 struct rev_filler *filler;
4778 size_t i;
4780 if (opt_line_graphics)
4781 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4783 filler = &fillers[DEFAULT];
4785 for (i = 0; i < graph->pos; i++) {
4786 append_to_rev_graph(graph, filler->line);
4787 if (graph_parent_is_merge(graph->prev) &&
4788 graph->prev->pos == i)
4789 filler = &fillers[RSHARP];
4791 append_to_rev_graph(graph, filler->separator);
4792 }
4794 /* Place the symbol for this revision. */
4795 append_to_rev_graph(graph, symbol);
4797 if (graph->prev->size > graph->size)
4798 filler = &fillers[RDIAG];
4799 else
4800 filler = &fillers[DEFAULT];
4802 i++;
4804 for (; i < graph->size; i++) {
4805 append_to_rev_graph(graph, filler->separator);
4806 append_to_rev_graph(graph, filler->line);
4807 if (graph_parent_is_merge(graph->prev) &&
4808 i < graph->prev->pos + graph->parents->size)
4809 filler = &fillers[RSHARP];
4810 if (graph->prev->size > graph->size)
4811 filler = &fillers[LDIAG];
4812 }
4814 if (graph->prev->size > graph->size) {
4815 append_to_rev_graph(graph, filler->separator);
4816 if (filler->line != ' ')
4817 append_to_rev_graph(graph, filler->line);
4818 }
4819 }
4821 /* Prepare the next rev graph */
4822 static void
4823 prepare_rev_graph(struct rev_graph *graph)
4824 {
4825 size_t i;
4827 /* First, traverse all lines of revisions up to the active one. */
4828 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4829 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4830 break;
4832 push_rev_graph(graph->next, graph->rev[graph->pos]);
4833 }
4835 /* Interleave the new revision parent(s). */
4836 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4837 push_rev_graph(graph->next, graph->parents->rev[i]);
4839 /* Lastly, put any remaining revisions. */
4840 for (i = graph->pos + 1; i < graph->size; i++)
4841 push_rev_graph(graph->next, graph->rev[i]);
4842 }
4844 static void
4845 update_rev_graph(struct rev_graph *graph)
4846 {
4847 /* If this is the finalizing update ... */
4848 if (graph->commit)
4849 prepare_rev_graph(graph);
4851 /* Graph visualization needs a one rev look-ahead,
4852 * so the first update doesn't visualize anything. */
4853 if (!graph->prev->commit)
4854 return;
4856 draw_rev_graph(graph->prev);
4857 done_rev_graph(graph->prev->prev);
4858 }
4861 /*
4862 * Main view backend
4863 */
4865 static bool
4866 main_draw(struct view *view, struct line *line, unsigned int lineno)
4867 {
4868 struct commit *commit = line->data;
4870 if (!*commit->author)
4871 return FALSE;
4873 if (opt_date && draw_date(view, &commit->time))
4874 return TRUE;
4876 if (opt_author &&
4877 draw_field(view, LINE_MAIN_AUTHOR, commit->author, AUTHOR_COLS, TRUE))
4878 return TRUE;
4880 if (opt_rev_graph && commit->graph_size &&
4881 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
4882 return TRUE;
4884 if (opt_show_refs && commit->refs) {
4885 size_t i = 0;
4887 do {
4888 enum line_type type;
4890 if (commit->refs[i]->head)
4891 type = LINE_MAIN_HEAD;
4892 else if (commit->refs[i]->ltag)
4893 type = LINE_MAIN_LOCAL_TAG;
4894 else if (commit->refs[i]->tag)
4895 type = LINE_MAIN_TAG;
4896 else if (commit->refs[i]->tracked)
4897 type = LINE_MAIN_TRACKED;
4898 else if (commit->refs[i]->remote)
4899 type = LINE_MAIN_REMOTE;
4900 else
4901 type = LINE_MAIN_REF;
4903 if (draw_text(view, type, "[", TRUE) ||
4904 draw_text(view, type, commit->refs[i]->name, TRUE) ||
4905 draw_text(view, type, "]", TRUE))
4906 return TRUE;
4908 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
4909 return TRUE;
4910 } while (commit->refs[i++]->next);
4911 }
4913 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
4914 return TRUE;
4915 }
4917 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4918 static bool
4919 main_read(struct view *view, char *line)
4920 {
4921 static struct rev_graph *graph = graph_stacks;
4922 enum line_type type;
4923 struct commit *commit;
4925 if (!line) {
4926 if (!view->lines && !view->parent)
4927 die("No revisions match the given arguments.");
4928 update_rev_graph(graph);
4929 return TRUE;
4930 }
4932 type = get_line_type(line);
4933 if (type == LINE_COMMIT) {
4934 commit = calloc(1, sizeof(struct commit));
4935 if (!commit)
4936 return FALSE;
4938 line += STRING_SIZE("commit ");
4939 if (*line == '-') {
4940 graph->boundary = 1;
4941 line++;
4942 }
4944 string_copy_rev(commit->id, line);
4945 commit->refs = get_refs(commit->id);
4946 graph->commit = commit;
4947 add_line_data(view, commit, LINE_MAIN_COMMIT);
4949 while ((line = strchr(line, ' '))) {
4950 line++;
4951 push_rev_graph(graph->parents, line);
4952 commit->has_parents = TRUE;
4953 }
4954 return TRUE;
4955 }
4957 if (!view->lines)
4958 return TRUE;
4959 commit = view->line[view->lines - 1].data;
4961 switch (type) {
4962 case LINE_PARENT:
4963 if (commit->has_parents)
4964 break;
4965 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4966 break;
4968 case LINE_AUTHOR:
4969 {
4970 /* Parse author lines where the name may be empty:
4971 * author <email@address.tld> 1138474660 +0100
4972 */
4973 char *ident = line + STRING_SIZE("author ");
4974 char *nameend = strchr(ident, '<');
4975 char *emailend = strchr(ident, '>');
4977 if (!nameend || !emailend)
4978 break;
4980 update_rev_graph(graph);
4981 graph = graph->next;
4983 *nameend = *emailend = 0;
4984 ident = chomp_string(ident);
4985 if (!*ident) {
4986 ident = chomp_string(nameend + 1);
4987 if (!*ident)
4988 ident = "Unknown";
4989 }
4991 string_ncopy(commit->author, ident, strlen(ident));
4993 /* Parse epoch and timezone */
4994 if (emailend[1] == ' ') {
4995 char *secs = emailend + 2;
4996 char *zone = strchr(secs, ' ');
4997 time_t time = (time_t) atol(secs);
4999 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5000 long tz;
5002 zone++;
5003 tz = ('0' - zone[1]) * 60 * 60 * 10;
5004 tz += ('0' - zone[2]) * 60 * 60;
5005 tz += ('0' - zone[3]) * 60;
5006 tz += ('0' - zone[4]) * 60;
5008 if (zone[0] == '-')
5009 tz = -tz;
5011 time -= tz;
5012 }
5014 gmtime_r(&time, &commit->time);
5015 }
5016 break;
5017 }
5018 default:
5019 /* Fill in the commit title if it has not already been set. */
5020 if (commit->title[0])
5021 break;
5023 /* Require titles to start with a non-space character at the
5024 * offset used by git log. */
5025 if (strncmp(line, " ", 4))
5026 break;
5027 line += 4;
5028 /* Well, if the title starts with a whitespace character,
5029 * try to be forgiving. Otherwise we end up with no title. */
5030 while (isspace(*line))
5031 line++;
5032 if (*line == '\0')
5033 break;
5034 /* FIXME: More graceful handling of titles; append "..." to
5035 * shortened titles, etc. */
5037 string_ncopy(commit->title, line, strlen(line));
5038 }
5040 return TRUE;
5041 }
5043 static enum request
5044 main_request(struct view *view, enum request request, struct line *line)
5045 {
5046 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5048 if (request == REQ_ENTER)
5049 open_view(view, REQ_VIEW_DIFF, flags);
5050 else
5051 return request;
5053 return REQ_NONE;
5054 }
5056 static bool
5057 grep_refs(struct ref **refs, regex_t *regex)
5058 {
5059 regmatch_t pmatch;
5060 size_t i = 0;
5062 if (!refs)
5063 return FALSE;
5064 do {
5065 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5066 return TRUE;
5067 } while (refs[i++]->next);
5069 return FALSE;
5070 }
5072 static bool
5073 main_grep(struct view *view, struct line *line)
5074 {
5075 struct commit *commit = line->data;
5076 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5077 char buf[DATE_COLS + 1];
5078 regmatch_t pmatch;
5080 for (state = S_TITLE; state < S_END; state++) {
5081 char *text;
5083 switch (state) {
5084 case S_TITLE: text = commit->title; break;
5085 case S_AUTHOR:
5086 if (!opt_author)
5087 continue;
5088 text = commit->author;
5089 break;
5090 case S_DATE:
5091 if (!opt_date)
5092 continue;
5093 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5094 continue;
5095 text = buf;
5096 break;
5097 case S_REFS:
5098 if (!opt_show_refs)
5099 continue;
5100 if (grep_refs(commit->refs, view->regex) == TRUE)
5101 return TRUE;
5102 continue;
5103 default:
5104 return FALSE;
5105 }
5107 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5108 return TRUE;
5109 }
5111 return FALSE;
5112 }
5114 static void
5115 main_select(struct view *view, struct line *line)
5116 {
5117 struct commit *commit = line->data;
5119 string_copy_rev(view->ref, commit->id);
5120 string_copy_rev(ref_commit, view->ref);
5121 }
5123 static struct view_ops main_ops = {
5124 "commit",
5125 NULL,
5126 main_read,
5127 main_draw,
5128 main_request,
5129 main_grep,
5130 main_select,
5131 };
5134 /*
5135 * Unicode / UTF-8 handling
5136 *
5137 * NOTE: Much of the following code for dealing with unicode is derived from
5138 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5139 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5140 */
5142 /* I've (over)annotated a lot of code snippets because I am not entirely
5143 * confident that the approach taken by this small UTF-8 interface is correct.
5144 * --jonas */
5146 static inline int
5147 unicode_width(unsigned long c)
5148 {
5149 if (c >= 0x1100 &&
5150 (c <= 0x115f /* Hangul Jamo */
5151 || c == 0x2329
5152 || c == 0x232a
5153 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5154 /* CJK ... Yi */
5155 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5156 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5157 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5158 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5159 || (c >= 0xffe0 && c <= 0xffe6)
5160 || (c >= 0x20000 && c <= 0x2fffd)
5161 || (c >= 0x30000 && c <= 0x3fffd)))
5162 return 2;
5164 if (c == '\t')
5165 return opt_tab_size;
5167 return 1;
5168 }
5170 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5171 * Illegal bytes are set one. */
5172 static const unsigned char utf8_bytes[256] = {
5173 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,
5174 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,
5175 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,
5176 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,
5177 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,
5178 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,
5179 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,
5180 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,
5181 };
5183 /* Decode UTF-8 multi-byte representation into a unicode character. */
5184 static inline unsigned long
5185 utf8_to_unicode(const char *string, size_t length)
5186 {
5187 unsigned long unicode;
5189 switch (length) {
5190 case 1:
5191 unicode = string[0];
5192 break;
5193 case 2:
5194 unicode = (string[0] & 0x1f) << 6;
5195 unicode += (string[1] & 0x3f);
5196 break;
5197 case 3:
5198 unicode = (string[0] & 0x0f) << 12;
5199 unicode += ((string[1] & 0x3f) << 6);
5200 unicode += (string[2] & 0x3f);
5201 break;
5202 case 4:
5203 unicode = (string[0] & 0x0f) << 18;
5204 unicode += ((string[1] & 0x3f) << 12);
5205 unicode += ((string[2] & 0x3f) << 6);
5206 unicode += (string[3] & 0x3f);
5207 break;
5208 case 5:
5209 unicode = (string[0] & 0x0f) << 24;
5210 unicode += ((string[1] & 0x3f) << 18);
5211 unicode += ((string[2] & 0x3f) << 12);
5212 unicode += ((string[3] & 0x3f) << 6);
5213 unicode += (string[4] & 0x3f);
5214 break;
5215 case 6:
5216 unicode = (string[0] & 0x01) << 30;
5217 unicode += ((string[1] & 0x3f) << 24);
5218 unicode += ((string[2] & 0x3f) << 18);
5219 unicode += ((string[3] & 0x3f) << 12);
5220 unicode += ((string[4] & 0x3f) << 6);
5221 unicode += (string[5] & 0x3f);
5222 break;
5223 default:
5224 die("Invalid unicode length");
5225 }
5227 /* Invalid characters could return the special 0xfffd value but NUL
5228 * should be just as good. */
5229 return unicode > 0xffff ? 0 : unicode;
5230 }
5232 /* Calculates how much of string can be shown within the given maximum width
5233 * and sets trimmed parameter to non-zero value if all of string could not be
5234 * shown. If the reserve flag is TRUE, it will reserve at least one
5235 * trailing character, which can be useful when drawing a delimiter.
5236 *
5237 * Returns the number of bytes to output from string to satisfy max_width. */
5238 static size_t
5239 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5240 {
5241 const char *start = string;
5242 const char *end = strchr(string, '\0');
5243 unsigned char last_bytes = 0;
5244 size_t last_ucwidth = 0;
5246 *width = 0;
5247 *trimmed = 0;
5249 while (string < end) {
5250 int c = *(unsigned char *) string;
5251 unsigned char bytes = utf8_bytes[c];
5252 size_t ucwidth;
5253 unsigned long unicode;
5255 if (string + bytes > end)
5256 break;
5258 /* Change representation to figure out whether
5259 * it is a single- or double-width character. */
5261 unicode = utf8_to_unicode(string, bytes);
5262 /* FIXME: Graceful handling of invalid unicode character. */
5263 if (!unicode)
5264 break;
5266 ucwidth = unicode_width(unicode);
5267 *width += ucwidth;
5268 if (*width > max_width) {
5269 *trimmed = 1;
5270 *width -= ucwidth;
5271 if (reserve && *width == max_width) {
5272 string -= last_bytes;
5273 *width -= last_ucwidth;
5274 }
5275 break;
5276 }
5278 string += bytes;
5279 last_bytes = bytes;
5280 last_ucwidth = ucwidth;
5281 }
5283 return string - start;
5284 }
5287 /*
5288 * Status management
5289 */
5291 /* Whether or not the curses interface has been initialized. */
5292 static bool cursed = FALSE;
5294 /* The status window is used for polling keystrokes. */
5295 static WINDOW *status_win;
5297 static bool status_empty = TRUE;
5299 /* Update status and title window. */
5300 static void
5301 report(const char *msg, ...)
5302 {
5303 struct view *view = display[current_view];
5305 if (input_mode)
5306 return;
5308 if (!view) {
5309 char buf[SIZEOF_STR];
5310 va_list args;
5312 va_start(args, msg);
5313 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5314 buf[sizeof(buf) - 1] = 0;
5315 buf[sizeof(buf) - 2] = '.';
5316 buf[sizeof(buf) - 3] = '.';
5317 buf[sizeof(buf) - 4] = '.';
5318 }
5319 va_end(args);
5320 die("%s", buf);
5321 }
5323 if (!status_empty || *msg) {
5324 va_list args;
5326 va_start(args, msg);
5328 wmove(status_win, 0, 0);
5329 if (*msg) {
5330 vwprintw(status_win, msg, args);
5331 status_empty = FALSE;
5332 } else {
5333 status_empty = TRUE;
5334 }
5335 wclrtoeol(status_win);
5336 wrefresh(status_win);
5338 va_end(args);
5339 }
5341 update_view_title(view);
5342 update_display_cursor(view);
5343 }
5345 /* Controls when nodelay should be in effect when polling user input. */
5346 static void
5347 set_nonblocking_input(bool loading)
5348 {
5349 static unsigned int loading_views;
5351 if ((loading == FALSE && loading_views-- == 1) ||
5352 (loading == TRUE && loading_views++ == 0))
5353 nodelay(status_win, loading);
5354 }
5356 static void
5357 init_display(void)
5358 {
5359 int x, y;
5361 /* Initialize the curses library */
5362 if (isatty(STDIN_FILENO)) {
5363 cursed = !!initscr();
5364 } else {
5365 /* Leave stdin and stdout alone when acting as a pager. */
5366 FILE *io = fopen("/dev/tty", "r+");
5368 if (!io)
5369 die("Failed to open /dev/tty");
5370 cursed = !!newterm(NULL, io, io);
5371 }
5373 if (!cursed)
5374 die("Failed to initialize curses");
5376 nonl(); /* Tell curses not to do NL->CR/NL on output */
5377 cbreak(); /* Take input chars one at a time, no wait for \n */
5378 noecho(); /* Don't echo input */
5379 leaveok(stdscr, TRUE);
5381 if (has_colors())
5382 init_colors();
5384 getmaxyx(stdscr, y, x);
5385 status_win = newwin(1, 0, y - 1, 0);
5386 if (!status_win)
5387 die("Failed to create status window");
5389 /* Enable keyboard mapping */
5390 keypad(status_win, TRUE);
5391 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5393 TABSIZE = opt_tab_size;
5394 if (opt_line_graphics) {
5395 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5396 }
5397 }
5399 static char *
5400 read_prompt(const char *prompt)
5401 {
5402 enum { READING, STOP, CANCEL } status = READING;
5403 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5404 int pos = 0;
5406 while (status == READING) {
5407 struct view *view;
5408 int i, key;
5410 input_mode = TRUE;
5412 foreach_view (view, i)
5413 update_view(view);
5415 input_mode = FALSE;
5417 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5418 wclrtoeol(status_win);
5420 /* Refresh, accept single keystroke of input */
5421 key = wgetch(status_win);
5422 switch (key) {
5423 case KEY_RETURN:
5424 case KEY_ENTER:
5425 case '\n':
5426 status = pos ? STOP : CANCEL;
5427 break;
5429 case KEY_BACKSPACE:
5430 if (pos > 0)
5431 pos--;
5432 else
5433 status = CANCEL;
5434 break;
5436 case KEY_ESC:
5437 status = CANCEL;
5438 break;
5440 case ERR:
5441 break;
5443 default:
5444 if (pos >= sizeof(buf)) {
5445 report("Input string too long");
5446 return NULL;
5447 }
5449 if (isprint(key))
5450 buf[pos++] = (char) key;
5451 }
5452 }
5454 /* Clear the status window */
5455 status_empty = FALSE;
5456 report("");
5458 if (status == CANCEL)
5459 return NULL;
5461 buf[pos++] = 0;
5463 return buf;
5464 }
5466 /*
5467 * Repository references
5468 */
5470 static struct ref *refs = NULL;
5471 static size_t refs_alloc = 0;
5472 static size_t refs_size = 0;
5474 /* Id <-> ref store */
5475 static struct ref ***id_refs = NULL;
5476 static size_t id_refs_alloc = 0;
5477 static size_t id_refs_size = 0;
5479 static struct ref **
5480 get_refs(char *id)
5481 {
5482 struct ref ***tmp_id_refs;
5483 struct ref **ref_list = NULL;
5484 size_t ref_list_alloc = 0;
5485 size_t ref_list_size = 0;
5486 size_t i;
5488 for (i = 0; i < id_refs_size; i++)
5489 if (!strcmp(id, id_refs[i][0]->id))
5490 return id_refs[i];
5492 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5493 sizeof(*id_refs));
5494 if (!tmp_id_refs)
5495 return NULL;
5497 id_refs = tmp_id_refs;
5499 for (i = 0; i < refs_size; i++) {
5500 struct ref **tmp;
5502 if (strcmp(id, refs[i].id))
5503 continue;
5505 tmp = realloc_items(ref_list, &ref_list_alloc,
5506 ref_list_size + 1, sizeof(*ref_list));
5507 if (!tmp) {
5508 if (ref_list)
5509 free(ref_list);
5510 return NULL;
5511 }
5513 ref_list = tmp;
5514 if (ref_list_size > 0)
5515 ref_list[ref_list_size - 1]->next = 1;
5516 ref_list[ref_list_size] = &refs[i];
5518 /* XXX: The properties of the commit chains ensures that we can
5519 * safely modify the shared ref. The repo references will
5520 * always be similar for the same id. */
5521 ref_list[ref_list_size]->next = 0;
5522 ref_list_size++;
5523 }
5525 if (ref_list)
5526 id_refs[id_refs_size++] = ref_list;
5528 return ref_list;
5529 }
5531 static int
5532 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5533 {
5534 struct ref *ref;
5535 bool tag = FALSE;
5536 bool ltag = FALSE;
5537 bool remote = FALSE;
5538 bool tracked = FALSE;
5539 bool check_replace = FALSE;
5540 bool head = FALSE;
5542 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5543 if (!strcmp(name + namelen - 3, "^{}")) {
5544 namelen -= 3;
5545 name[namelen] = 0;
5546 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5547 check_replace = TRUE;
5548 } else {
5549 ltag = TRUE;
5550 }
5552 tag = TRUE;
5553 namelen -= STRING_SIZE("refs/tags/");
5554 name += STRING_SIZE("refs/tags/");
5556 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5557 remote = TRUE;
5558 namelen -= STRING_SIZE("refs/remotes/");
5559 name += STRING_SIZE("refs/remotes/");
5560 tracked = !strcmp(opt_remote, name);
5562 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5563 namelen -= STRING_SIZE("refs/heads/");
5564 name += STRING_SIZE("refs/heads/");
5565 head = !strncmp(opt_head, name, namelen);
5567 } else if (!strcmp(name, "HEAD")) {
5568 opt_no_head = FALSE;
5569 return OK;
5570 }
5572 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5573 /* it's an annotated tag, replace the previous sha1 with the
5574 * resolved commit id; relies on the fact git-ls-remote lists
5575 * the commit id of an annotated tag right beofre the commit id
5576 * it points to. */
5577 refs[refs_size - 1].ltag = ltag;
5578 string_copy_rev(refs[refs_size - 1].id, id);
5580 return OK;
5581 }
5582 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5583 if (!refs)
5584 return ERR;
5586 ref = &refs[refs_size++];
5587 ref->name = malloc(namelen + 1);
5588 if (!ref->name)
5589 return ERR;
5591 strncpy(ref->name, name, namelen);
5592 ref->name[namelen] = 0;
5593 ref->head = head;
5594 ref->tag = tag;
5595 ref->ltag = ltag;
5596 ref->remote = remote;
5597 ref->tracked = tracked;
5598 string_copy_rev(ref->id, id);
5600 return OK;
5601 }
5603 static int
5604 load_refs(void)
5605 {
5606 const char *cmd_env = getenv("TIG_LS_REMOTE");
5607 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5609 return read_properties(popen(cmd, "r"), "\t", read_ref);
5610 }
5612 static int
5613 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5614 {
5615 if (!strcmp(name, "i18n.commitencoding"))
5616 string_ncopy(opt_encoding, value, valuelen);
5618 if (!strcmp(name, "core.editor"))
5619 string_ncopy(opt_editor, value, valuelen);
5621 /* branch.<head>.remote */
5622 if (*opt_head &&
5623 !strncmp(name, "branch.", 7) &&
5624 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5625 !strcmp(name + 7 + strlen(opt_head), ".remote"))
5626 string_ncopy(opt_remote, value, valuelen);
5628 if (*opt_head && *opt_remote &&
5629 !strncmp(name, "branch.", 7) &&
5630 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5631 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5632 size_t from = strlen(opt_remote);
5634 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5635 value += STRING_SIZE("refs/heads/");
5636 valuelen -= STRING_SIZE("refs/heads/");
5637 }
5639 if (!string_format_from(opt_remote, &from, "/%s", value))
5640 opt_remote[0] = 0;
5641 }
5643 return OK;
5644 }
5646 static int
5647 load_git_config(void)
5648 {
5649 return read_properties(popen(GIT_CONFIG " --list", "r"),
5650 "=", read_repo_config_option);
5651 }
5653 static int
5654 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5655 {
5656 if (!opt_git_dir[0]) {
5657 string_ncopy(opt_git_dir, name, namelen);
5659 } else if (opt_is_inside_work_tree == -1) {
5660 /* This can be 3 different values depending on the
5661 * version of git being used. If git-rev-parse does not
5662 * understand --is-inside-work-tree it will simply echo
5663 * the option else either "true" or "false" is printed.
5664 * Default to true for the unknown case. */
5665 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5667 } else if (opt_cdup[0] == ' ') {
5668 string_ncopy(opt_cdup, name, namelen);
5669 } else {
5670 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5671 namelen -= STRING_SIZE("refs/heads/");
5672 name += STRING_SIZE("refs/heads/");
5673 string_ncopy(opt_head, name, namelen);
5674 }
5675 }
5677 return OK;
5678 }
5680 static int
5681 load_repo_info(void)
5682 {
5683 int result;
5684 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5685 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5687 /* XXX: The line outputted by "--show-cdup" can be empty so
5688 * initialize it to something invalid to make it possible to
5689 * detect whether it has been set or not. */
5690 opt_cdup[0] = ' ';
5692 result = read_properties(pipe, "=", read_repo_info);
5693 if (opt_cdup[0] == ' ')
5694 opt_cdup[0] = 0;
5696 return result;
5697 }
5699 static int
5700 read_properties(FILE *pipe, const char *separators,
5701 int (*read_property)(char *, size_t, char *, size_t))
5702 {
5703 char buffer[BUFSIZ];
5704 char *name;
5705 int state = OK;
5707 if (!pipe)
5708 return ERR;
5710 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5711 char *value;
5712 size_t namelen;
5713 size_t valuelen;
5715 name = chomp_string(name);
5716 namelen = strcspn(name, separators);
5718 if (name[namelen]) {
5719 name[namelen] = 0;
5720 value = chomp_string(name + namelen + 1);
5721 valuelen = strlen(value);
5723 } else {
5724 value = "";
5725 valuelen = 0;
5726 }
5728 state = read_property(name, namelen, value, valuelen);
5729 }
5731 if (state != ERR && ferror(pipe))
5732 state = ERR;
5734 pclose(pipe);
5736 return state;
5737 }
5740 /*
5741 * Main
5742 */
5744 static void __NORETURN
5745 quit(int sig)
5746 {
5747 /* XXX: Restore tty modes and let the OS cleanup the rest! */
5748 if (cursed)
5749 endwin();
5750 exit(0);
5751 }
5753 static void __NORETURN
5754 die(const char *err, ...)
5755 {
5756 va_list args;
5758 endwin();
5760 va_start(args, err);
5761 fputs("tig: ", stderr);
5762 vfprintf(stderr, err, args);
5763 fputs("\n", stderr);
5764 va_end(args);
5766 exit(1);
5767 }
5769 static void
5770 warn(const char *msg, ...)
5771 {
5772 va_list args;
5774 va_start(args, msg);
5775 fputs("tig warning: ", stderr);
5776 vfprintf(stderr, msg, args);
5777 fputs("\n", stderr);
5778 va_end(args);
5779 }
5781 int
5782 main(int argc, char *argv[])
5783 {
5784 struct view *view;
5785 enum request request;
5786 size_t i;
5788 signal(SIGINT, quit);
5790 if (setlocale(LC_ALL, "")) {
5791 char *codeset = nl_langinfo(CODESET);
5793 string_ncopy(opt_codeset, codeset, strlen(codeset));
5794 }
5796 if (load_repo_info() == ERR)
5797 die("Failed to load repo info.");
5799 if (load_options() == ERR)
5800 die("Failed to load user config.");
5802 if (load_git_config() == ERR)
5803 die("Failed to load repo config.");
5805 if (!parse_options(argc, argv))
5806 return 0;
5808 /* Require a git repository unless when running in pager mode. */
5809 if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5810 die("Not a git repository");
5812 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5813 opt_utf8 = FALSE;
5815 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5816 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5817 if (opt_iconv == ICONV_NONE)
5818 die("Failed to initialize character set conversion");
5819 }
5821 if (*opt_git_dir && load_refs() == ERR)
5822 die("Failed to load refs.");
5824 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5825 view->cmd_env = getenv(view->cmd_env);
5827 request = opt_request;
5829 init_display();
5831 while (view_driver(display[current_view], request)) {
5832 int key;
5833 int i;
5835 foreach_view (view, i)
5836 update_view(view);
5838 /* Refresh, accept single keystroke of input */
5839 key = wgetch(status_win);
5841 /* wgetch() with nodelay() enabled returns ERR when there's no
5842 * input. */
5843 if (key == ERR) {
5844 request = REQ_NONE;
5845 continue;
5846 }
5848 request = get_keybinding(display[current_view]->keymap, key);
5850 /* Some low-level request handling. This keeps access to
5851 * status_win restricted. */
5852 switch (request) {
5853 case REQ_PROMPT:
5854 {
5855 char *cmd = read_prompt(":");
5857 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5858 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5859 opt_request = REQ_VIEW_DIFF;
5860 } else {
5861 opt_request = REQ_VIEW_PAGER;
5862 }
5863 break;
5864 }
5866 request = REQ_NONE;
5867 break;
5868 }
5869 case REQ_SEARCH:
5870 case REQ_SEARCH_BACK:
5871 {
5872 const char *prompt = request == REQ_SEARCH
5873 ? "/" : "?";
5874 char *search = read_prompt(prompt);
5876 if (search)
5877 string_ncopy(opt_search, search, strlen(search));
5878 else
5879 request = REQ_NONE;
5880 break;
5881 }
5882 case REQ_SCREEN_RESIZE:
5883 {
5884 int height, width;
5886 getmaxyx(stdscr, height, width);
5888 /* Resize the status view and let the view driver take
5889 * care of resizing the displayed views. */
5890 wresize(status_win, 1, width);
5891 mvwin(status_win, height - 1, 0);
5892 wrefresh(status_win);
5893 break;
5894 }
5895 default:
5896 break;
5897 }
5898 }
5900 quit(0);
5902 return 0;
5903 }