9e4d7938adc51e73c4e2f56150c29e4bc11750ce
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 *tmp;
960 struct run_request req = { keymap, key };
961 size_t bufpos;
963 for (bufpos = 0; argc > 0; argc--, argv++)
964 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
965 return REQ_NONE;
967 req.cmd[bufpos - 1] = 0;
969 tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
970 if (!tmp)
971 return REQ_NONE;
973 run_request = tmp;
974 run_request[run_requests++] = req;
976 return REQ_NONE + run_requests;
977 }
979 static struct run_request *
980 get_run_request(enum request request)
981 {
982 if (request <= REQ_NONE)
983 return NULL;
984 return &run_request[request - REQ_NONE - 1];
985 }
987 static void
988 add_builtin_run_requests(void)
989 {
990 struct {
991 enum keymap keymap;
992 int key;
993 char *argv[1];
994 } reqs[] = {
995 { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
996 { KEYMAP_GENERIC, 'G', { "git gc" } },
997 };
998 int i;
1000 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1001 enum request req;
1003 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1004 if (req != REQ_NONE)
1005 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1006 }
1007 }
1009 /*
1010 * User config file handling.
1011 */
1013 static struct int_map color_map[] = {
1014 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1015 COLOR_MAP(DEFAULT),
1016 COLOR_MAP(BLACK),
1017 COLOR_MAP(BLUE),
1018 COLOR_MAP(CYAN),
1019 COLOR_MAP(GREEN),
1020 COLOR_MAP(MAGENTA),
1021 COLOR_MAP(RED),
1022 COLOR_MAP(WHITE),
1023 COLOR_MAP(YELLOW),
1024 };
1026 #define set_color(color, name) \
1027 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1029 static struct int_map attr_map[] = {
1030 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1031 ATTR_MAP(NORMAL),
1032 ATTR_MAP(BLINK),
1033 ATTR_MAP(BOLD),
1034 ATTR_MAP(DIM),
1035 ATTR_MAP(REVERSE),
1036 ATTR_MAP(STANDOUT),
1037 ATTR_MAP(UNDERLINE),
1038 };
1040 #define set_attribute(attr, name) \
1041 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1043 static int config_lineno;
1044 static bool config_errors;
1045 static char *config_msg;
1047 /* Wants: object fgcolor bgcolor [attr] */
1048 static int
1049 option_color_command(int argc, char *argv[])
1050 {
1051 struct line_info *info;
1053 if (argc != 3 && argc != 4) {
1054 config_msg = "Wrong number of arguments given to color command";
1055 return ERR;
1056 }
1058 info = get_line_info(argv[0]);
1059 if (!info) {
1060 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1061 info = get_line_info("delimiter");
1063 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1064 info = get_line_info("date");
1066 } else {
1067 config_msg = "Unknown color name";
1068 return ERR;
1069 }
1070 }
1072 if (set_color(&info->fg, argv[1]) == ERR ||
1073 set_color(&info->bg, argv[2]) == ERR) {
1074 config_msg = "Unknown color";
1075 return ERR;
1076 }
1078 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1079 config_msg = "Unknown attribute";
1080 return ERR;
1081 }
1083 return OK;
1084 }
1086 static bool parse_bool(const char *s)
1087 {
1088 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1089 !strcmp(s, "yes")) ? TRUE : FALSE;
1090 }
1092 /* Wants: name = value */
1093 static int
1094 option_set_command(int argc, char *argv[])
1095 {
1096 if (argc != 3) {
1097 config_msg = "Wrong number of arguments given to set command";
1098 return ERR;
1099 }
1101 if (strcmp(argv[1], "=")) {
1102 config_msg = "No value assigned";
1103 return ERR;
1104 }
1106 if (!strcmp(argv[0], "show-author")) {
1107 opt_author = parse_bool(argv[2]);
1108 return OK;
1109 }
1111 if (!strcmp(argv[0], "show-date")) {
1112 opt_date = parse_bool(argv[2]);
1113 return OK;
1114 }
1116 if (!strcmp(argv[0], "show-rev-graph")) {
1117 opt_rev_graph = parse_bool(argv[2]);
1118 return OK;
1119 }
1121 if (!strcmp(argv[0], "show-refs")) {
1122 opt_show_refs = parse_bool(argv[2]);
1123 return OK;
1124 }
1126 if (!strcmp(argv[0], "show-line-numbers")) {
1127 opt_line_number = parse_bool(argv[2]);
1128 return OK;
1129 }
1131 if (!strcmp(argv[0], "line-graphics")) {
1132 opt_line_graphics = parse_bool(argv[2]);
1133 return OK;
1134 }
1136 if (!strcmp(argv[0], "line-number-interval")) {
1137 opt_num_interval = atoi(argv[2]);
1138 return OK;
1139 }
1141 if (!strcmp(argv[0], "tab-size")) {
1142 opt_tab_size = atoi(argv[2]);
1143 return OK;
1144 }
1146 if (!strcmp(argv[0], "commit-encoding")) {
1147 char *arg = argv[2];
1148 int delimiter = *arg;
1149 int i;
1151 switch (delimiter) {
1152 case '"':
1153 case '\'':
1154 for (arg++, i = 0; arg[i]; i++)
1155 if (arg[i] == delimiter) {
1156 arg[i] = 0;
1157 break;
1158 }
1159 default:
1160 string_ncopy(opt_encoding, arg, strlen(arg));
1161 return OK;
1162 }
1163 }
1165 config_msg = "Unknown variable name";
1166 return ERR;
1167 }
1169 /* Wants: mode request key */
1170 static int
1171 option_bind_command(int argc, char *argv[])
1172 {
1173 enum request request;
1174 int keymap;
1175 int key;
1177 if (argc < 3) {
1178 config_msg = "Wrong number of arguments given to bind command";
1179 return ERR;
1180 }
1182 if (set_keymap(&keymap, argv[0]) == ERR) {
1183 config_msg = "Unknown key map";
1184 return ERR;
1185 }
1187 key = get_key_value(argv[1]);
1188 if (key == ERR) {
1189 config_msg = "Unknown key";
1190 return ERR;
1191 }
1193 request = get_request(argv[2]);
1194 if (request == REQ_NONE) {
1195 const char *obsolete[] = { "cherry-pick" };
1196 size_t namelen = strlen(argv[2]);
1197 int i;
1199 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1200 if (namelen == strlen(obsolete[i]) &&
1201 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1202 config_msg = "Obsolete request name";
1203 return ERR;
1204 }
1205 }
1206 }
1207 if (request == REQ_NONE && *argv[2]++ == '!')
1208 request = add_run_request(keymap, key, argc - 2, argv + 2);
1209 if (request == REQ_NONE) {
1210 config_msg = "Unknown request name";
1211 return ERR;
1212 }
1214 add_keybinding(keymap, request, key);
1216 return OK;
1217 }
1219 static int
1220 set_option(char *opt, char *value)
1221 {
1222 char *argv[16];
1223 int valuelen;
1224 int argc = 0;
1226 /* Tokenize */
1227 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1228 argv[argc++] = value;
1229 value += valuelen;
1231 /* Nothing more to tokenize or last available token. */
1232 if (!*value || argc >= ARRAY_SIZE(argv))
1233 break;
1235 *value++ = 0;
1236 while (isspace(*value))
1237 value++;
1238 }
1240 if (!strcmp(opt, "color"))
1241 return option_color_command(argc, argv);
1243 if (!strcmp(opt, "set"))
1244 return option_set_command(argc, argv);
1246 if (!strcmp(opt, "bind"))
1247 return option_bind_command(argc, argv);
1249 config_msg = "Unknown option command";
1250 return ERR;
1251 }
1253 static int
1254 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1255 {
1256 int status = OK;
1258 config_lineno++;
1259 config_msg = "Internal error";
1261 /* Check for comment markers, since read_properties() will
1262 * only ensure opt and value are split at first " \t". */
1263 optlen = strcspn(opt, "#");
1264 if (optlen == 0)
1265 return OK;
1267 if (opt[optlen] != 0) {
1268 config_msg = "No option value";
1269 status = ERR;
1271 } else {
1272 /* Look for comment endings in the value. */
1273 size_t len = strcspn(value, "#");
1275 if (len < valuelen) {
1276 valuelen = len;
1277 value[valuelen] = 0;
1278 }
1280 status = set_option(opt, value);
1281 }
1283 if (status == ERR) {
1284 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1285 config_lineno, (int) optlen, opt, config_msg);
1286 config_errors = TRUE;
1287 }
1289 /* Always keep going if errors are encountered. */
1290 return OK;
1291 }
1293 static void
1294 load_option_file(const char *path)
1295 {
1296 FILE *file;
1298 /* It's ok that the file doesn't exist. */
1299 file = fopen(path, "r");
1300 if (!file)
1301 return;
1303 config_lineno = 0;
1304 config_errors = FALSE;
1306 if (read_properties(file, " \t", read_option) == ERR ||
1307 config_errors == TRUE)
1308 fprintf(stderr, "Errors while loading %s.\n", path);
1309 }
1311 static int
1312 load_options(void)
1313 {
1314 char *home = getenv("HOME");
1315 char *tigrc_user = getenv("TIGRC_USER");
1316 char *tigrc_system = getenv("TIGRC_SYSTEM");
1317 char buf[SIZEOF_STR];
1319 add_builtin_run_requests();
1321 if (!tigrc_system) {
1322 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1323 return ERR;
1324 tigrc_system = buf;
1325 }
1326 load_option_file(tigrc_system);
1328 if (!tigrc_user) {
1329 if (!home || !string_format(buf, "%s/.tigrc", home))
1330 return ERR;
1331 tigrc_user = buf;
1332 }
1333 load_option_file(tigrc_user);
1335 return OK;
1336 }
1339 /*
1340 * The viewer
1341 */
1343 struct view;
1344 struct view_ops;
1346 /* The display array of active views and the index of the current view. */
1347 static struct view *display[2];
1348 static unsigned int current_view;
1350 /* Reading from the prompt? */
1351 static bool input_mode = FALSE;
1353 #define foreach_displayed_view(view, i) \
1354 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1356 #define displayed_views() (display[1] != NULL ? 2 : 1)
1358 /* Current head and commit ID */
1359 static char ref_blob[SIZEOF_REF] = "";
1360 static char ref_commit[SIZEOF_REF] = "HEAD";
1361 static char ref_head[SIZEOF_REF] = "HEAD";
1363 struct view {
1364 const char *name; /* View name */
1365 const char *cmd_fmt; /* Default command line format */
1366 const char *cmd_env; /* Command line set via environment */
1367 const char *id; /* Points to either of ref_{head,commit,blob} */
1369 struct view_ops *ops; /* View operations */
1371 enum keymap keymap; /* What keymap does this view have */
1372 bool git_dir; /* Whether the view requires a git directory. */
1374 char cmd[SIZEOF_STR]; /* Command buffer */
1375 char ref[SIZEOF_REF]; /* Hovered commit reference */
1376 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1378 int height, width; /* The width and height of the main window */
1379 WINDOW *win; /* The main window */
1380 WINDOW *title; /* The title window living below the main window */
1382 /* Navigation */
1383 unsigned long offset; /* Offset of the window top */
1384 unsigned long lineno; /* Current line number */
1386 /* Searching */
1387 char grep[SIZEOF_STR]; /* Search string */
1388 regex_t *regex; /* Pre-compiled regex */
1390 /* If non-NULL, points to the view that opened this view. If this view
1391 * is closed tig will switch back to the parent view. */
1392 struct view *parent;
1394 /* Buffering */
1395 size_t lines; /* Total number of lines */
1396 struct line *line; /* Line index */
1397 size_t line_alloc; /* Total number of allocated lines */
1398 size_t line_size; /* Total number of used lines */
1399 unsigned int digits; /* Number of digits in the lines member. */
1401 /* Drawing */
1402 struct line *curline; /* Line currently being drawn. */
1403 enum line_type curtype; /* Attribute currently used for drawing. */
1404 unsigned long col; /* Column when drawing. */
1406 /* Loading */
1407 FILE *pipe;
1408 time_t start_time;
1409 };
1411 struct view_ops {
1412 /* What type of content being displayed. Used in the title bar. */
1413 const char *type;
1414 /* Open and reads in all view content. */
1415 bool (*open)(struct view *view);
1416 /* Read one line; updates view->line. */
1417 bool (*read)(struct view *view, char *data);
1418 /* Draw one line; @lineno must be < view->height. */
1419 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1420 /* Depending on view handle a special requests. */
1421 enum request (*request)(struct view *view, enum request request, struct line *line);
1422 /* Search for regex in a line. */
1423 bool (*grep)(struct view *view, struct line *line);
1424 /* Select line */
1425 void (*select)(struct view *view, struct line *line);
1426 };
1428 static struct view_ops pager_ops;
1429 static struct view_ops main_ops;
1430 static struct view_ops tree_ops;
1431 static struct view_ops blob_ops;
1432 static struct view_ops blame_ops;
1433 static struct view_ops help_ops;
1434 static struct view_ops status_ops;
1435 static struct view_ops stage_ops;
1437 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1438 { name, cmd, #env, ref, ops, map, git }
1440 #define VIEW_(id, name, ops, git, ref) \
1441 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1444 static struct view views[] = {
1445 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1446 VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit),
1447 VIEW_(LOG, "log", &pager_ops, TRUE, ref_head),
1448 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1449 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1450 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1451 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1452 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1453 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1454 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1455 };
1457 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1458 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1460 #define foreach_view(view, i) \
1461 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1463 #define view_is_displayed(view) \
1464 (view == display[0] || view == display[1])
1467 enum line_graphic {
1468 LINE_GRAPHIC_VLINE,
1469 };
1471 static int line_graphics[] = {
1472 /* LINE_GRAPHIC_VLINE: */ '|'
1473 };
1475 static inline void
1476 set_view_attr(struct view *view, enum line_type type)
1477 {
1478 if (!view->curline->selected && view->curtype != type) {
1479 wattrset(view->win, get_line_attr(type));
1480 wchgat(view->win, -1, 0, type, NULL);
1481 view->curtype = type;
1482 }
1483 }
1485 static int
1486 draw_chars(struct view *view, enum line_type type, const char *string,
1487 int max_len, bool use_tilde)
1488 {
1489 int len = 0;
1490 int col = 0;
1491 int trimmed = FALSE;
1493 if (max_len <= 0)
1494 return 0;
1496 if (opt_utf8) {
1497 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1498 } else {
1499 col = len = strlen(string);
1500 if (len > max_len) {
1501 if (use_tilde) {
1502 max_len -= 1;
1503 }
1504 col = len = max_len;
1505 trimmed = TRUE;
1506 }
1507 }
1509 set_view_attr(view, type);
1510 waddnstr(view->win, string, len);
1511 if (trimmed && use_tilde) {
1512 set_view_attr(view, LINE_DELIMITER);
1513 waddch(view->win, '~');
1514 col++;
1515 }
1517 return col;
1518 }
1520 static int
1521 draw_space(struct view *view, enum line_type type, int max, int spaces)
1522 {
1523 static char space[] = " ";
1524 int col = 0;
1526 spaces = MIN(max, spaces);
1528 while (spaces > 0) {
1529 int len = MIN(spaces, sizeof(space) - 1);
1531 col += draw_chars(view, type, space, spaces, FALSE);
1532 spaces -= len;
1533 }
1535 return col;
1536 }
1538 static bool
1539 draw_lineno(struct view *view, unsigned int lineno)
1540 {
1541 char number[10];
1542 int digits3 = view->digits < 3 ? 3 : view->digits;
1543 int max_number = MIN(digits3, STRING_SIZE(number));
1544 int max = view->width - view->col;
1545 int col;
1547 if (max < max_number)
1548 max_number = max;
1550 lineno += view->offset + 1;
1551 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1552 static char fmt[] = "%1ld";
1554 if (view->digits <= 9)
1555 fmt[1] = '0' + digits3;
1557 if (!string_format(number, fmt, lineno))
1558 number[0] = 0;
1559 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1560 } else {
1561 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1562 }
1564 if (col < max) {
1565 set_view_attr(view, LINE_DEFAULT);
1566 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1567 col++;
1568 }
1570 if (col < max)
1571 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1572 view->col += col;
1574 return view->width - view->col <= 0;
1575 }
1577 static bool
1578 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1579 {
1580 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1581 return view->width - view->col <= 0;
1582 }
1584 static bool
1585 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1586 {
1587 int max = view->width - view->col;
1588 int i;
1590 if (max < size)
1591 size = max;
1593 set_view_attr(view, type);
1594 /* Using waddch() instead of waddnstr() ensures that
1595 * they'll be rendered correctly for the cursor line. */
1596 for (i = 0; i < size; i++)
1597 waddch(view->win, graphic[i]);
1599 view->col += size;
1600 if (size < max) {
1601 waddch(view->win, ' ');
1602 view->col++;
1603 }
1605 return view->width - view->col <= 0;
1606 }
1608 static bool
1609 draw_field(struct view *view, enum line_type type, char *text, int len, bool trim)
1610 {
1611 int max = MIN(view->width - view->col, len);
1612 int col;
1614 if (text)
1615 col = draw_chars(view, type, text, max - 1, trim);
1616 else
1617 col = draw_space(view, type, max - 1, max - 1);
1619 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1620 return view->width - view->col <= 0;
1621 }
1623 static bool
1624 draw_date(struct view *view, struct tm *time)
1625 {
1626 char buf[DATE_COLS];
1627 char *date;
1628 int timelen = 0;
1630 if (time)
1631 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1632 date = timelen ? buf : NULL;
1634 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1635 }
1637 static bool
1638 draw_view_line(struct view *view, unsigned int lineno)
1639 {
1640 struct line *line;
1641 bool selected = (view->offset + lineno == view->lineno);
1642 bool draw_ok;
1644 assert(view_is_displayed(view));
1646 if (view->offset + lineno >= view->lines)
1647 return FALSE;
1649 line = &view->line[view->offset + lineno];
1651 wmove(view->win, lineno, 0);
1652 view->col = 0;
1653 view->curline = line;
1654 view->curtype = LINE_NONE;
1655 line->selected = FALSE;
1657 if (selected) {
1658 set_view_attr(view, LINE_CURSOR);
1659 line->selected = TRUE;
1660 view->ops->select(view, line);
1661 } else if (line->selected) {
1662 wclrtoeol(view->win);
1663 }
1665 scrollok(view->win, FALSE);
1666 draw_ok = view->ops->draw(view, line, lineno);
1667 scrollok(view->win, TRUE);
1669 return draw_ok;
1670 }
1672 static void
1673 redraw_view_dirty(struct view *view)
1674 {
1675 bool dirty = FALSE;
1676 int lineno;
1678 for (lineno = 0; lineno < view->height; lineno++) {
1679 struct line *line = &view->line[view->offset + lineno];
1681 if (!line->dirty)
1682 continue;
1683 line->dirty = 0;
1684 dirty = TRUE;
1685 if (!draw_view_line(view, lineno))
1686 break;
1687 }
1689 if (!dirty)
1690 return;
1691 redrawwin(view->win);
1692 if (input_mode)
1693 wnoutrefresh(view->win);
1694 else
1695 wrefresh(view->win);
1696 }
1698 static void
1699 redraw_view_from(struct view *view, int lineno)
1700 {
1701 assert(0 <= lineno && lineno < view->height);
1703 for (; lineno < view->height; lineno++) {
1704 if (!draw_view_line(view, lineno))
1705 break;
1706 }
1708 redrawwin(view->win);
1709 if (input_mode)
1710 wnoutrefresh(view->win);
1711 else
1712 wrefresh(view->win);
1713 }
1715 static void
1716 redraw_view(struct view *view)
1717 {
1718 wclear(view->win);
1719 redraw_view_from(view, 0);
1720 }
1723 static void
1724 update_view_title(struct view *view)
1725 {
1726 char buf[SIZEOF_STR];
1727 char state[SIZEOF_STR];
1728 size_t bufpos = 0, statelen = 0;
1730 assert(view_is_displayed(view));
1732 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1733 unsigned int view_lines = view->offset + view->height;
1734 unsigned int lines = view->lines
1735 ? MIN(view_lines, view->lines) * 100 / view->lines
1736 : 0;
1738 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1739 view->ops->type,
1740 view->lineno + 1,
1741 view->lines,
1742 lines);
1744 if (view->pipe) {
1745 time_t secs = time(NULL) - view->start_time;
1747 /* Three git seconds are a long time ... */
1748 if (secs > 2)
1749 string_format_from(state, &statelen, " %lds", secs);
1750 }
1751 }
1753 string_format_from(buf, &bufpos, "[%s]", view->name);
1754 if (*view->ref && bufpos < view->width) {
1755 size_t refsize = strlen(view->ref);
1756 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1758 if (minsize < view->width)
1759 refsize = view->width - minsize + 7;
1760 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1761 }
1763 if (statelen && bufpos < view->width) {
1764 string_format_from(buf, &bufpos, " %s", state);
1765 }
1767 if (view == display[current_view])
1768 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1769 else
1770 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1772 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1773 wclrtoeol(view->title);
1774 wmove(view->title, 0, view->width - 1);
1776 if (input_mode)
1777 wnoutrefresh(view->title);
1778 else
1779 wrefresh(view->title);
1780 }
1782 static void
1783 resize_display(void)
1784 {
1785 int offset, i;
1786 struct view *base = display[0];
1787 struct view *view = display[1] ? display[1] : display[0];
1789 /* Setup window dimensions */
1791 getmaxyx(stdscr, base->height, base->width);
1793 /* Make room for the status window. */
1794 base->height -= 1;
1796 if (view != base) {
1797 /* Horizontal split. */
1798 view->width = base->width;
1799 view->height = SCALE_SPLIT_VIEW(base->height);
1800 base->height -= view->height;
1802 /* Make room for the title bar. */
1803 view->height -= 1;
1804 }
1806 /* Make room for the title bar. */
1807 base->height -= 1;
1809 offset = 0;
1811 foreach_displayed_view (view, i) {
1812 if (!view->win) {
1813 view->win = newwin(view->height, 0, offset, 0);
1814 if (!view->win)
1815 die("Failed to create %s view", view->name);
1817 scrollok(view->win, TRUE);
1819 view->title = newwin(1, 0, offset + view->height, 0);
1820 if (!view->title)
1821 die("Failed to create title window");
1823 } else {
1824 wresize(view->win, view->height, view->width);
1825 mvwin(view->win, offset, 0);
1826 mvwin(view->title, offset + view->height, 0);
1827 }
1829 offset += view->height + 1;
1830 }
1831 }
1833 static void
1834 redraw_display(void)
1835 {
1836 struct view *view;
1837 int i;
1839 foreach_displayed_view (view, i) {
1840 redraw_view(view);
1841 update_view_title(view);
1842 }
1843 }
1845 static void
1846 update_display_cursor(struct view *view)
1847 {
1848 /* Move the cursor to the right-most column of the cursor line.
1849 *
1850 * XXX: This could turn out to be a bit expensive, but it ensures that
1851 * the cursor does not jump around. */
1852 if (view->lines) {
1853 wmove(view->win, view->lineno - view->offset, view->width - 1);
1854 wrefresh(view->win);
1855 }
1856 }
1858 /*
1859 * Navigation
1860 */
1862 /* Scrolling backend */
1863 static void
1864 do_scroll_view(struct view *view, int lines)
1865 {
1866 bool redraw_current_line = FALSE;
1868 /* The rendering expects the new offset. */
1869 view->offset += lines;
1871 assert(0 <= view->offset && view->offset < view->lines);
1872 assert(lines);
1874 /* Move current line into the view. */
1875 if (view->lineno < view->offset) {
1876 view->lineno = view->offset;
1877 redraw_current_line = TRUE;
1878 } else if (view->lineno >= view->offset + view->height) {
1879 view->lineno = view->offset + view->height - 1;
1880 redraw_current_line = TRUE;
1881 }
1883 assert(view->offset <= view->lineno && view->lineno < view->lines);
1885 /* Redraw the whole screen if scrolling is pointless. */
1886 if (view->height < ABS(lines)) {
1887 redraw_view(view);
1889 } else {
1890 int line = lines > 0 ? view->height - lines : 0;
1891 int end = line + ABS(lines);
1893 wscrl(view->win, lines);
1895 for (; line < end; line++) {
1896 if (!draw_view_line(view, line))
1897 break;
1898 }
1900 if (redraw_current_line)
1901 draw_view_line(view, view->lineno - view->offset);
1902 }
1904 redrawwin(view->win);
1905 wrefresh(view->win);
1906 report("");
1907 }
1909 /* Scroll frontend */
1910 static void
1911 scroll_view(struct view *view, enum request request)
1912 {
1913 int lines = 1;
1915 assert(view_is_displayed(view));
1917 switch (request) {
1918 case REQ_SCROLL_PAGE_DOWN:
1919 lines = view->height;
1920 case REQ_SCROLL_LINE_DOWN:
1921 if (view->offset + lines > view->lines)
1922 lines = view->lines - view->offset;
1924 if (lines == 0 || view->offset + view->height >= view->lines) {
1925 report("Cannot scroll beyond the last line");
1926 return;
1927 }
1928 break;
1930 case REQ_SCROLL_PAGE_UP:
1931 lines = view->height;
1932 case REQ_SCROLL_LINE_UP:
1933 if (lines > view->offset)
1934 lines = view->offset;
1936 if (lines == 0) {
1937 report("Cannot scroll beyond the first line");
1938 return;
1939 }
1941 lines = -lines;
1942 break;
1944 default:
1945 die("request %d not handled in switch", request);
1946 }
1948 do_scroll_view(view, lines);
1949 }
1951 /* Cursor moving */
1952 static void
1953 move_view(struct view *view, enum request request)
1954 {
1955 int scroll_steps = 0;
1956 int steps;
1958 switch (request) {
1959 case REQ_MOVE_FIRST_LINE:
1960 steps = -view->lineno;
1961 break;
1963 case REQ_MOVE_LAST_LINE:
1964 steps = view->lines - view->lineno - 1;
1965 break;
1967 case REQ_MOVE_PAGE_UP:
1968 steps = view->height > view->lineno
1969 ? -view->lineno : -view->height;
1970 break;
1972 case REQ_MOVE_PAGE_DOWN:
1973 steps = view->lineno + view->height >= view->lines
1974 ? view->lines - view->lineno - 1 : view->height;
1975 break;
1977 case REQ_MOVE_UP:
1978 steps = -1;
1979 break;
1981 case REQ_MOVE_DOWN:
1982 steps = 1;
1983 break;
1985 default:
1986 die("request %d not handled in switch", request);
1987 }
1989 if (steps <= 0 && view->lineno == 0) {
1990 report("Cannot move beyond the first line");
1991 return;
1993 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1994 report("Cannot move beyond the last line");
1995 return;
1996 }
1998 /* Move the current line */
1999 view->lineno += steps;
2000 assert(0 <= view->lineno && view->lineno < view->lines);
2002 /* Check whether the view needs to be scrolled */
2003 if (view->lineno < view->offset ||
2004 view->lineno >= view->offset + view->height) {
2005 scroll_steps = steps;
2006 if (steps < 0 && -steps > view->offset) {
2007 scroll_steps = -view->offset;
2009 } else if (steps > 0) {
2010 if (view->lineno == view->lines - 1 &&
2011 view->lines > view->height) {
2012 scroll_steps = view->lines - view->offset - 1;
2013 if (scroll_steps >= view->height)
2014 scroll_steps -= view->height - 1;
2015 }
2016 }
2017 }
2019 if (!view_is_displayed(view)) {
2020 view->offset += scroll_steps;
2021 assert(0 <= view->offset && view->offset < view->lines);
2022 view->ops->select(view, &view->line[view->lineno]);
2023 return;
2024 }
2026 /* Repaint the old "current" line if we be scrolling */
2027 if (ABS(steps) < view->height)
2028 draw_view_line(view, view->lineno - steps - view->offset);
2030 if (scroll_steps) {
2031 do_scroll_view(view, scroll_steps);
2032 return;
2033 }
2035 /* Draw the current line */
2036 draw_view_line(view, view->lineno - view->offset);
2038 redrawwin(view->win);
2039 wrefresh(view->win);
2040 report("");
2041 }
2044 /*
2045 * Searching
2046 */
2048 static void search_view(struct view *view, enum request request);
2050 static bool
2051 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2052 {
2053 assert(view_is_displayed(view));
2055 if (!view->ops->grep(view, line))
2056 return FALSE;
2058 if (lineno - view->offset >= view->height) {
2059 view->offset = lineno;
2060 view->lineno = lineno;
2061 redraw_view(view);
2063 } else {
2064 unsigned long old_lineno = view->lineno - view->offset;
2066 view->lineno = lineno;
2067 draw_view_line(view, old_lineno);
2069 draw_view_line(view, view->lineno - view->offset);
2070 redrawwin(view->win);
2071 wrefresh(view->win);
2072 }
2074 report("Line %ld matches '%s'", lineno + 1, view->grep);
2075 return TRUE;
2076 }
2078 static void
2079 find_next(struct view *view, enum request request)
2080 {
2081 unsigned long lineno = view->lineno;
2082 int direction;
2084 if (!*view->grep) {
2085 if (!*opt_search)
2086 report("No previous search");
2087 else
2088 search_view(view, request);
2089 return;
2090 }
2092 switch (request) {
2093 case REQ_SEARCH:
2094 case REQ_FIND_NEXT:
2095 direction = 1;
2096 break;
2098 case REQ_SEARCH_BACK:
2099 case REQ_FIND_PREV:
2100 direction = -1;
2101 break;
2103 default:
2104 return;
2105 }
2107 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2108 lineno += direction;
2110 /* Note, lineno is unsigned long so will wrap around in which case it
2111 * will become bigger than view->lines. */
2112 for (; lineno < view->lines; lineno += direction) {
2113 struct line *line = &view->line[lineno];
2115 if (find_next_line(view, lineno, line))
2116 return;
2117 }
2119 report("No match found for '%s'", view->grep);
2120 }
2122 static void
2123 search_view(struct view *view, enum request request)
2124 {
2125 int regex_err;
2127 if (view->regex) {
2128 regfree(view->regex);
2129 *view->grep = 0;
2130 } else {
2131 view->regex = calloc(1, sizeof(*view->regex));
2132 if (!view->regex)
2133 return;
2134 }
2136 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2137 if (regex_err != 0) {
2138 char buf[SIZEOF_STR] = "unknown error";
2140 regerror(regex_err, view->regex, buf, sizeof(buf));
2141 report("Search failed: %s", buf);
2142 return;
2143 }
2145 string_copy(view->grep, opt_search);
2147 find_next(view, request);
2148 }
2150 /*
2151 * Incremental updating
2152 */
2154 static void
2155 end_update(struct view *view)
2156 {
2157 if (!view->pipe)
2158 return;
2159 set_nonblocking_input(FALSE);
2160 if (view->pipe == stdin)
2161 fclose(view->pipe);
2162 else
2163 pclose(view->pipe);
2164 view->pipe = NULL;
2165 }
2167 static bool
2168 begin_update(struct view *view)
2169 {
2170 if (view->pipe)
2171 end_update(view);
2173 if (opt_cmd[0]) {
2174 string_copy(view->cmd, opt_cmd);
2175 opt_cmd[0] = 0;
2176 /* When running random commands, initially show the
2177 * command in the title. However, it maybe later be
2178 * overwritten if a commit line is selected. */
2179 if (view == VIEW(REQ_VIEW_PAGER))
2180 string_copy(view->ref, view->cmd);
2181 else
2182 view->ref[0] = 0;
2184 } else if (view == VIEW(REQ_VIEW_TREE)) {
2185 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2186 char path[SIZEOF_STR];
2188 if (strcmp(view->vid, view->id))
2189 opt_path[0] = path[0] = 0;
2190 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2191 return FALSE;
2193 if (!string_format(view->cmd, format, view->id, path))
2194 return FALSE;
2196 } else {
2197 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2198 const char *id = view->id;
2200 if (!string_format(view->cmd, format, id, id, id, id, id))
2201 return FALSE;
2203 /* Put the current ref_* value to the view title ref
2204 * member. This is needed by the blob view. Most other
2205 * views sets it automatically after loading because the
2206 * first line is a commit line. */
2207 string_copy_rev(view->ref, view->id);
2208 }
2210 /* Special case for the pager view. */
2211 if (opt_pipe) {
2212 view->pipe = opt_pipe;
2213 opt_pipe = NULL;
2214 } else {
2215 view->pipe = popen(view->cmd, "r");
2216 }
2218 if (!view->pipe)
2219 return FALSE;
2221 set_nonblocking_input(TRUE);
2223 view->offset = 0;
2224 view->lines = 0;
2225 view->lineno = 0;
2226 string_copy_rev(view->vid, view->id);
2228 if (view->line) {
2229 int i;
2231 for (i = 0; i < view->lines; i++)
2232 if (view->line[i].data)
2233 free(view->line[i].data);
2235 free(view->line);
2236 view->line = NULL;
2237 }
2239 view->start_time = time(NULL);
2241 return TRUE;
2242 }
2244 #define ITEM_CHUNK_SIZE 256
2245 static void *
2246 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2247 {
2248 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2249 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2251 if (mem == NULL || num_chunks != num_chunks_new) {
2252 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2253 mem = realloc(mem, *size * item_size);
2254 }
2256 return mem;
2257 }
2259 static struct line *
2260 realloc_lines(struct view *view, size_t line_size)
2261 {
2262 size_t alloc = view->line_alloc;
2263 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2264 sizeof(*view->line));
2266 if (!tmp)
2267 return NULL;
2269 view->line = tmp;
2270 view->line_alloc = alloc;
2271 view->line_size = line_size;
2272 return view->line;
2273 }
2275 static bool
2276 update_view(struct view *view)
2277 {
2278 char in_buffer[BUFSIZ];
2279 char out_buffer[BUFSIZ * 2];
2280 char *line;
2281 /* The number of lines to read. If too low it will cause too much
2282 * redrawing (and possible flickering), if too high responsiveness
2283 * will suffer. */
2284 unsigned long lines = view->height;
2285 int redraw_from = -1;
2287 if (!view->pipe)
2288 return TRUE;
2290 /* Only redraw if lines are visible. */
2291 if (view->offset + view->height >= view->lines)
2292 redraw_from = view->lines - view->offset;
2294 /* FIXME: This is probably not perfect for backgrounded views. */
2295 if (!realloc_lines(view, view->lines + lines))
2296 goto alloc_error;
2298 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2299 size_t linelen = strlen(line);
2301 if (linelen)
2302 line[linelen - 1] = 0;
2304 if (opt_iconv != ICONV_NONE) {
2305 ICONV_CONST char *inbuf = line;
2306 size_t inlen = linelen;
2308 char *outbuf = out_buffer;
2309 size_t outlen = sizeof(out_buffer);
2311 size_t ret;
2313 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2314 if (ret != (size_t) -1) {
2315 line = out_buffer;
2316 linelen = strlen(out_buffer);
2317 }
2318 }
2320 if (!view->ops->read(view, line))
2321 goto alloc_error;
2323 if (lines-- == 1)
2324 break;
2325 }
2327 {
2328 int digits;
2330 lines = view->lines;
2331 for (digits = 0; lines; digits++)
2332 lines /= 10;
2334 /* Keep the displayed view in sync with line number scaling. */
2335 if (digits != view->digits) {
2336 view->digits = digits;
2337 redraw_from = 0;
2338 }
2339 }
2341 if (!view_is_displayed(view))
2342 goto check_pipe;
2344 if (view == VIEW(REQ_VIEW_TREE)) {
2345 /* Clear the view and redraw everything since the tree sorting
2346 * might have rearranged things. */
2347 redraw_view(view);
2349 } else if (redraw_from >= 0) {
2350 /* If this is an incremental update, redraw the previous line
2351 * since for commits some members could have changed when
2352 * loading the main view. */
2353 if (redraw_from > 0)
2354 redraw_from--;
2356 /* Since revision graph visualization requires knowledge
2357 * about the parent commit, it causes a further one-off
2358 * needed to be redrawn for incremental updates. */
2359 if (redraw_from > 0 && opt_rev_graph)
2360 redraw_from--;
2362 /* Incrementally draw avoids flickering. */
2363 redraw_view_from(view, redraw_from);
2364 }
2366 if (view == VIEW(REQ_VIEW_BLAME))
2367 redraw_view_dirty(view);
2369 /* Update the title _after_ the redraw so that if the redraw picks up a
2370 * commit reference in view->ref it'll be available here. */
2371 update_view_title(view);
2373 check_pipe:
2374 if (ferror(view->pipe)) {
2375 report("Failed to read: %s", strerror(errno));
2376 goto end;
2378 } else if (feof(view->pipe)) {
2379 report("");
2380 goto end;
2381 }
2383 return TRUE;
2385 alloc_error:
2386 report("Allocation failure");
2388 end:
2389 if (view->ops->read(view, NULL))
2390 end_update(view);
2391 return FALSE;
2392 }
2394 static struct line *
2395 add_line_data(struct view *view, void *data, enum line_type type)
2396 {
2397 struct line *line = &view->line[view->lines++];
2399 memset(line, 0, sizeof(*line));
2400 line->type = type;
2401 line->data = data;
2403 return line;
2404 }
2406 static struct line *
2407 add_line_text(struct view *view, char *data, enum line_type type)
2408 {
2409 if (data)
2410 data = strdup(data);
2412 return data ? add_line_data(view, data, type) : NULL;
2413 }
2416 /*
2417 * View opening
2418 */
2420 enum open_flags {
2421 OPEN_DEFAULT = 0, /* Use default view switching. */
2422 OPEN_SPLIT = 1, /* Split current view. */
2423 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2424 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2425 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2426 };
2428 static void
2429 open_view(struct view *prev, enum request request, enum open_flags flags)
2430 {
2431 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2432 bool split = !!(flags & OPEN_SPLIT);
2433 bool reload = !!(flags & OPEN_RELOAD);
2434 bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2435 struct view *view = VIEW(request);
2436 int nviews = displayed_views();
2437 struct view *base_view = display[0];
2439 if (view == prev && nviews == 1 && !reload) {
2440 report("Already in %s view", view->name);
2441 return;
2442 }
2444 if (view->git_dir && !opt_git_dir[0]) {
2445 report("The %s view is disabled in pager view", view->name);
2446 return;
2447 }
2449 if (split) {
2450 display[1] = view;
2451 if (!backgrounded)
2452 current_view = 1;
2453 } else if (!nomaximize) {
2454 /* Maximize the current view. */
2455 memset(display, 0, sizeof(display));
2456 current_view = 0;
2457 display[current_view] = view;
2458 }
2460 /* Resize the view when switching between split- and full-screen,
2461 * or when switching between two different full-screen views. */
2462 if (nviews != displayed_views() ||
2463 (nviews == 1 && base_view != display[0]))
2464 resize_display();
2466 if (view->ops->open) {
2467 if (!view->ops->open(view)) {
2468 report("Failed to load %s view", view->name);
2469 return;
2470 }
2472 } else if ((reload || strcmp(view->vid, view->id)) &&
2473 !begin_update(view)) {
2474 report("Failed to load %s view", view->name);
2475 return;
2476 }
2478 if (split && prev->lineno - prev->offset >= prev->height) {
2479 /* Take the title line into account. */
2480 int lines = prev->lineno - prev->offset - prev->height + 1;
2482 /* Scroll the view that was split if the current line is
2483 * outside the new limited view. */
2484 do_scroll_view(prev, lines);
2485 }
2487 if (prev && view != prev) {
2488 if (split && !backgrounded) {
2489 /* "Blur" the previous view. */
2490 update_view_title(prev);
2491 }
2493 view->parent = prev;
2494 }
2496 if (view->pipe && view->lines == 0) {
2497 /* Clear the old view and let the incremental updating refill
2498 * the screen. */
2499 werase(view->win);
2500 report("");
2501 } else {
2502 redraw_view(view);
2503 report("");
2504 }
2506 /* If the view is backgrounded the above calls to report()
2507 * won't redraw the view title. */
2508 if (backgrounded)
2509 update_view_title(view);
2510 }
2512 static void
2513 open_external_viewer(const char *cmd)
2514 {
2515 def_prog_mode(); /* save current tty modes */
2516 endwin(); /* restore original tty modes */
2517 system(cmd);
2518 fprintf(stderr, "Press Enter to continue");
2519 getc(stdin);
2520 reset_prog_mode();
2521 redraw_display();
2522 }
2524 static void
2525 open_mergetool(const char *file)
2526 {
2527 char cmd[SIZEOF_STR];
2528 char file_sq[SIZEOF_STR];
2530 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2531 string_format(cmd, "git mergetool %s", file_sq)) {
2532 open_external_viewer(cmd);
2533 }
2534 }
2536 static void
2537 open_editor(bool from_root, const char *file)
2538 {
2539 char cmd[SIZEOF_STR];
2540 char file_sq[SIZEOF_STR];
2541 char *editor;
2542 char *prefix = from_root ? opt_cdup : "";
2544 editor = getenv("GIT_EDITOR");
2545 if (!editor && *opt_editor)
2546 editor = opt_editor;
2547 if (!editor)
2548 editor = getenv("VISUAL");
2549 if (!editor)
2550 editor = getenv("EDITOR");
2551 if (!editor)
2552 editor = "vi";
2554 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2555 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2556 open_external_viewer(cmd);
2557 }
2558 }
2560 static void
2561 open_run_request(enum request request)
2562 {
2563 struct run_request *req = get_run_request(request);
2564 char buf[SIZEOF_STR * 2];
2565 size_t bufpos;
2566 char *cmd;
2568 if (!req) {
2569 report("Unknown run request");
2570 return;
2571 }
2573 bufpos = 0;
2574 cmd = req->cmd;
2576 while (cmd) {
2577 char *next = strstr(cmd, "%(");
2578 int len = next - cmd;
2579 char *value;
2581 if (!next) {
2582 len = strlen(cmd);
2583 value = "";
2585 } else if (!strncmp(next, "%(head)", 7)) {
2586 value = ref_head;
2588 } else if (!strncmp(next, "%(commit)", 9)) {
2589 value = ref_commit;
2591 } else if (!strncmp(next, "%(blob)", 7)) {
2592 value = ref_blob;
2594 } else {
2595 report("Unknown replacement in run request: `%s`", req->cmd);
2596 return;
2597 }
2599 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2600 return;
2602 if (next)
2603 next = strchr(next, ')') + 1;
2604 cmd = next;
2605 }
2607 open_external_viewer(buf);
2608 }
2610 /*
2611 * User request switch noodle
2612 */
2614 static int
2615 view_driver(struct view *view, enum request request)
2616 {
2617 int i;
2619 if (request == REQ_NONE) {
2620 doupdate();
2621 return TRUE;
2622 }
2624 if (request > REQ_NONE) {
2625 open_run_request(request);
2626 /* FIXME: When all views can refresh always do this. */
2627 if (view == VIEW(REQ_VIEW_STATUS) ||
2628 view == VIEW(REQ_VIEW_STAGE))
2629 request = REQ_REFRESH;
2630 else
2631 return TRUE;
2632 }
2634 if (view && view->lines) {
2635 request = view->ops->request(view, request, &view->line[view->lineno]);
2636 if (request == REQ_NONE)
2637 return TRUE;
2638 }
2640 switch (request) {
2641 case REQ_MOVE_UP:
2642 case REQ_MOVE_DOWN:
2643 case REQ_MOVE_PAGE_UP:
2644 case REQ_MOVE_PAGE_DOWN:
2645 case REQ_MOVE_FIRST_LINE:
2646 case REQ_MOVE_LAST_LINE:
2647 move_view(view, request);
2648 break;
2650 case REQ_SCROLL_LINE_DOWN:
2651 case REQ_SCROLL_LINE_UP:
2652 case REQ_SCROLL_PAGE_DOWN:
2653 case REQ_SCROLL_PAGE_UP:
2654 scroll_view(view, request);
2655 break;
2657 case REQ_VIEW_BLAME:
2658 if (!opt_file[0]) {
2659 report("No file chosen, press %s to open tree view",
2660 get_key(REQ_VIEW_TREE));
2661 break;
2662 }
2663 open_view(view, request, OPEN_DEFAULT);
2664 break;
2666 case REQ_VIEW_BLOB:
2667 if (!ref_blob[0]) {
2668 report("No file chosen, press %s to open tree view",
2669 get_key(REQ_VIEW_TREE));
2670 break;
2671 }
2672 open_view(view, request, OPEN_DEFAULT);
2673 break;
2675 case REQ_VIEW_PAGER:
2676 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2677 report("No pager content, press %s to run command from prompt",
2678 get_key(REQ_PROMPT));
2679 break;
2680 }
2681 open_view(view, request, OPEN_DEFAULT);
2682 break;
2684 case REQ_VIEW_STAGE:
2685 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2686 report("No stage content, press %s to open the status view and choose file",
2687 get_key(REQ_VIEW_STATUS));
2688 break;
2689 }
2690 open_view(view, request, OPEN_DEFAULT);
2691 break;
2693 case REQ_VIEW_STATUS:
2694 if (opt_is_inside_work_tree == FALSE) {
2695 report("The status view requires a working tree");
2696 break;
2697 }
2698 open_view(view, request, OPEN_DEFAULT);
2699 break;
2701 case REQ_VIEW_MAIN:
2702 case REQ_VIEW_DIFF:
2703 case REQ_VIEW_LOG:
2704 case REQ_VIEW_TREE:
2705 case REQ_VIEW_HELP:
2706 open_view(view, request, OPEN_DEFAULT);
2707 break;
2709 case REQ_NEXT:
2710 case REQ_PREVIOUS:
2711 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2713 if ((view == VIEW(REQ_VIEW_DIFF) &&
2714 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2715 (view == VIEW(REQ_VIEW_DIFF) &&
2716 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2717 (view == VIEW(REQ_VIEW_STAGE) &&
2718 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2719 (view == VIEW(REQ_VIEW_BLOB) &&
2720 view->parent == VIEW(REQ_VIEW_TREE))) {
2721 int line;
2723 view = view->parent;
2724 line = view->lineno;
2725 move_view(view, request);
2726 if (view_is_displayed(view))
2727 update_view_title(view);
2728 if (line != view->lineno)
2729 view->ops->request(view, REQ_ENTER,
2730 &view->line[view->lineno]);
2732 } else {
2733 move_view(view, request);
2734 }
2735 break;
2737 case REQ_VIEW_NEXT:
2738 {
2739 int nviews = displayed_views();
2740 int next_view = (current_view + 1) % nviews;
2742 if (next_view == current_view) {
2743 report("Only one view is displayed");
2744 break;
2745 }
2747 current_view = next_view;
2748 /* Blur out the title of the previous view. */
2749 update_view_title(view);
2750 report("");
2751 break;
2752 }
2753 case REQ_REFRESH:
2754 report("Refreshing is not yet supported for the %s view", view->name);
2755 break;
2757 case REQ_MAXIMIZE:
2758 if (displayed_views() == 2)
2759 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2760 break;
2762 case REQ_TOGGLE_LINENO:
2763 opt_line_number = !opt_line_number;
2764 redraw_display();
2765 break;
2767 case REQ_TOGGLE_DATE:
2768 opt_date = !opt_date;
2769 redraw_display();
2770 break;
2772 case REQ_TOGGLE_AUTHOR:
2773 opt_author = !opt_author;
2774 redraw_display();
2775 break;
2777 case REQ_TOGGLE_REV_GRAPH:
2778 opt_rev_graph = !opt_rev_graph;
2779 redraw_display();
2780 break;
2782 case REQ_TOGGLE_REFS:
2783 opt_show_refs = !opt_show_refs;
2784 redraw_display();
2785 break;
2787 case REQ_PROMPT:
2788 /* Always reload^Wrerun commands from the prompt. */
2789 open_view(view, opt_request, OPEN_RELOAD);
2790 break;
2792 case REQ_SEARCH:
2793 case REQ_SEARCH_BACK:
2794 search_view(view, request);
2795 break;
2797 case REQ_FIND_NEXT:
2798 case REQ_FIND_PREV:
2799 find_next(view, request);
2800 break;
2802 case REQ_STOP_LOADING:
2803 for (i = 0; i < ARRAY_SIZE(views); i++) {
2804 view = &views[i];
2805 if (view->pipe)
2806 report("Stopped loading the %s view", view->name),
2807 end_update(view);
2808 }
2809 break;
2811 case REQ_SHOW_VERSION:
2812 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2813 return TRUE;
2815 case REQ_SCREEN_RESIZE:
2816 resize_display();
2817 /* Fall-through */
2818 case REQ_SCREEN_REDRAW:
2819 redraw_display();
2820 break;
2822 case REQ_EDIT:
2823 report("Nothing to edit");
2824 break;
2827 case REQ_ENTER:
2828 report("Nothing to enter");
2829 break;
2832 case REQ_VIEW_CLOSE:
2833 /* XXX: Mark closed views by letting view->parent point to the
2834 * view itself. Parents to closed view should never be
2835 * followed. */
2836 if (view->parent &&
2837 view->parent->parent != view->parent) {
2838 memset(display, 0, sizeof(display));
2839 current_view = 0;
2840 display[current_view] = view->parent;
2841 view->parent = view;
2842 resize_display();
2843 redraw_display();
2844 break;
2845 }
2846 /* Fall-through */
2847 case REQ_QUIT:
2848 return FALSE;
2850 default:
2851 /* An unknown key will show most commonly used commands. */
2852 report("Unknown key, press 'h' for help");
2853 return TRUE;
2854 }
2856 return TRUE;
2857 }
2860 /*
2861 * Pager backend
2862 */
2864 static bool
2865 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2866 {
2867 char *text = line->data;
2869 if (opt_line_number && draw_lineno(view, lineno))
2870 return TRUE;
2872 draw_text(view, line->type, text, TRUE);
2873 return TRUE;
2874 }
2876 static bool
2877 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2878 {
2879 char refbuf[SIZEOF_STR];
2880 char *ref = NULL;
2881 FILE *pipe;
2883 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2884 return TRUE;
2886 pipe = popen(refbuf, "r");
2887 if (!pipe)
2888 return TRUE;
2890 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2891 ref = chomp_string(ref);
2892 pclose(pipe);
2894 if (!ref || !*ref)
2895 return TRUE;
2897 /* This is the only fatal call, since it can "corrupt" the buffer. */
2898 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2899 return FALSE;
2901 return TRUE;
2902 }
2904 static void
2905 add_pager_refs(struct view *view, struct line *line)
2906 {
2907 char buf[SIZEOF_STR];
2908 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2909 struct ref **refs;
2910 size_t bufpos = 0, refpos = 0;
2911 const char *sep = "Refs: ";
2912 bool is_tag = FALSE;
2914 assert(line->type == LINE_COMMIT);
2916 refs = get_refs(commit_id);
2917 if (!refs) {
2918 if (view == VIEW(REQ_VIEW_DIFF))
2919 goto try_add_describe_ref;
2920 return;
2921 }
2923 do {
2924 struct ref *ref = refs[refpos];
2925 char *fmt = ref->tag ? "%s[%s]" :
2926 ref->remote ? "%s<%s>" : "%s%s";
2928 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2929 return;
2930 sep = ", ";
2931 if (ref->tag)
2932 is_tag = TRUE;
2933 } while (refs[refpos++]->next);
2935 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2936 try_add_describe_ref:
2937 /* Add <tag>-g<commit_id> "fake" reference. */
2938 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2939 return;
2940 }
2942 if (bufpos == 0)
2943 return;
2945 if (!realloc_lines(view, view->line_size + 1))
2946 return;
2948 add_line_text(view, buf, LINE_PP_REFS);
2949 }
2951 static bool
2952 pager_read(struct view *view, char *data)
2953 {
2954 struct line *line;
2956 if (!data)
2957 return TRUE;
2959 line = add_line_text(view, data, get_line_type(data));
2960 if (!line)
2961 return FALSE;
2963 if (line->type == LINE_COMMIT &&
2964 (view == VIEW(REQ_VIEW_DIFF) ||
2965 view == VIEW(REQ_VIEW_LOG)))
2966 add_pager_refs(view, line);
2968 return TRUE;
2969 }
2971 static enum request
2972 pager_request(struct view *view, enum request request, struct line *line)
2973 {
2974 int split = 0;
2976 if (request != REQ_ENTER)
2977 return request;
2979 if (line->type == LINE_COMMIT &&
2980 (view == VIEW(REQ_VIEW_LOG) ||
2981 view == VIEW(REQ_VIEW_PAGER))) {
2982 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2983 split = 1;
2984 }
2986 /* Always scroll the view even if it was split. That way
2987 * you can use Enter to scroll through the log view and
2988 * split open each commit diff. */
2989 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2991 /* FIXME: A minor workaround. Scrolling the view will call report("")
2992 * but if we are scrolling a non-current view this won't properly
2993 * update the view title. */
2994 if (split)
2995 update_view_title(view);
2997 return REQ_NONE;
2998 }
3000 static bool
3001 pager_grep(struct view *view, struct line *line)
3002 {
3003 regmatch_t pmatch;
3004 char *text = line->data;
3006 if (!*text)
3007 return FALSE;
3009 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3010 return FALSE;
3012 return TRUE;
3013 }
3015 static void
3016 pager_select(struct view *view, struct line *line)
3017 {
3018 if (line->type == LINE_COMMIT) {
3019 char *text = (char *)line->data + STRING_SIZE("commit ");
3021 if (view != VIEW(REQ_VIEW_PAGER))
3022 string_copy_rev(view->ref, text);
3023 string_copy_rev(ref_commit, text);
3024 }
3025 }
3027 static struct view_ops pager_ops = {
3028 "line",
3029 NULL,
3030 pager_read,
3031 pager_draw,
3032 pager_request,
3033 pager_grep,
3034 pager_select,
3035 };
3038 /*
3039 * Help backend
3040 */
3042 static bool
3043 help_open(struct view *view)
3044 {
3045 char buf[BUFSIZ];
3046 int lines = ARRAY_SIZE(req_info) + 2;
3047 int i;
3049 if (view->lines > 0)
3050 return TRUE;
3052 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3053 if (!req_info[i].request)
3054 lines++;
3056 lines += run_requests + 1;
3058 view->line = calloc(lines, sizeof(*view->line));
3059 if (!view->line)
3060 return FALSE;
3062 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3064 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3065 char *key;
3067 if (req_info[i].request == REQ_NONE)
3068 continue;
3070 if (!req_info[i].request) {
3071 add_line_text(view, "", LINE_DEFAULT);
3072 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3073 continue;
3074 }
3076 key = get_key(req_info[i].request);
3077 if (!*key)
3078 key = "(no key defined)";
3080 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3081 continue;
3083 add_line_text(view, buf, LINE_DEFAULT);
3084 }
3086 if (run_requests) {
3087 add_line_text(view, "", LINE_DEFAULT);
3088 add_line_text(view, "External commands:", LINE_DEFAULT);
3089 }
3091 for (i = 0; i < run_requests; i++) {
3092 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3093 char *key;
3095 if (!req)
3096 continue;
3098 key = get_key_name(req->key);
3099 if (!*key)
3100 key = "(no key defined)";
3102 if (!string_format(buf, " %-10s %-14s `%s`",
3103 keymap_table[req->keymap].name,
3104 key, req->cmd))
3105 continue;
3107 add_line_text(view, buf, LINE_DEFAULT);
3108 }
3110 return TRUE;
3111 }
3113 static struct view_ops help_ops = {
3114 "line",
3115 help_open,
3116 NULL,
3117 pager_draw,
3118 pager_request,
3119 pager_grep,
3120 pager_select,
3121 };
3124 /*
3125 * Tree backend
3126 */
3128 struct tree_stack_entry {
3129 struct tree_stack_entry *prev; /* Entry below this in the stack */
3130 unsigned long lineno; /* Line number to restore */
3131 char *name; /* Position of name in opt_path */
3132 };
3134 /* The top of the path stack. */
3135 static struct tree_stack_entry *tree_stack = NULL;
3136 unsigned long tree_lineno = 0;
3138 static void
3139 pop_tree_stack_entry(void)
3140 {
3141 struct tree_stack_entry *entry = tree_stack;
3143 tree_lineno = entry->lineno;
3144 entry->name[0] = 0;
3145 tree_stack = entry->prev;
3146 free(entry);
3147 }
3149 static void
3150 push_tree_stack_entry(char *name, unsigned long lineno)
3151 {
3152 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3153 size_t pathlen = strlen(opt_path);
3155 if (!entry)
3156 return;
3158 entry->prev = tree_stack;
3159 entry->name = opt_path + pathlen;
3160 tree_stack = entry;
3162 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3163 pop_tree_stack_entry();
3164 return;
3165 }
3167 /* Move the current line to the first tree entry. */
3168 tree_lineno = 1;
3169 entry->lineno = lineno;
3170 }
3172 /* Parse output from git-ls-tree(1):
3173 *
3174 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3175 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3176 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3177 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3178 */
3180 #define SIZEOF_TREE_ATTR \
3181 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3183 #define TREE_UP_FORMAT "040000 tree %s\t.."
3185 static int
3186 tree_compare_entry(enum line_type type1, char *name1,
3187 enum line_type type2, char *name2)
3188 {
3189 if (type1 != type2) {
3190 if (type1 == LINE_TREE_DIR)
3191 return -1;
3192 return 1;
3193 }
3195 return strcmp(name1, name2);
3196 }
3198 static char *
3199 tree_path(struct line *line)
3200 {
3201 char *path = line->data;
3203 return path + SIZEOF_TREE_ATTR;
3204 }
3206 static bool
3207 tree_read(struct view *view, char *text)
3208 {
3209 size_t textlen = text ? strlen(text) : 0;
3210 char buf[SIZEOF_STR];
3211 unsigned long pos;
3212 enum line_type type;
3213 bool first_read = view->lines == 0;
3215 if (!text)
3216 return TRUE;
3217 if (textlen <= SIZEOF_TREE_ATTR)
3218 return FALSE;
3220 type = text[STRING_SIZE("100644 ")] == 't'
3221 ? LINE_TREE_DIR : LINE_TREE_FILE;
3223 if (first_read) {
3224 /* Add path info line */
3225 if (!string_format(buf, "Directory path /%s", opt_path) ||
3226 !realloc_lines(view, view->line_size + 1) ||
3227 !add_line_text(view, buf, LINE_DEFAULT))
3228 return FALSE;
3230 /* Insert "link" to parent directory. */
3231 if (*opt_path) {
3232 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3233 !realloc_lines(view, view->line_size + 1) ||
3234 !add_line_text(view, buf, LINE_TREE_DIR))
3235 return FALSE;
3236 }
3237 }
3239 /* Strip the path part ... */
3240 if (*opt_path) {
3241 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3242 size_t striplen = strlen(opt_path);
3243 char *path = text + SIZEOF_TREE_ATTR;
3245 if (pathlen > striplen)
3246 memmove(path, path + striplen,
3247 pathlen - striplen + 1);
3248 }
3250 /* Skip "Directory ..." and ".." line. */
3251 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3252 struct line *line = &view->line[pos];
3253 char *path1 = tree_path(line);
3254 char *path2 = text + SIZEOF_TREE_ATTR;
3255 int cmp = tree_compare_entry(line->type, path1, type, path2);
3257 if (cmp <= 0)
3258 continue;
3260 text = strdup(text);
3261 if (!text)
3262 return FALSE;
3264 if (view->lines > pos)
3265 memmove(&view->line[pos + 1], &view->line[pos],
3266 (view->lines - pos) * sizeof(*line));
3268 line = &view->line[pos];
3269 line->data = text;
3270 line->type = type;
3271 view->lines++;
3272 return TRUE;
3273 }
3275 if (!add_line_text(view, text, type))
3276 return FALSE;
3278 if (tree_lineno > view->lineno) {
3279 view->lineno = tree_lineno;
3280 tree_lineno = 0;
3281 }
3283 return TRUE;
3284 }
3286 static enum request
3287 tree_request(struct view *view, enum request request, struct line *line)
3288 {
3289 enum open_flags flags;
3291 if (request == REQ_VIEW_BLAME) {
3292 char *filename = tree_path(line);
3294 if (line->type == LINE_TREE_DIR) {
3295 report("Cannot show blame for directory %s", opt_path);
3296 return REQ_NONE;
3297 }
3299 string_copy(opt_ref, view->vid);
3300 string_format(opt_file, "%s%s", opt_path, filename);
3301 return request;
3302 }
3303 if (request == REQ_TREE_PARENT) {
3304 if (*opt_path) {
3305 /* fake 'cd ..' */
3306 request = REQ_ENTER;
3307 line = &view->line[1];
3308 } else {
3309 /* quit view if at top of tree */
3310 return REQ_VIEW_CLOSE;
3311 }
3312 }
3313 if (request != REQ_ENTER)
3314 return request;
3316 /* Cleanup the stack if the tree view is at a different tree. */
3317 while (!*opt_path && tree_stack)
3318 pop_tree_stack_entry();
3320 switch (line->type) {
3321 case LINE_TREE_DIR:
3322 /* Depending on whether it is a subdir or parent (updir?) link
3323 * mangle the path buffer. */
3324 if (line == &view->line[1] && *opt_path) {
3325 pop_tree_stack_entry();
3327 } else {
3328 char *basename = tree_path(line);
3330 push_tree_stack_entry(basename, view->lineno);
3331 }
3333 /* Trees and subtrees share the same ID, so they are not not
3334 * unique like blobs. */
3335 flags = OPEN_RELOAD;
3336 request = REQ_VIEW_TREE;
3337 break;
3339 case LINE_TREE_FILE:
3340 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3341 request = REQ_VIEW_BLOB;
3342 break;
3344 default:
3345 return TRUE;
3346 }
3348 open_view(view, request, flags);
3349 if (request == REQ_VIEW_TREE) {
3350 view->lineno = tree_lineno;
3351 }
3353 return REQ_NONE;
3354 }
3356 static void
3357 tree_select(struct view *view, struct line *line)
3358 {
3359 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3361 if (line->type == LINE_TREE_FILE) {
3362 string_copy_rev(ref_blob, text);
3364 } else if (line->type != LINE_TREE_DIR) {
3365 return;
3366 }
3368 string_copy_rev(view->ref, text);
3369 }
3371 static struct view_ops tree_ops = {
3372 "file",
3373 NULL,
3374 tree_read,
3375 pager_draw,
3376 tree_request,
3377 pager_grep,
3378 tree_select,
3379 };
3381 static bool
3382 blob_read(struct view *view, char *line)
3383 {
3384 if (!line)
3385 return TRUE;
3386 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3387 }
3389 static struct view_ops blob_ops = {
3390 "line",
3391 NULL,
3392 blob_read,
3393 pager_draw,
3394 pager_request,
3395 pager_grep,
3396 pager_select,
3397 };
3399 /*
3400 * Blame backend
3401 *
3402 * Loading the blame view is a two phase job:
3403 *
3404 * 1. File content is read either using opt_file from the
3405 * filesystem or using git-cat-file.
3406 * 2. Then blame information is incrementally added by
3407 * reading output from git-blame.
3408 */
3410 struct blame_commit {
3411 char id[SIZEOF_REV]; /* SHA1 ID. */
3412 char title[128]; /* First line of the commit message. */
3413 char author[75]; /* Author of the commit. */
3414 struct tm time; /* Date from the author ident. */
3415 char filename[128]; /* Name of file. */
3416 };
3418 struct blame {
3419 struct blame_commit *commit;
3420 unsigned int header:1;
3421 char text[1];
3422 };
3424 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3425 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3427 static bool
3428 blame_open(struct view *view)
3429 {
3430 char path[SIZEOF_STR];
3431 char ref[SIZEOF_STR] = "";
3433 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3434 return FALSE;
3436 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3437 return FALSE;
3439 if (*opt_ref) {
3440 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3441 return FALSE;
3442 } else {
3443 view->pipe = fopen(opt_file, "r");
3444 if (!view->pipe &&
3445 !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3446 return FALSE;
3447 }
3449 if (!view->pipe)
3450 view->pipe = popen(view->cmd, "r");
3451 if (!view->pipe)
3452 return FALSE;
3454 if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3455 return FALSE;
3457 string_format(view->ref, "%s ...", opt_file);
3458 string_copy_rev(view->vid, opt_file);
3459 set_nonblocking_input(TRUE);
3461 if (view->line) {
3462 int i;
3464 for (i = 0; i < view->lines; i++)
3465 free(view->line[i].data);
3466 free(view->line);
3467 }
3469 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3470 view->offset = view->lines = view->lineno = 0;
3471 view->line = NULL;
3472 view->start_time = time(NULL);
3474 return TRUE;
3475 }
3477 static struct blame_commit *
3478 get_blame_commit(struct view *view, const char *id)
3479 {
3480 size_t i;
3482 for (i = 0; i < view->lines; i++) {
3483 struct blame *blame = view->line[i].data;
3485 if (!blame->commit)
3486 continue;
3488 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3489 return blame->commit;
3490 }
3492 {
3493 struct blame_commit *commit = calloc(1, sizeof(*commit));
3495 if (commit)
3496 string_ncopy(commit->id, id, SIZEOF_REV);
3497 return commit;
3498 }
3499 }
3501 static bool
3502 parse_number(char **posref, size_t *number, size_t min, size_t max)
3503 {
3504 char *pos = *posref;
3506 *posref = NULL;
3507 pos = strchr(pos + 1, ' ');
3508 if (!pos || !isdigit(pos[1]))
3509 return FALSE;
3510 *number = atoi(pos + 1);
3511 if (*number < min || *number > max)
3512 return FALSE;
3514 *posref = pos;
3515 return TRUE;
3516 }
3518 static struct blame_commit *
3519 parse_blame_commit(struct view *view, char *text, int *blamed)
3520 {
3521 struct blame_commit *commit;
3522 struct blame *blame;
3523 char *pos = text + SIZEOF_REV - 1;
3524 size_t lineno;
3525 size_t group;
3527 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3528 return NULL;
3530 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3531 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3532 return NULL;
3534 commit = get_blame_commit(view, text);
3535 if (!commit)
3536 return NULL;
3538 *blamed += group;
3539 while (group--) {
3540 struct line *line = &view->line[lineno + group - 1];
3542 blame = line->data;
3543 blame->commit = commit;
3544 blame->header = !group;
3545 line->dirty = 1;
3546 }
3548 return commit;
3549 }
3551 static bool
3552 blame_read_file(struct view *view, char *line)
3553 {
3554 if (!line) {
3555 FILE *pipe = NULL;
3557 if (view->lines > 0)
3558 pipe = popen(view->cmd, "r");
3559 else if (!view->parent)
3560 die("No blame exist for %s", view->vid);
3561 view->cmd[0] = 0;
3562 if (!pipe) {
3563 report("Failed to load blame data");
3564 return TRUE;
3565 }
3567 fclose(view->pipe);
3568 view->pipe = pipe;
3569 return FALSE;
3571 } else {
3572 size_t linelen = strlen(line);
3573 struct blame *blame = malloc(sizeof(*blame) + linelen);
3575 if (!line)
3576 return FALSE;
3578 blame->commit = NULL;
3579 strncpy(blame->text, line, linelen);
3580 blame->text[linelen] = 0;
3581 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3582 }
3583 }
3585 static bool
3586 match_blame_header(const char *name, char **line)
3587 {
3588 size_t namelen = strlen(name);
3589 bool matched = !strncmp(name, *line, namelen);
3591 if (matched)
3592 *line += namelen;
3594 return matched;
3595 }
3597 static bool
3598 blame_read(struct view *view, char *line)
3599 {
3600 static struct blame_commit *commit = NULL;
3601 static int blamed = 0;
3602 static time_t author_time;
3604 if (*view->cmd)
3605 return blame_read_file(view, line);
3607 if (!line) {
3608 /* Reset all! */
3609 commit = NULL;
3610 blamed = 0;
3611 string_format(view->ref, "%s", view->vid);
3612 if (view_is_displayed(view)) {
3613 update_view_title(view);
3614 redraw_view_from(view, 0);
3615 }
3616 return TRUE;
3617 }
3619 if (!commit) {
3620 commit = parse_blame_commit(view, line, &blamed);
3621 string_format(view->ref, "%s %2d%%", view->vid,
3622 blamed * 100 / view->lines);
3624 } else if (match_blame_header("author ", &line)) {
3625 string_ncopy(commit->author, line, strlen(line));
3627 } else if (match_blame_header("author-time ", &line)) {
3628 author_time = (time_t) atol(line);
3630 } else if (match_blame_header("author-tz ", &line)) {
3631 long tz;
3633 tz = ('0' - line[1]) * 60 * 60 * 10;
3634 tz += ('0' - line[2]) * 60 * 60;
3635 tz += ('0' - line[3]) * 60;
3636 tz += ('0' - line[4]) * 60;
3638 if (line[0] == '-')
3639 tz = -tz;
3641 author_time -= tz;
3642 gmtime_r(&author_time, &commit->time);
3644 } else if (match_blame_header("summary ", &line)) {
3645 string_ncopy(commit->title, line, strlen(line));
3647 } else if (match_blame_header("filename ", &line)) {
3648 string_ncopy(commit->filename, line, strlen(line));
3649 commit = NULL;
3650 }
3652 return TRUE;
3653 }
3655 static bool
3656 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3657 {
3658 struct blame *blame = line->data;
3659 struct tm *time = NULL;
3660 char *id = NULL, *author = NULL;
3662 if (blame->commit && *blame->commit->filename) {
3663 id = blame->commit->id;
3664 author = blame->commit->author;
3665 time = &blame->commit->time;
3666 }
3668 if (opt_date && draw_date(view, time))
3669 return TRUE;
3671 if (opt_author &&
3672 draw_field(view, LINE_MAIN_AUTHOR, author, AUTHOR_COLS, TRUE))
3673 return TRUE;
3675 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3676 return TRUE;
3678 if (draw_lineno(view, lineno))
3679 return TRUE;
3681 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3682 return TRUE;
3683 }
3685 static enum request
3686 blame_request(struct view *view, enum request request, struct line *line)
3687 {
3688 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3689 struct blame *blame = line->data;
3691 switch (request) {
3692 case REQ_ENTER:
3693 if (!blame->commit) {
3694 report("No commit loaded yet");
3695 break;
3696 }
3698 if (!strcmp(blame->commit->id, NULL_ID)) {
3699 char path[SIZEOF_STR];
3701 if (sq_quote(path, 0, view->vid) >= sizeof(path))
3702 break;
3703 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3704 }
3706 open_view(view, REQ_VIEW_DIFF, flags);
3707 break;
3709 default:
3710 return request;
3711 }
3713 return REQ_NONE;
3714 }
3716 static bool
3717 blame_grep(struct view *view, struct line *line)
3718 {
3719 struct blame *blame = line->data;
3720 struct blame_commit *commit = blame->commit;
3721 regmatch_t pmatch;
3723 #define MATCH(text, on) \
3724 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3726 if (commit) {
3727 char buf[DATE_COLS + 1];
3729 if (MATCH(commit->title, 1) ||
3730 MATCH(commit->author, opt_author) ||
3731 MATCH(commit->id, opt_date))
3732 return TRUE;
3734 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3735 MATCH(buf, 1))
3736 return TRUE;
3737 }
3739 return MATCH(blame->text, 1);
3741 #undef MATCH
3742 }
3744 static void
3745 blame_select(struct view *view, struct line *line)
3746 {
3747 struct blame *blame = line->data;
3748 struct blame_commit *commit = blame->commit;
3750 if (!commit)
3751 return;
3753 if (!strcmp(commit->id, NULL_ID))
3754 string_ncopy(ref_commit, "HEAD", 4);
3755 else
3756 string_copy_rev(ref_commit, commit->id);
3757 }
3759 static struct view_ops blame_ops = {
3760 "line",
3761 blame_open,
3762 blame_read,
3763 blame_draw,
3764 blame_request,
3765 blame_grep,
3766 blame_select,
3767 };
3769 /*
3770 * Status backend
3771 */
3773 struct status {
3774 char status;
3775 struct {
3776 mode_t mode;
3777 char rev[SIZEOF_REV];
3778 char name[SIZEOF_STR];
3779 } old;
3780 struct {
3781 mode_t mode;
3782 char rev[SIZEOF_REV];
3783 char name[SIZEOF_STR];
3784 } new;
3785 };
3787 static char status_onbranch[SIZEOF_STR];
3788 static struct status stage_status;
3789 static enum line_type stage_line_type;
3791 /* Get fields from the diff line:
3792 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3793 */
3794 static inline bool
3795 status_get_diff(struct status *file, char *buf, size_t bufsize)
3796 {
3797 char *old_mode = buf + 1;
3798 char *new_mode = buf + 8;
3799 char *old_rev = buf + 15;
3800 char *new_rev = buf + 56;
3801 char *status = buf + 97;
3803 if (bufsize < 99 ||
3804 old_mode[-1] != ':' ||
3805 new_mode[-1] != ' ' ||
3806 old_rev[-1] != ' ' ||
3807 new_rev[-1] != ' ' ||
3808 status[-1] != ' ')
3809 return FALSE;
3811 file->status = *status;
3813 string_copy_rev(file->old.rev, old_rev);
3814 string_copy_rev(file->new.rev, new_rev);
3816 file->old.mode = strtoul(old_mode, NULL, 8);
3817 file->new.mode = strtoul(new_mode, NULL, 8);
3819 file->old.name[0] = file->new.name[0] = 0;
3821 return TRUE;
3822 }
3824 static bool
3825 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3826 {
3827 struct status *file = NULL;
3828 struct status *unmerged = NULL;
3829 char buf[SIZEOF_STR * 4];
3830 size_t bufsize = 0;
3831 FILE *pipe;
3833 pipe = popen(cmd, "r");
3834 if (!pipe)
3835 return FALSE;
3837 add_line_data(view, NULL, type);
3839 while (!feof(pipe) && !ferror(pipe)) {
3840 char *sep;
3841 size_t readsize;
3843 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3844 if (!readsize)
3845 break;
3846 bufsize += readsize;
3848 /* Process while we have NUL chars. */
3849 while ((sep = memchr(buf, 0, bufsize))) {
3850 size_t sepsize = sep - buf + 1;
3852 if (!file) {
3853 if (!realloc_lines(view, view->line_size + 1))
3854 goto error_out;
3856 file = calloc(1, sizeof(*file));
3857 if (!file)
3858 goto error_out;
3860 add_line_data(view, file, type);
3861 }
3863 /* Parse diff info part. */
3864 if (status) {
3865 file->status = status;
3866 if (status == 'A')
3867 string_copy(file->old.rev, NULL_ID);
3869 } else if (!file->status) {
3870 if (!status_get_diff(file, buf, sepsize))
3871 goto error_out;
3873 bufsize -= sepsize;
3874 memmove(buf, sep + 1, bufsize);
3876 sep = memchr(buf, 0, bufsize);
3877 if (!sep)
3878 break;
3879 sepsize = sep - buf + 1;
3881 /* Collapse all 'M'odified entries that
3882 * follow a associated 'U'nmerged entry.
3883 */
3884 if (file->status == 'U') {
3885 unmerged = file;
3887 } else if (unmerged) {
3888 int collapse = !strcmp(buf, unmerged->new.name);
3890 unmerged = NULL;
3891 if (collapse) {
3892 free(file);
3893 view->lines--;
3894 continue;
3895 }
3896 }
3897 }
3899 /* Grab the old name for rename/copy. */
3900 if (!*file->old.name &&
3901 (file->status == 'R' || file->status == 'C')) {
3902 sepsize = sep - buf + 1;
3903 string_ncopy(file->old.name, buf, sepsize);
3904 bufsize -= sepsize;
3905 memmove(buf, sep + 1, bufsize);
3907 sep = memchr(buf, 0, bufsize);
3908 if (!sep)
3909 break;
3910 sepsize = sep - buf + 1;
3911 }
3913 /* git-ls-files just delivers a NUL separated
3914 * list of file names similar to the second half
3915 * of the git-diff-* output. */
3916 string_ncopy(file->new.name, buf, sepsize);
3917 if (!*file->old.name)
3918 string_copy(file->old.name, file->new.name);
3919 bufsize -= sepsize;
3920 memmove(buf, sep + 1, bufsize);
3921 file = NULL;
3922 }
3923 }
3925 if (ferror(pipe)) {
3926 error_out:
3927 pclose(pipe);
3928 return FALSE;
3929 }
3931 if (!view->line[view->lines - 1].data)
3932 add_line_data(view, NULL, LINE_STAT_NONE);
3934 pclose(pipe);
3935 return TRUE;
3936 }
3938 /* Don't show unmerged entries in the staged section. */
3939 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3940 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3941 #define STATUS_LIST_OTHER_CMD \
3942 "git ls-files -z --others --exclude-per-directory=.gitignore"
3943 #define STATUS_LIST_NO_HEAD_CMD \
3944 "git ls-files -z --cached --exclude-per-directory=.gitignore"
3946 #define STATUS_DIFF_INDEX_SHOW_CMD \
3947 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3949 #define STATUS_DIFF_FILES_SHOW_CMD \
3950 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3952 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3953 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3955 /* First parse staged info using git-diff-index(1), then parse unstaged
3956 * info using git-diff-files(1), and finally untracked files using
3957 * git-ls-files(1). */
3958 static bool
3959 status_open(struct view *view)
3960 {
3961 struct stat statbuf;
3962 char exclude[SIZEOF_STR];
3963 char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3964 char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3965 unsigned long prev_lineno = view->lineno;
3966 char indexstatus = 0;
3967 size_t i;
3969 for (i = 0; i < view->lines; i++)
3970 free(view->line[i].data);
3971 free(view->line);
3972 view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3973 view->line = NULL;
3975 if (!realloc_lines(view, view->line_size + 7))
3976 return FALSE;
3978 add_line_data(view, NULL, LINE_STAT_HEAD);
3979 if (opt_no_head)
3980 string_copy(status_onbranch, "Initial commit");
3981 else if (!*opt_head)
3982 string_copy(status_onbranch, "Not currently on any branch");
3983 else if (!string_format(status_onbranch, "On branch %s", opt_head))
3984 return FALSE;
3986 if (opt_no_head) {
3987 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
3988 indexstatus = 'A';
3989 }
3991 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3992 return FALSE;
3994 if (stat(exclude, &statbuf) >= 0) {
3995 size_t cmdsize = strlen(othercmd);
3997 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
3998 sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
3999 return FALSE;
4001 cmdsize = strlen(indexcmd);
4002 if (opt_no_head &&
4003 (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
4004 sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
4005 return FALSE;
4006 }
4008 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4010 if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
4011 !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4012 !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
4013 return FALSE;
4015 /* If all went well restore the previous line number to stay in
4016 * the context or select a line with something that can be
4017 * updated. */
4018 if (prev_lineno >= view->lines)
4019 prev_lineno = view->lines - 1;
4020 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4021 prev_lineno++;
4022 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4023 prev_lineno--;
4025 /* If the above fails, always skip the "On branch" line. */
4026 if (prev_lineno < view->lines)
4027 view->lineno = prev_lineno;
4028 else
4029 view->lineno = 1;
4031 if (view->lineno < view->offset)
4032 view->offset = view->lineno;
4033 else if (view->offset + view->height <= view->lineno)
4034 view->offset = view->lineno - view->height + 1;
4036 return TRUE;
4037 }
4039 static bool
4040 status_draw(struct view *view, struct line *line, unsigned int lineno)
4041 {
4042 struct status *status = line->data;
4043 enum line_type type;
4044 char *text;
4046 if (!status) {
4047 switch (line->type) {
4048 case LINE_STAT_STAGED:
4049 type = LINE_STAT_SECTION;
4050 text = "Changes to be committed:";
4051 break;
4053 case LINE_STAT_UNSTAGED:
4054 type = LINE_STAT_SECTION;
4055 text = "Changed but not updated:";
4056 break;
4058 case LINE_STAT_UNTRACKED:
4059 type = LINE_STAT_SECTION;
4060 text = "Untracked files:";
4061 break;
4063 case LINE_STAT_NONE:
4064 type = LINE_DEFAULT;
4065 text = " (no files)";
4066 break;
4068 case LINE_STAT_HEAD:
4069 type = LINE_STAT_HEAD;
4070 text = status_onbranch;
4071 break;
4073 default:
4074 return FALSE;
4075 }
4076 } else {
4077 char buf[] = { status->status, ' ', ' ', ' ', 0 };
4079 if (draw_text(view, line->type, buf, TRUE))
4080 return TRUE;
4081 type = LINE_DEFAULT;
4082 text = status->new.name;
4083 }
4085 draw_text(view, type, text, TRUE);
4086 return TRUE;
4087 }
4089 static enum request
4090 status_enter(struct view *view, struct line *line)
4091 {
4092 struct status *status = line->data;
4093 char oldpath[SIZEOF_STR] = "";
4094 char newpath[SIZEOF_STR] = "";
4095 char *info;
4096 size_t cmdsize = 0;
4097 enum open_flags split;
4099 if (line->type == LINE_STAT_NONE ||
4100 (!status && line[1].type == LINE_STAT_NONE)) {
4101 report("No file to diff");
4102 return REQ_NONE;
4103 }
4105 if (status) {
4106 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4107 return REQ_QUIT;
4108 /* Diffs for unmerged entries are empty when pasing the
4109 * new path, so leave it empty. */
4110 if (status->status != 'U' &&
4111 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4112 return REQ_QUIT;
4113 }
4115 if (opt_cdup[0] &&
4116 line->type != LINE_STAT_UNTRACKED &&
4117 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4118 return REQ_QUIT;
4120 switch (line->type) {
4121 case LINE_STAT_STAGED:
4122 if (opt_no_head) {
4123 if (!string_format_from(opt_cmd, &cmdsize,
4124 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4125 newpath))
4126 return REQ_QUIT;
4127 } else {
4128 if (!string_format_from(opt_cmd, &cmdsize,
4129 STATUS_DIFF_INDEX_SHOW_CMD,
4130 oldpath, newpath))
4131 return REQ_QUIT;
4132 }
4134 if (status)
4135 info = "Staged changes to %s";
4136 else
4137 info = "Staged changes";
4138 break;
4140 case LINE_STAT_UNSTAGED:
4141 if (!string_format_from(opt_cmd, &cmdsize,
4142 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4143 return REQ_QUIT;
4144 if (status)
4145 info = "Unstaged changes to %s";
4146 else
4147 info = "Unstaged changes";
4148 break;
4150 case LINE_STAT_UNTRACKED:
4151 if (opt_pipe)
4152 return REQ_QUIT;
4154 if (!status) {
4155 report("No file to show");
4156 return REQ_NONE;
4157 }
4159 opt_pipe = fopen(status->new.name, "r");
4160 info = "Untracked file %s";
4161 break;
4163 case LINE_STAT_HEAD:
4164 return REQ_NONE;
4166 default:
4167 die("line type %d not handled in switch", line->type);
4168 }
4170 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4171 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4172 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4173 if (status) {
4174 stage_status = *status;
4175 } else {
4176 memset(&stage_status, 0, sizeof(stage_status));
4177 }
4179 stage_line_type = line->type;
4180 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4181 }
4183 return REQ_NONE;
4184 }
4186 static bool
4187 status_exists(struct status *status, enum line_type type)
4188 {
4189 struct view *view = VIEW(REQ_VIEW_STATUS);
4190 struct line *line;
4192 for (line = view->line; line < view->line + view->lines; line++) {
4193 struct status *pos = line->data;
4195 if (line->type == type && pos &&
4196 !strcmp(status->new.name, pos->new.name))
4197 return TRUE;
4198 }
4200 return FALSE;
4201 }
4204 static FILE *
4205 status_update_prepare(enum line_type type)
4206 {
4207 char cmd[SIZEOF_STR];
4208 size_t cmdsize = 0;
4210 if (opt_cdup[0] &&
4211 type != LINE_STAT_UNTRACKED &&
4212 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4213 return NULL;
4215 switch (type) {
4216 case LINE_STAT_STAGED:
4217 string_add(cmd, cmdsize, "git update-index -z --index-info");
4218 break;
4220 case LINE_STAT_UNSTAGED:
4221 case LINE_STAT_UNTRACKED:
4222 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4223 break;
4225 default:
4226 die("line type %d not handled in switch", type);
4227 }
4229 return popen(cmd, "w");
4230 }
4232 static bool
4233 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4234 {
4235 char buf[SIZEOF_STR];
4236 size_t bufsize = 0;
4237 size_t written = 0;
4239 switch (type) {
4240 case LINE_STAT_STAGED:
4241 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4242 status->old.mode,
4243 status->old.rev,
4244 status->old.name, 0))
4245 return FALSE;
4246 break;
4248 case LINE_STAT_UNSTAGED:
4249 case LINE_STAT_UNTRACKED:
4250 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4251 return FALSE;
4252 break;
4254 default:
4255 die("line type %d not handled in switch", type);
4256 }
4258 while (!ferror(pipe) && written < bufsize) {
4259 written += fwrite(buf + written, 1, bufsize - written, pipe);
4260 }
4262 return written == bufsize;
4263 }
4265 static bool
4266 status_update_file(struct status *status, enum line_type type)
4267 {
4268 FILE *pipe = status_update_prepare(type);
4269 bool result;
4271 if (!pipe)
4272 return FALSE;
4274 result = status_update_write(pipe, status, type);
4275 pclose(pipe);
4276 return result;
4277 }
4279 static bool
4280 status_update_files(struct view *view, struct line *line)
4281 {
4282 FILE *pipe = status_update_prepare(line->type);
4283 bool result = TRUE;
4284 struct line *pos = view->line + view->lines;
4285 int files = 0;
4286 int file, done;
4288 if (!pipe)
4289 return FALSE;
4291 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4292 files++;
4294 for (file = 0, done = 0; result && file < files; line++, file++) {
4295 int almost_done = file * 100 / files;
4297 if (almost_done > done) {
4298 done = almost_done;
4299 string_format(view->ref, "updating file %u of %u (%d%% done)",
4300 file, files, done);
4301 update_view_title(view);
4302 }
4303 result = status_update_write(pipe, line->data, line->type);
4304 }
4306 pclose(pipe);
4307 return result;
4308 }
4310 static bool
4311 status_update(struct view *view)
4312 {
4313 struct line *line = &view->line[view->lineno];
4315 assert(view->lines);
4317 if (!line->data) {
4318 /* This should work even for the "On branch" line. */
4319 if (line < view->line + view->lines && !line[1].data) {
4320 report("Nothing to update");
4321 return FALSE;
4322 }
4324 if (!status_update_files(view, line + 1)) {
4325 report("Failed to update file status");
4326 return FALSE;
4327 }
4329 } else if (!status_update_file(line->data, line->type)) {
4330 report("Failed to update file status");
4331 return FALSE;
4332 }
4334 return TRUE;
4335 }
4337 static enum request
4338 status_request(struct view *view, enum request request, struct line *line)
4339 {
4340 struct status *status = line->data;
4342 switch (request) {
4343 case REQ_STATUS_UPDATE:
4344 if (!status_update(view))
4345 return REQ_NONE;
4346 break;
4348 case REQ_STATUS_MERGE:
4349 if (!status || status->status != 'U') {
4350 report("Merging only possible for files with unmerged status ('U').");
4351 return REQ_NONE;
4352 }
4353 open_mergetool(status->new.name);
4354 break;
4356 case REQ_EDIT:
4357 if (!status)
4358 return request;
4360 open_editor(status->status != '?', status->new.name);
4361 break;
4363 case REQ_VIEW_BLAME:
4364 if (status) {
4365 string_copy(opt_file, status->new.name);
4366 opt_ref[0] = 0;
4367 }
4368 return request;
4370 case REQ_ENTER:
4371 /* After returning the status view has been split to
4372 * show the stage view. No further reloading is
4373 * necessary. */
4374 status_enter(view, line);
4375 return REQ_NONE;
4377 case REQ_REFRESH:
4378 /* Simply reload the view. */
4379 break;
4381 default:
4382 return request;
4383 }
4385 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4387 return REQ_NONE;
4388 }
4390 static void
4391 status_select(struct view *view, struct line *line)
4392 {
4393 struct status *status = line->data;
4394 char file[SIZEOF_STR] = "all files";
4395 char *text;
4396 char *key;
4398 if (status && !string_format(file, "'%s'", status->new.name))
4399 return;
4401 if (!status && line[1].type == LINE_STAT_NONE)
4402 line++;
4404 switch (line->type) {
4405 case LINE_STAT_STAGED:
4406 text = "Press %s to unstage %s for commit";
4407 break;
4409 case LINE_STAT_UNSTAGED:
4410 text = "Press %s to stage %s for commit";
4411 break;
4413 case LINE_STAT_UNTRACKED:
4414 text = "Press %s to stage %s for addition";
4415 break;
4417 case LINE_STAT_HEAD:
4418 case LINE_STAT_NONE:
4419 text = "Nothing to update";
4420 break;
4422 default:
4423 die("line type %d not handled in switch", line->type);
4424 }
4426 if (status && status->status == 'U') {
4427 text = "Press %s to resolve conflict in %s";
4428 key = get_key(REQ_STATUS_MERGE);
4430 } else {
4431 key = get_key(REQ_STATUS_UPDATE);
4432 }
4434 string_format(view->ref, text, key, file);
4435 }
4437 static bool
4438 status_grep(struct view *view, struct line *line)
4439 {
4440 struct status *status = line->data;
4441 enum { S_STATUS, S_NAME, S_END } state;
4442 char buf[2] = "?";
4443 regmatch_t pmatch;
4445 if (!status)
4446 return FALSE;
4448 for (state = S_STATUS; state < S_END; state++) {
4449 char *text;
4451 switch (state) {
4452 case S_NAME: text = status->new.name; break;
4453 case S_STATUS:
4454 buf[0] = status->status;
4455 text = buf;
4456 break;
4458 default:
4459 return FALSE;
4460 }
4462 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4463 return TRUE;
4464 }
4466 return FALSE;
4467 }
4469 static struct view_ops status_ops = {
4470 "file",
4471 status_open,
4472 NULL,
4473 status_draw,
4474 status_request,
4475 status_grep,
4476 status_select,
4477 };
4480 static bool
4481 stage_diff_line(FILE *pipe, struct line *line)
4482 {
4483 char *buf = line->data;
4484 size_t bufsize = strlen(buf);
4485 size_t written = 0;
4487 while (!ferror(pipe) && written < bufsize) {
4488 written += fwrite(buf + written, 1, bufsize - written, pipe);
4489 }
4491 fputc('\n', pipe);
4493 return written == bufsize;
4494 }
4496 static bool
4497 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4498 {
4499 while (line < end) {
4500 if (!stage_diff_line(pipe, line++))
4501 return FALSE;
4502 if (line->type == LINE_DIFF_CHUNK ||
4503 line->type == LINE_DIFF_HEADER)
4504 break;
4505 }
4507 return TRUE;
4508 }
4510 static struct line *
4511 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4512 {
4513 for (; view->line < line; line--)
4514 if (line->type == type)
4515 return line;
4517 return NULL;
4518 }
4520 static bool
4521 stage_update_chunk(struct view *view, struct line *chunk)
4522 {
4523 char cmd[SIZEOF_STR];
4524 size_t cmdsize = 0;
4525 struct line *diff_hdr;
4526 FILE *pipe;
4528 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4529 if (!diff_hdr)
4530 return FALSE;
4532 if (opt_cdup[0] &&
4533 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4534 return FALSE;
4536 if (!string_format_from(cmd, &cmdsize,
4537 "git apply --whitespace=nowarn --cached %s - && "
4538 "git update-index -q --unmerged --refresh 2>/dev/null",
4539 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4540 return FALSE;
4542 pipe = popen(cmd, "w");
4543 if (!pipe)
4544 return FALSE;
4546 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4547 !stage_diff_write(pipe, chunk, view->line + view->lines))
4548 chunk = NULL;
4550 pclose(pipe);
4552 return chunk ? TRUE : FALSE;
4553 }
4555 static bool
4556 stage_update(struct view *view, struct line *line)
4557 {
4558 struct line *chunk = NULL;
4560 if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4561 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4563 if (chunk) {
4564 if (!stage_update_chunk(view, chunk)) {
4565 report("Failed to apply chunk");
4566 return FALSE;
4567 }
4569 } else if (!stage_status.status) {
4570 view = VIEW(REQ_VIEW_STATUS);
4572 for (line = view->line; line < view->line + view->lines; line++)
4573 if (line->type == stage_line_type)
4574 break;
4576 if (!status_update_files(view, line + 1)) {
4577 report("Failed to update files");
4578 return FALSE;
4579 }
4581 } else if (!status_update_file(&stage_status, stage_line_type)) {
4582 report("Failed to update file");
4583 return FALSE;
4584 }
4586 return TRUE;
4587 }
4589 static enum request
4590 stage_request(struct view *view, enum request request, struct line *line)
4591 {
4592 switch (request) {
4593 case REQ_STATUS_UPDATE:
4594 if (!stage_update(view, line))
4595 return REQ_NONE;
4596 break;
4598 case REQ_EDIT:
4599 if (!stage_status.new.name[0])
4600 return request;
4602 open_editor(stage_status.status != '?', stage_status.new.name);
4603 break;
4605 case REQ_REFRESH:
4606 /* Reload everything ... */
4607 break;
4609 case REQ_VIEW_BLAME:
4610 if (stage_status.new.name[0]) {
4611 string_copy(opt_file, stage_status.new.name);
4612 opt_ref[0] = 0;
4613 }
4614 return request;
4616 case REQ_ENTER:
4617 return pager_request(view, request, line);
4619 default:
4620 return request;
4621 }
4623 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4625 /* Check whether the staged entry still exists, and close the
4626 * stage view if it doesn't. */
4627 if (!status_exists(&stage_status, stage_line_type))
4628 return REQ_VIEW_CLOSE;
4630 if (stage_line_type == LINE_STAT_UNTRACKED)
4631 opt_pipe = fopen(stage_status.new.name, "r");
4632 else
4633 string_copy(opt_cmd, view->cmd);
4634 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4636 return REQ_NONE;
4637 }
4639 static struct view_ops stage_ops = {
4640 "line",
4641 NULL,
4642 pager_read,
4643 pager_draw,
4644 stage_request,
4645 pager_grep,
4646 pager_select,
4647 };
4650 /*
4651 * Revision graph
4652 */
4654 struct commit {
4655 char id[SIZEOF_REV]; /* SHA1 ID. */
4656 char title[128]; /* First line of the commit message. */
4657 char author[75]; /* Author of the commit. */
4658 struct tm time; /* Date from the author ident. */
4659 struct ref **refs; /* Repository references. */
4660 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
4661 size_t graph_size; /* The width of the graph array. */
4662 bool has_parents; /* Rewritten --parents seen. */
4663 };
4665 /* Size of rev graph with no "padding" columns */
4666 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4668 struct rev_graph {
4669 struct rev_graph *prev, *next, *parents;
4670 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4671 size_t size;
4672 struct commit *commit;
4673 size_t pos;
4674 unsigned int boundary:1;
4675 };
4677 /* Parents of the commit being visualized. */
4678 static struct rev_graph graph_parents[4];
4680 /* The current stack of revisions on the graph. */
4681 static struct rev_graph graph_stacks[4] = {
4682 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4683 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4684 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4685 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4686 };
4688 static inline bool
4689 graph_parent_is_merge(struct rev_graph *graph)
4690 {
4691 return graph->parents->size > 1;
4692 }
4694 static inline void
4695 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4696 {
4697 struct commit *commit = graph->commit;
4699 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4700 commit->graph[commit->graph_size++] = symbol;
4701 }
4703 static void
4704 done_rev_graph(struct rev_graph *graph)
4705 {
4706 if (graph_parent_is_merge(graph) &&
4707 graph->pos < graph->size - 1 &&
4708 graph->next->size == graph->size + graph->parents->size - 1) {
4709 size_t i = graph->pos + graph->parents->size - 1;
4711 graph->commit->graph_size = i * 2;
4712 while (i < graph->next->size - 1) {
4713 append_to_rev_graph(graph, ' ');
4714 append_to_rev_graph(graph, '\\');
4715 i++;
4716 }
4717 }
4719 graph->size = graph->pos = 0;
4720 graph->commit = NULL;
4721 memset(graph->parents, 0, sizeof(*graph->parents));
4722 }
4724 static void
4725 push_rev_graph(struct rev_graph *graph, char *parent)
4726 {
4727 int i;
4729 /* "Collapse" duplicate parents lines.
4730 *
4731 * FIXME: This needs to also update update the drawn graph but
4732 * for now it just serves as a method for pruning graph lines. */
4733 for (i = 0; i < graph->size; i++)
4734 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4735 return;
4737 if (graph->size < SIZEOF_REVITEMS) {
4738 string_copy_rev(graph->rev[graph->size++], parent);
4739 }
4740 }
4742 static chtype
4743 get_rev_graph_symbol(struct rev_graph *graph)
4744 {
4745 chtype symbol;
4747 if (graph->boundary)
4748 symbol = REVGRAPH_BOUND;
4749 else if (graph->parents->size == 0)
4750 symbol = REVGRAPH_INIT;
4751 else if (graph_parent_is_merge(graph))
4752 symbol = REVGRAPH_MERGE;
4753 else if (graph->pos >= graph->size)
4754 symbol = REVGRAPH_BRANCH;
4755 else
4756 symbol = REVGRAPH_COMMIT;
4758 return symbol;
4759 }
4761 static void
4762 draw_rev_graph(struct rev_graph *graph)
4763 {
4764 struct rev_filler {
4765 chtype separator, line;
4766 };
4767 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4768 static struct rev_filler fillers[] = {
4769 { ' ', '|' },
4770 { '`', '.' },
4771 { '\'', ' ' },
4772 { '/', ' ' },
4773 };
4774 chtype symbol = get_rev_graph_symbol(graph);
4775 struct rev_filler *filler;
4776 size_t i;
4778 if (opt_line_graphics)
4779 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4781 filler = &fillers[DEFAULT];
4783 for (i = 0; i < graph->pos; i++) {
4784 append_to_rev_graph(graph, filler->line);
4785 if (graph_parent_is_merge(graph->prev) &&
4786 graph->prev->pos == i)
4787 filler = &fillers[RSHARP];
4789 append_to_rev_graph(graph, filler->separator);
4790 }
4792 /* Place the symbol for this revision. */
4793 append_to_rev_graph(graph, symbol);
4795 if (graph->prev->size > graph->size)
4796 filler = &fillers[RDIAG];
4797 else
4798 filler = &fillers[DEFAULT];
4800 i++;
4802 for (; i < graph->size; i++) {
4803 append_to_rev_graph(graph, filler->separator);
4804 append_to_rev_graph(graph, filler->line);
4805 if (graph_parent_is_merge(graph->prev) &&
4806 i < graph->prev->pos + graph->parents->size)
4807 filler = &fillers[RSHARP];
4808 if (graph->prev->size > graph->size)
4809 filler = &fillers[LDIAG];
4810 }
4812 if (graph->prev->size > graph->size) {
4813 append_to_rev_graph(graph, filler->separator);
4814 if (filler->line != ' ')
4815 append_to_rev_graph(graph, filler->line);
4816 }
4817 }
4819 /* Prepare the next rev graph */
4820 static void
4821 prepare_rev_graph(struct rev_graph *graph)
4822 {
4823 size_t i;
4825 /* First, traverse all lines of revisions up to the active one. */
4826 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4827 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4828 break;
4830 push_rev_graph(graph->next, graph->rev[graph->pos]);
4831 }
4833 /* Interleave the new revision parent(s). */
4834 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4835 push_rev_graph(graph->next, graph->parents->rev[i]);
4837 /* Lastly, put any remaining revisions. */
4838 for (i = graph->pos + 1; i < graph->size; i++)
4839 push_rev_graph(graph->next, graph->rev[i]);
4840 }
4842 static void
4843 update_rev_graph(struct rev_graph *graph)
4844 {
4845 /* If this is the finalizing update ... */
4846 if (graph->commit)
4847 prepare_rev_graph(graph);
4849 /* Graph visualization needs a one rev look-ahead,
4850 * so the first update doesn't visualize anything. */
4851 if (!graph->prev->commit)
4852 return;
4854 draw_rev_graph(graph->prev);
4855 done_rev_graph(graph->prev->prev);
4856 }
4859 /*
4860 * Main view backend
4861 */
4863 static bool
4864 main_draw(struct view *view, struct line *line, unsigned int lineno)
4865 {
4866 struct commit *commit = line->data;
4868 if (!*commit->author)
4869 return FALSE;
4871 if (opt_date && draw_date(view, &commit->time))
4872 return TRUE;
4874 if (opt_author &&
4875 draw_field(view, LINE_MAIN_AUTHOR, commit->author, AUTHOR_COLS, TRUE))
4876 return TRUE;
4878 if (opt_rev_graph && commit->graph_size &&
4879 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
4880 return TRUE;
4882 if (opt_show_refs && commit->refs) {
4883 size_t i = 0;
4885 do {
4886 enum line_type type;
4888 if (commit->refs[i]->head)
4889 type = LINE_MAIN_HEAD;
4890 else if (commit->refs[i]->ltag)
4891 type = LINE_MAIN_LOCAL_TAG;
4892 else if (commit->refs[i]->tag)
4893 type = LINE_MAIN_TAG;
4894 else if (commit->refs[i]->tracked)
4895 type = LINE_MAIN_TRACKED;
4896 else if (commit->refs[i]->remote)
4897 type = LINE_MAIN_REMOTE;
4898 else
4899 type = LINE_MAIN_REF;
4901 if (draw_text(view, type, "[", TRUE) ||
4902 draw_text(view, type, commit->refs[i]->name, TRUE) ||
4903 draw_text(view, type, "]", TRUE))
4904 return TRUE;
4906 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
4907 return TRUE;
4908 } while (commit->refs[i++]->next);
4909 }
4911 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
4912 return TRUE;
4913 }
4915 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4916 static bool
4917 main_read(struct view *view, char *line)
4918 {
4919 static struct rev_graph *graph = graph_stacks;
4920 enum line_type type;
4921 struct commit *commit;
4923 if (!line) {
4924 if (!view->lines && !view->parent)
4925 die("No revisions match the given arguments.");
4926 update_rev_graph(graph);
4927 return TRUE;
4928 }
4930 type = get_line_type(line);
4931 if (type == LINE_COMMIT) {
4932 commit = calloc(1, sizeof(struct commit));
4933 if (!commit)
4934 return FALSE;
4936 line += STRING_SIZE("commit ");
4937 if (*line == '-') {
4938 graph->boundary = 1;
4939 line++;
4940 }
4942 string_copy_rev(commit->id, line);
4943 commit->refs = get_refs(commit->id);
4944 graph->commit = commit;
4945 add_line_data(view, commit, LINE_MAIN_COMMIT);
4947 while ((line = strchr(line, ' '))) {
4948 line++;
4949 push_rev_graph(graph->parents, line);
4950 commit->has_parents = TRUE;
4951 }
4952 return TRUE;
4953 }
4955 if (!view->lines)
4956 return TRUE;
4957 commit = view->line[view->lines - 1].data;
4959 switch (type) {
4960 case LINE_PARENT:
4961 if (commit->has_parents)
4962 break;
4963 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4964 break;
4966 case LINE_AUTHOR:
4967 {
4968 /* Parse author lines where the name may be empty:
4969 * author <email@address.tld> 1138474660 +0100
4970 */
4971 char *ident = line + STRING_SIZE("author ");
4972 char *nameend = strchr(ident, '<');
4973 char *emailend = strchr(ident, '>');
4975 if (!nameend || !emailend)
4976 break;
4978 update_rev_graph(graph);
4979 graph = graph->next;
4981 *nameend = *emailend = 0;
4982 ident = chomp_string(ident);
4983 if (!*ident) {
4984 ident = chomp_string(nameend + 1);
4985 if (!*ident)
4986 ident = "Unknown";
4987 }
4989 string_ncopy(commit->author, ident, strlen(ident));
4991 /* Parse epoch and timezone */
4992 if (emailend[1] == ' ') {
4993 char *secs = emailend + 2;
4994 char *zone = strchr(secs, ' ');
4995 time_t time = (time_t) atol(secs);
4997 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4998 long tz;
5000 zone++;
5001 tz = ('0' - zone[1]) * 60 * 60 * 10;
5002 tz += ('0' - zone[2]) * 60 * 60;
5003 tz += ('0' - zone[3]) * 60;
5004 tz += ('0' - zone[4]) * 60;
5006 if (zone[0] == '-')
5007 tz = -tz;
5009 time -= tz;
5010 }
5012 gmtime_r(&time, &commit->time);
5013 }
5014 break;
5015 }
5016 default:
5017 /* Fill in the commit title if it has not already been set. */
5018 if (commit->title[0])
5019 break;
5021 /* Require titles to start with a non-space character at the
5022 * offset used by git log. */
5023 if (strncmp(line, " ", 4))
5024 break;
5025 line += 4;
5026 /* Well, if the title starts with a whitespace character,
5027 * try to be forgiving. Otherwise we end up with no title. */
5028 while (isspace(*line))
5029 line++;
5030 if (*line == '\0')
5031 break;
5032 /* FIXME: More graceful handling of titles; append "..." to
5033 * shortened titles, etc. */
5035 string_ncopy(commit->title, line, strlen(line));
5036 }
5038 return TRUE;
5039 }
5041 static enum request
5042 main_request(struct view *view, enum request request, struct line *line)
5043 {
5044 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5046 if (request == REQ_ENTER)
5047 open_view(view, REQ_VIEW_DIFF, flags);
5048 else
5049 return request;
5051 return REQ_NONE;
5052 }
5054 static bool
5055 grep_refs(struct ref **refs, regex_t *regex)
5056 {
5057 regmatch_t pmatch;
5058 size_t i = 0;
5060 if (!refs)
5061 return FALSE;
5062 do {
5063 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5064 return TRUE;
5065 } while (refs[i++]->next);
5067 return FALSE;
5068 }
5070 static bool
5071 main_grep(struct view *view, struct line *line)
5072 {
5073 struct commit *commit = line->data;
5074 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5075 char buf[DATE_COLS + 1];
5076 regmatch_t pmatch;
5078 for (state = S_TITLE; state < S_END; state++) {
5079 char *text;
5081 switch (state) {
5082 case S_TITLE: text = commit->title; break;
5083 case S_AUTHOR:
5084 if (!opt_author)
5085 continue;
5086 text = commit->author;
5087 break;
5088 case S_DATE:
5089 if (!opt_date)
5090 continue;
5091 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5092 continue;
5093 text = buf;
5094 break;
5095 case S_REFS:
5096 if (!opt_show_refs)
5097 continue;
5098 if (grep_refs(commit->refs, view->regex) == TRUE)
5099 return TRUE;
5100 continue;
5101 default:
5102 return FALSE;
5103 }
5105 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5106 return TRUE;
5107 }
5109 return FALSE;
5110 }
5112 static void
5113 main_select(struct view *view, struct line *line)
5114 {
5115 struct commit *commit = line->data;
5117 string_copy_rev(view->ref, commit->id);
5118 string_copy_rev(ref_commit, view->ref);
5119 }
5121 static struct view_ops main_ops = {
5122 "commit",
5123 NULL,
5124 main_read,
5125 main_draw,
5126 main_request,
5127 main_grep,
5128 main_select,
5129 };
5132 /*
5133 * Unicode / UTF-8 handling
5134 *
5135 * NOTE: Much of the following code for dealing with unicode is derived from
5136 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5137 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5138 */
5140 /* I've (over)annotated a lot of code snippets because I am not entirely
5141 * confident that the approach taken by this small UTF-8 interface is correct.
5142 * --jonas */
5144 static inline int
5145 unicode_width(unsigned long c)
5146 {
5147 if (c >= 0x1100 &&
5148 (c <= 0x115f /* Hangul Jamo */
5149 || c == 0x2329
5150 || c == 0x232a
5151 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5152 /* CJK ... Yi */
5153 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5154 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5155 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5156 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5157 || (c >= 0xffe0 && c <= 0xffe6)
5158 || (c >= 0x20000 && c <= 0x2fffd)
5159 || (c >= 0x30000 && c <= 0x3fffd)))
5160 return 2;
5162 if (c == '\t')
5163 return opt_tab_size;
5165 return 1;
5166 }
5168 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5169 * Illegal bytes are set one. */
5170 static const unsigned char utf8_bytes[256] = {
5171 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,
5172 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,
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 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,
5178 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,
5179 };
5181 /* Decode UTF-8 multi-byte representation into a unicode character. */
5182 static inline unsigned long
5183 utf8_to_unicode(const char *string, size_t length)
5184 {
5185 unsigned long unicode;
5187 switch (length) {
5188 case 1:
5189 unicode = string[0];
5190 break;
5191 case 2:
5192 unicode = (string[0] & 0x1f) << 6;
5193 unicode += (string[1] & 0x3f);
5194 break;
5195 case 3:
5196 unicode = (string[0] & 0x0f) << 12;
5197 unicode += ((string[1] & 0x3f) << 6);
5198 unicode += (string[2] & 0x3f);
5199 break;
5200 case 4:
5201 unicode = (string[0] & 0x0f) << 18;
5202 unicode += ((string[1] & 0x3f) << 12);
5203 unicode += ((string[2] & 0x3f) << 6);
5204 unicode += (string[3] & 0x3f);
5205 break;
5206 case 5:
5207 unicode = (string[0] & 0x0f) << 24;
5208 unicode += ((string[1] & 0x3f) << 18);
5209 unicode += ((string[2] & 0x3f) << 12);
5210 unicode += ((string[3] & 0x3f) << 6);
5211 unicode += (string[4] & 0x3f);
5212 break;
5213 case 6:
5214 unicode = (string[0] & 0x01) << 30;
5215 unicode += ((string[1] & 0x3f) << 24);
5216 unicode += ((string[2] & 0x3f) << 18);
5217 unicode += ((string[3] & 0x3f) << 12);
5218 unicode += ((string[4] & 0x3f) << 6);
5219 unicode += (string[5] & 0x3f);
5220 break;
5221 default:
5222 die("Invalid unicode length");
5223 }
5225 /* Invalid characters could return the special 0xfffd value but NUL
5226 * should be just as good. */
5227 return unicode > 0xffff ? 0 : unicode;
5228 }
5230 /* Calculates how much of string can be shown within the given maximum width
5231 * and sets trimmed parameter to non-zero value if all of string could not be
5232 * shown. If the reserve flag is TRUE, it will reserve at least one
5233 * trailing character, which can be useful when drawing a delimiter.
5234 *
5235 * Returns the number of bytes to output from string to satisfy max_width. */
5236 static size_t
5237 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5238 {
5239 const char *start = string;
5240 const char *end = strchr(string, '\0');
5241 unsigned char last_bytes = 0;
5242 size_t last_ucwidth = 0;
5244 *width = 0;
5245 *trimmed = 0;
5247 while (string < end) {
5248 int c = *(unsigned char *) string;
5249 unsigned char bytes = utf8_bytes[c];
5250 size_t ucwidth;
5251 unsigned long unicode;
5253 if (string + bytes > end)
5254 break;
5256 /* Change representation to figure out whether
5257 * it is a single- or double-width character. */
5259 unicode = utf8_to_unicode(string, bytes);
5260 /* FIXME: Graceful handling of invalid unicode character. */
5261 if (!unicode)
5262 break;
5264 ucwidth = unicode_width(unicode);
5265 *width += ucwidth;
5266 if (*width > max_width) {
5267 *trimmed = 1;
5268 *width -= ucwidth;
5269 if (reserve && *width == max_width) {
5270 string -= last_bytes;
5271 *width -= last_ucwidth;
5272 }
5273 break;
5274 }
5276 string += bytes;
5277 last_bytes = bytes;
5278 last_ucwidth = ucwidth;
5279 }
5281 return string - start;
5282 }
5285 /*
5286 * Status management
5287 */
5289 /* Whether or not the curses interface has been initialized. */
5290 static bool cursed = FALSE;
5292 /* The status window is used for polling keystrokes. */
5293 static WINDOW *status_win;
5295 static bool status_empty = TRUE;
5297 /* Update status and title window. */
5298 static void
5299 report(const char *msg, ...)
5300 {
5301 struct view *view = display[current_view];
5303 if (input_mode)
5304 return;
5306 if (!view) {
5307 char buf[SIZEOF_STR];
5308 va_list args;
5310 va_start(args, msg);
5311 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5312 buf[sizeof(buf) - 1] = 0;
5313 buf[sizeof(buf) - 2] = '.';
5314 buf[sizeof(buf) - 3] = '.';
5315 buf[sizeof(buf) - 4] = '.';
5316 }
5317 va_end(args);
5318 die("%s", buf);
5319 }
5321 if (!status_empty || *msg) {
5322 va_list args;
5324 va_start(args, msg);
5326 wmove(status_win, 0, 0);
5327 if (*msg) {
5328 vwprintw(status_win, msg, args);
5329 status_empty = FALSE;
5330 } else {
5331 status_empty = TRUE;
5332 }
5333 wclrtoeol(status_win);
5334 wrefresh(status_win);
5336 va_end(args);
5337 }
5339 update_view_title(view);
5340 update_display_cursor(view);
5341 }
5343 /* Controls when nodelay should be in effect when polling user input. */
5344 static void
5345 set_nonblocking_input(bool loading)
5346 {
5347 static unsigned int loading_views;
5349 if ((loading == FALSE && loading_views-- == 1) ||
5350 (loading == TRUE && loading_views++ == 0))
5351 nodelay(status_win, loading);
5352 }
5354 static void
5355 init_display(void)
5356 {
5357 int x, y;
5359 /* Initialize the curses library */
5360 if (isatty(STDIN_FILENO)) {
5361 cursed = !!initscr();
5362 } else {
5363 /* Leave stdin and stdout alone when acting as a pager. */
5364 FILE *io = fopen("/dev/tty", "r+");
5366 if (!io)
5367 die("Failed to open /dev/tty");
5368 cursed = !!newterm(NULL, io, io);
5369 }
5371 if (!cursed)
5372 die("Failed to initialize curses");
5374 nonl(); /* Tell curses not to do NL->CR/NL on output */
5375 cbreak(); /* Take input chars one at a time, no wait for \n */
5376 noecho(); /* Don't echo input */
5377 leaveok(stdscr, TRUE);
5379 if (has_colors())
5380 init_colors();
5382 getmaxyx(stdscr, y, x);
5383 status_win = newwin(1, 0, y - 1, 0);
5384 if (!status_win)
5385 die("Failed to create status window");
5387 /* Enable keyboard mapping */
5388 keypad(status_win, TRUE);
5389 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5391 TABSIZE = opt_tab_size;
5392 if (opt_line_graphics) {
5393 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5394 }
5395 }
5397 static char *
5398 read_prompt(const char *prompt)
5399 {
5400 enum { READING, STOP, CANCEL } status = READING;
5401 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5402 int pos = 0;
5404 while (status == READING) {
5405 struct view *view;
5406 int i, key;
5408 input_mode = TRUE;
5410 foreach_view (view, i)
5411 update_view(view);
5413 input_mode = FALSE;
5415 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5416 wclrtoeol(status_win);
5418 /* Refresh, accept single keystroke of input */
5419 key = wgetch(status_win);
5420 switch (key) {
5421 case KEY_RETURN:
5422 case KEY_ENTER:
5423 case '\n':
5424 status = pos ? STOP : CANCEL;
5425 break;
5427 case KEY_BACKSPACE:
5428 if (pos > 0)
5429 pos--;
5430 else
5431 status = CANCEL;
5432 break;
5434 case KEY_ESC:
5435 status = CANCEL;
5436 break;
5438 case ERR:
5439 break;
5441 default:
5442 if (pos >= sizeof(buf)) {
5443 report("Input string too long");
5444 return NULL;
5445 }
5447 if (isprint(key))
5448 buf[pos++] = (char) key;
5449 }
5450 }
5452 /* Clear the status window */
5453 status_empty = FALSE;
5454 report("");
5456 if (status == CANCEL)
5457 return NULL;
5459 buf[pos++] = 0;
5461 return buf;
5462 }
5464 /*
5465 * Repository references
5466 */
5468 static struct ref *refs = NULL;
5469 static size_t refs_alloc = 0;
5470 static size_t refs_size = 0;
5472 /* Id <-> ref store */
5473 static struct ref ***id_refs = NULL;
5474 static size_t id_refs_alloc = 0;
5475 static size_t id_refs_size = 0;
5477 static struct ref **
5478 get_refs(char *id)
5479 {
5480 struct ref ***tmp_id_refs;
5481 struct ref **ref_list = NULL;
5482 size_t ref_list_alloc = 0;
5483 size_t ref_list_size = 0;
5484 size_t i;
5486 for (i = 0; i < id_refs_size; i++)
5487 if (!strcmp(id, id_refs[i][0]->id))
5488 return id_refs[i];
5490 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5491 sizeof(*id_refs));
5492 if (!tmp_id_refs)
5493 return NULL;
5495 id_refs = tmp_id_refs;
5497 for (i = 0; i < refs_size; i++) {
5498 struct ref **tmp;
5500 if (strcmp(id, refs[i].id))
5501 continue;
5503 tmp = realloc_items(ref_list, &ref_list_alloc,
5504 ref_list_size + 1, sizeof(*ref_list));
5505 if (!tmp) {
5506 if (ref_list)
5507 free(ref_list);
5508 return NULL;
5509 }
5511 ref_list = tmp;
5512 if (ref_list_size > 0)
5513 ref_list[ref_list_size - 1]->next = 1;
5514 ref_list[ref_list_size] = &refs[i];
5516 /* XXX: The properties of the commit chains ensures that we can
5517 * safely modify the shared ref. The repo references will
5518 * always be similar for the same id. */
5519 ref_list[ref_list_size]->next = 0;
5520 ref_list_size++;
5521 }
5523 if (ref_list)
5524 id_refs[id_refs_size++] = ref_list;
5526 return ref_list;
5527 }
5529 static int
5530 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5531 {
5532 struct ref *ref;
5533 bool tag = FALSE;
5534 bool ltag = FALSE;
5535 bool remote = FALSE;
5536 bool tracked = FALSE;
5537 bool check_replace = FALSE;
5538 bool head = FALSE;
5540 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5541 if (!strcmp(name + namelen - 3, "^{}")) {
5542 namelen -= 3;
5543 name[namelen] = 0;
5544 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5545 check_replace = TRUE;
5546 } else {
5547 ltag = TRUE;
5548 }
5550 tag = TRUE;
5551 namelen -= STRING_SIZE("refs/tags/");
5552 name += STRING_SIZE("refs/tags/");
5554 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5555 remote = TRUE;
5556 namelen -= STRING_SIZE("refs/remotes/");
5557 name += STRING_SIZE("refs/remotes/");
5558 tracked = !strcmp(opt_remote, name);
5560 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5561 namelen -= STRING_SIZE("refs/heads/");
5562 name += STRING_SIZE("refs/heads/");
5563 head = !strncmp(opt_head, name, namelen);
5565 } else if (!strcmp(name, "HEAD")) {
5566 opt_no_head = FALSE;
5567 return OK;
5568 }
5570 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5571 /* it's an annotated tag, replace the previous sha1 with the
5572 * resolved commit id; relies on the fact git-ls-remote lists
5573 * the commit id of an annotated tag right beofre the commit id
5574 * it points to. */
5575 refs[refs_size - 1].ltag = ltag;
5576 string_copy_rev(refs[refs_size - 1].id, id);
5578 return OK;
5579 }
5580 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5581 if (!refs)
5582 return ERR;
5584 ref = &refs[refs_size++];
5585 ref->name = malloc(namelen + 1);
5586 if (!ref->name)
5587 return ERR;
5589 strncpy(ref->name, name, namelen);
5590 ref->name[namelen] = 0;
5591 ref->head = head;
5592 ref->tag = tag;
5593 ref->ltag = ltag;
5594 ref->remote = remote;
5595 ref->tracked = tracked;
5596 string_copy_rev(ref->id, id);
5598 return OK;
5599 }
5601 static int
5602 load_refs(void)
5603 {
5604 const char *cmd_env = getenv("TIG_LS_REMOTE");
5605 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5607 return read_properties(popen(cmd, "r"), "\t", read_ref);
5608 }
5610 static int
5611 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5612 {
5613 if (!strcmp(name, "i18n.commitencoding"))
5614 string_ncopy(opt_encoding, value, valuelen);
5616 if (!strcmp(name, "core.editor"))
5617 string_ncopy(opt_editor, value, valuelen);
5619 /* branch.<head>.remote */
5620 if (*opt_head &&
5621 !strncmp(name, "branch.", 7) &&
5622 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5623 !strcmp(name + 7 + strlen(opt_head), ".remote"))
5624 string_ncopy(opt_remote, value, valuelen);
5626 if (*opt_head && *opt_remote &&
5627 !strncmp(name, "branch.", 7) &&
5628 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5629 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5630 size_t from = strlen(opt_remote);
5632 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5633 value += STRING_SIZE("refs/heads/");
5634 valuelen -= STRING_SIZE("refs/heads/");
5635 }
5637 if (!string_format_from(opt_remote, &from, "/%s", value))
5638 opt_remote[0] = 0;
5639 }
5641 return OK;
5642 }
5644 static int
5645 load_git_config(void)
5646 {
5647 return read_properties(popen(GIT_CONFIG " --list", "r"),
5648 "=", read_repo_config_option);
5649 }
5651 static int
5652 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5653 {
5654 if (!opt_git_dir[0]) {
5655 string_ncopy(opt_git_dir, name, namelen);
5657 } else if (opt_is_inside_work_tree == -1) {
5658 /* This can be 3 different values depending on the
5659 * version of git being used. If git-rev-parse does not
5660 * understand --is-inside-work-tree it will simply echo
5661 * the option else either "true" or "false" is printed.
5662 * Default to true for the unknown case. */
5663 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5665 } else if (opt_cdup[0] == ' ') {
5666 string_ncopy(opt_cdup, name, namelen);
5667 } else {
5668 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5669 namelen -= STRING_SIZE("refs/heads/");
5670 name += STRING_SIZE("refs/heads/");
5671 string_ncopy(opt_head, name, namelen);
5672 }
5673 }
5675 return OK;
5676 }
5678 static int
5679 load_repo_info(void)
5680 {
5681 int result;
5682 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5683 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5685 /* XXX: The line outputted by "--show-cdup" can be empty so
5686 * initialize it to something invalid to make it possible to
5687 * detect whether it has been set or not. */
5688 opt_cdup[0] = ' ';
5690 result = read_properties(pipe, "=", read_repo_info);
5691 if (opt_cdup[0] == ' ')
5692 opt_cdup[0] = 0;
5694 return result;
5695 }
5697 static int
5698 read_properties(FILE *pipe, const char *separators,
5699 int (*read_property)(char *, size_t, char *, size_t))
5700 {
5701 char buffer[BUFSIZ];
5702 char *name;
5703 int state = OK;
5705 if (!pipe)
5706 return ERR;
5708 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5709 char *value;
5710 size_t namelen;
5711 size_t valuelen;
5713 name = chomp_string(name);
5714 namelen = strcspn(name, separators);
5716 if (name[namelen]) {
5717 name[namelen] = 0;
5718 value = chomp_string(name + namelen + 1);
5719 valuelen = strlen(value);
5721 } else {
5722 value = "";
5723 valuelen = 0;
5724 }
5726 state = read_property(name, namelen, value, valuelen);
5727 }
5729 if (state != ERR && ferror(pipe))
5730 state = ERR;
5732 pclose(pipe);
5734 return state;
5735 }
5738 /*
5739 * Main
5740 */
5742 static void __NORETURN
5743 quit(int sig)
5744 {
5745 /* XXX: Restore tty modes and let the OS cleanup the rest! */
5746 if (cursed)
5747 endwin();
5748 exit(0);
5749 }
5751 static void __NORETURN
5752 die(const char *err, ...)
5753 {
5754 va_list args;
5756 endwin();
5758 va_start(args, err);
5759 fputs("tig: ", stderr);
5760 vfprintf(stderr, err, args);
5761 fputs("\n", stderr);
5762 va_end(args);
5764 exit(1);
5765 }
5767 static void
5768 warn(const char *msg, ...)
5769 {
5770 va_list args;
5772 va_start(args, msg);
5773 fputs("tig warning: ", stderr);
5774 vfprintf(stderr, msg, args);
5775 fputs("\n", stderr);
5776 va_end(args);
5777 }
5779 int
5780 main(int argc, char *argv[])
5781 {
5782 struct view *view;
5783 enum request request;
5784 size_t i;
5786 signal(SIGINT, quit);
5788 if (setlocale(LC_ALL, "")) {
5789 char *codeset = nl_langinfo(CODESET);
5791 string_ncopy(opt_codeset, codeset, strlen(codeset));
5792 }
5794 if (load_repo_info() == ERR)
5795 die("Failed to load repo info.");
5797 if (load_options() == ERR)
5798 die("Failed to load user config.");
5800 if (load_git_config() == ERR)
5801 die("Failed to load repo config.");
5803 if (!parse_options(argc, argv))
5804 return 0;
5806 /* Require a git repository unless when running in pager mode. */
5807 if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5808 die("Not a git repository");
5810 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5811 opt_utf8 = FALSE;
5813 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5814 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5815 if (opt_iconv == ICONV_NONE)
5816 die("Failed to initialize character set conversion");
5817 }
5819 if (*opt_git_dir && load_refs() == ERR)
5820 die("Failed to load refs.");
5822 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5823 view->cmd_env = getenv(view->cmd_env);
5825 request = opt_request;
5827 init_display();
5829 while (view_driver(display[current_view], request)) {
5830 int key;
5831 int i;
5833 foreach_view (view, i)
5834 update_view(view);
5836 /* Refresh, accept single keystroke of input */
5837 key = wgetch(status_win);
5839 /* wgetch() with nodelay() enabled returns ERR when there's no
5840 * input. */
5841 if (key == ERR) {
5842 request = REQ_NONE;
5843 continue;
5844 }
5846 request = get_keybinding(display[current_view]->keymap, key);
5848 /* Some low-level request handling. This keeps access to
5849 * status_win restricted. */
5850 switch (request) {
5851 case REQ_PROMPT:
5852 {
5853 char *cmd = read_prompt(":");
5855 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5856 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5857 opt_request = REQ_VIEW_DIFF;
5858 } else {
5859 opt_request = REQ_VIEW_PAGER;
5860 }
5861 break;
5862 }
5864 request = REQ_NONE;
5865 break;
5866 }
5867 case REQ_SEARCH:
5868 case REQ_SEARCH_BACK:
5869 {
5870 const char *prompt = request == REQ_SEARCH
5871 ? "/" : "?";
5872 char *search = read_prompt(prompt);
5874 if (search)
5875 string_ncopy(opt_search, search, strlen(search));
5876 else
5877 request = REQ_NONE;
5878 break;
5879 }
5880 case REQ_SCREEN_RESIZE:
5881 {
5882 int height, width;
5884 getmaxyx(stdscr, height, width);
5886 /* Resize the status view and let the view driver take
5887 * care of resizing the displayed views. */
5888 wresize(status_win, 1, width);
5889 mvwin(status_win, height - 1, 0);
5890 wrefresh(status_win);
5891 break;
5892 }
5893 default:
5894 break;
5895 }
5896 }
5898 quit(0);
5900 return 0;
5901 }