Detect working trees and disable the status view when it is missing
[tig] / tig.c
1 /* Copyright (c) 2006-2007 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 */
13
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
17
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
21
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
25
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>
38
39 #include <regex.h>
40
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
44
45 #include <curses.h>
46
47 #if __GNUC__ >= 3
48 #define __NORETURN __attribute__((__noreturn__))
49 #else
50 #define __NORETURN
51 #endif
52
53 static void __NORETURN die(const char *err, ...);
54 static void report(const char *msg, ...);
55 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
56 static void set_nonblocking_input(bool loading);
57 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
58
59 #define ABS(x) ((x) >= 0 ? (x) : -(x))
60 #define MIN(x, y) ((x) < (y) ? (x) : (y))
61
62 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
63 #define STRING_SIZE(x) (sizeof(x) - 1)
64
65 #define SIZEOF_STR 1024 /* Default string size. */
66 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
67 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */
68
69 /* Revision graph */
70
71 #define REVGRAPH_INIT 'I'
72 #define REVGRAPH_MERGE 'M'
73 #define REVGRAPH_BRANCH '+'
74 #define REVGRAPH_COMMIT '*'
75 #define REVGRAPH_LINE '|'
76
77 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
78
79 /* This color name can be used to refer to the default term colors. */
80 #define COLOR_DEFAULT (-1)
81
82 #define ICONV_NONE ((iconv_t) -1)
83 #ifndef ICONV_CONST
84 #define ICONV_CONST /* nothing */
85 #endif
86
87 /* The format and size of the date column in the main view. */
88 #define DATE_FORMAT "%Y-%m-%d %H:%M"
89 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
90
91 #define AUTHOR_COLS 20
92
93 /* The default interval between line numbers. */
94 #define NUMBER_INTERVAL 1
95
96 #define TABSIZE 8
97
98 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
99
100 #ifndef GIT_CONFIG
101 #define GIT_CONFIG "git config"
102 #endif
103
104 #define TIG_LS_REMOTE \
105 "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
106
107 #define TIG_DIFF_CMD \
108 "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
109
110 #define TIG_LOG_CMD \
111 "git log --cc --stat -n100 %s 2>/dev/null"
112
113 #define TIG_MAIN_CMD \
114 "git log --topo-order --pretty=raw %s 2>/dev/null"
115
116 #define TIG_TREE_CMD \
117 "git ls-tree %s %s"
118
119 #define TIG_BLOB_CMD \
120 "git cat-file blob %s"
121
122 /* XXX: Needs to be defined to the empty string. */
123 #define TIG_HELP_CMD ""
124 #define TIG_PAGER_CMD ""
125 #define TIG_STATUS_CMD ""
126 #define TIG_STAGE_CMD ""
127
128 /* Some ascii-shorthands fitted into the ncurses namespace. */
129 #define KEY_TAB '\t'
130 #define KEY_RETURN '\r'
131 #define KEY_ESC 27
132
133
134 struct ref {
135 char *name; /* Ref name; tag or head names are shortened. */
136 char id[SIZEOF_REV]; /* Commit SHA1 ID */
137 unsigned int tag:1; /* Is it a tag? */
138 unsigned int remote:1; /* Is it a remote ref? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
140 };
141
142 static struct ref **get_refs(char *id);
143
144 struct int_map {
145 const char *name;
146 int namelen;
147 int value;
148 };
149
150 static int
151 set_from_int_map(struct int_map *map, size_t map_size,
152 int *value, const char *name, int namelen)
153 {
154
155 int i;
156
157 for (i = 0; i < map_size; i++)
158 if (namelen == map[i].namelen &&
159 !strncasecmp(name, map[i].name, namelen)) {
160 *value = map[i].value;
161 return OK;
162 }
163
164 return ERR;
165 }
166
167
168 /*
169 * String helpers
170 */
171
172 static inline void
173 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
174 {
175 if (srclen > dstlen - 1)
176 srclen = dstlen - 1;
177
178 strncpy(dst, src, srclen);
179 dst[srclen] = 0;
180 }
181
182 /* Shorthands for safely copying into a fixed buffer. */
183
184 #define string_copy(dst, src) \
185 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
186
187 #define string_ncopy(dst, src, srclen) \
188 string_ncopy_do(dst, sizeof(dst), src, srclen)
189
190 #define string_copy_rev(dst, src) \
191 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
192
193 #define string_add(dst, from, src) \
194 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
195
196 static char *
197 chomp_string(char *name)
198 {
199 int namelen;
200
201 while (isspace(*name))
202 name++;
203
204 namelen = strlen(name) - 1;
205 while (namelen > 0 && isspace(name[namelen]))
206 name[namelen--] = 0;
207
208 return name;
209 }
210
211 static bool
212 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
213 {
214 va_list args;
215 size_t pos = bufpos ? *bufpos : 0;
216
217 va_start(args, fmt);
218 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
219 va_end(args);
220
221 if (bufpos)
222 *bufpos = pos;
223
224 return pos >= bufsize ? FALSE : TRUE;
225 }
226
227 #define string_format(buf, fmt, args...) \
228 string_nformat(buf, sizeof(buf), NULL, fmt, args)
229
230 #define string_format_from(buf, from, fmt, args...) \
231 string_nformat(buf, sizeof(buf), from, fmt, args)
232
233 static int
234 string_enum_compare(const char *str1, const char *str2, int len)
235 {
236 size_t i;
237
238 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
239
240 /* Diff-Header == DIFF_HEADER */
241 for (i = 0; i < len; i++) {
242 if (toupper(str1[i]) == toupper(str2[i]))
243 continue;
244
245 if (string_enum_sep(str1[i]) &&
246 string_enum_sep(str2[i]))
247 continue;
248
249 return str1[i] - str2[i];
250 }
251
252 return 0;
253 }
254
255 /* Shell quoting
256 *
257 * NOTE: The following is a slightly modified copy of the git project's shell
258 * quoting routines found in the quote.c file.
259 *
260 * Help to copy the thing properly quoted for the shell safety. any single
261 * quote is replaced with '\'', any exclamation point is replaced with '\!',
262 * and the whole thing is enclosed in a
263 *
264 * E.g.
265 * original sq_quote result
266 * name ==> name ==> 'name'
267 * a b ==> a b ==> 'a b'
268 * a'b ==> a'\''b ==> 'a'\''b'
269 * a!b ==> a'\!'b ==> 'a'\!'b'
270 */
271
272 static size_t
273 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
274 {
275 char c;
276
277 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
278
279 BUFPUT('\'');
280 while ((c = *src++)) {
281 if (c == '\'' || c == '!') {
282 BUFPUT('\'');
283 BUFPUT('\\');
284 BUFPUT(c);
285 BUFPUT('\'');
286 } else {
287 BUFPUT(c);
288 }
289 }
290 BUFPUT('\'');
291
292 if (bufsize < SIZEOF_STR)
293 buf[bufsize] = 0;
294
295 return bufsize;
296 }
297
298
299 /*
300 * User requests
301 */
302
303 #define REQ_INFO \
304 /* XXX: Keep the view request first and in sync with views[]. */ \
305 REQ_GROUP("View switching") \
306 REQ_(VIEW_MAIN, "Show main view"), \
307 REQ_(VIEW_DIFF, "Show diff view"), \
308 REQ_(VIEW_LOG, "Show log view"), \
309 REQ_(VIEW_TREE, "Show tree view"), \
310 REQ_(VIEW_BLOB, "Show blob view"), \
311 REQ_(VIEW_HELP, "Show help page"), \
312 REQ_(VIEW_PAGER, "Show pager view"), \
313 REQ_(VIEW_STATUS, "Show status view"), \
314 REQ_(VIEW_STAGE, "Show stage view"), \
315 \
316 REQ_GROUP("View manipulation") \
317 REQ_(ENTER, "Enter current line and scroll"), \
318 REQ_(NEXT, "Move to next"), \
319 REQ_(PREVIOUS, "Move to previous"), \
320 REQ_(VIEW_NEXT, "Move focus to next view"), \
321 REQ_(REFRESH, "Reload and refresh"), \
322 REQ_(VIEW_CLOSE, "Close the current view"), \
323 REQ_(QUIT, "Close all views and quit"), \
324 \
325 REQ_GROUP("Cursor navigation") \
326 REQ_(MOVE_UP, "Move cursor one line up"), \
327 REQ_(MOVE_DOWN, "Move cursor one line down"), \
328 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
329 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
330 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
331 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
332 \
333 REQ_GROUP("Scrolling") \
334 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
335 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
336 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
337 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
338 \
339 REQ_GROUP("Searching") \
340 REQ_(SEARCH, "Search the view"), \
341 REQ_(SEARCH_BACK, "Search backwards in the view"), \
342 REQ_(FIND_NEXT, "Find next search match"), \
343 REQ_(FIND_PREV, "Find previous search match"), \
344 \
345 REQ_GROUP("Misc") \
346 REQ_(NONE, "Do nothing"), \
347 REQ_(PROMPT, "Bring up the prompt"), \
348 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
349 REQ_(SCREEN_RESIZE, "Resize the screen"), \
350 REQ_(SHOW_VERSION, "Show version information"), \
351 REQ_(STOP_LOADING, "Stop all loading views"), \
352 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
353 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
354 REQ_(STATUS_UPDATE, "Update file status"), \
355 REQ_(EDIT, "Open in editor"), \
356 REQ_(CHERRY_PICK, "Cherry-pick commit to current branch")
357
358
359 /* User action requests. */
360 enum request {
361 #define REQ_GROUP(help)
362 #define REQ_(req, help) REQ_##req
363
364 /* Offset all requests to avoid conflicts with ncurses getch values. */
365 REQ_OFFSET = KEY_MAX + 1,
366 REQ_INFO,
367 REQ_UNKNOWN,
368
369 #undef REQ_GROUP
370 #undef REQ_
371 };
372
373 struct request_info {
374 enum request request;
375 char *name;
376 int namelen;
377 char *help;
378 };
379
380 static struct request_info req_info[] = {
381 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
382 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
383 REQ_INFO
384 #undef REQ_GROUP
385 #undef REQ_
386 };
387
388 static enum request
389 get_request(const char *name)
390 {
391 int namelen = strlen(name);
392 int i;
393
394 for (i = 0; i < ARRAY_SIZE(req_info); i++)
395 if (req_info[i].namelen == namelen &&
396 !string_enum_compare(req_info[i].name, name, namelen))
397 return req_info[i].request;
398
399 return REQ_UNKNOWN;
400 }
401
402
403 /*
404 * Options
405 */
406
407 static const char usage[] =
408 "tig " TIG_VERSION " (" __DATE__ ")\n"
409 "\n"
410 "Usage: tig [options]\n"
411 " or: tig [options] [--] [git log options]\n"
412 " or: tig [options] log [git log options]\n"
413 " or: tig [options] diff [git diff options]\n"
414 " or: tig [options] show [git show options]\n"
415 " or: tig [options] < [git command output]\n"
416 "\n"
417 "Options:\n"
418 " -l Start up in log view\n"
419 " -d Start up in diff view\n"
420 " -S Start up in status view\n"
421 " -n[I], --line-number[=I] Show line numbers with given interval\n"
422 " -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
423 " -- Mark end of tig options\n"
424 " -v, --version Show version and exit\n"
425 " -h, --help Show help message and exit\n";
426
427 /* Option and state variables. */
428 static bool opt_line_number = FALSE;
429 static bool opt_rev_graph = FALSE;
430 static int opt_num_interval = NUMBER_INTERVAL;
431 static int opt_tab_size = TABSIZE;
432 static enum request opt_request = REQ_VIEW_MAIN;
433 static char opt_cmd[SIZEOF_STR] = "";
434 static char opt_path[SIZEOF_STR] = "";
435 static FILE *opt_pipe = NULL;
436 static char opt_encoding[20] = "UTF-8";
437 static bool opt_utf8 = TRUE;
438 static char opt_codeset[20] = "UTF-8";
439 static iconv_t opt_iconv = ICONV_NONE;
440 static char opt_search[SIZEOF_STR] = "";
441 static char opt_cdup[SIZEOF_STR] = "";
442 static char opt_git_dir[SIZEOF_STR] = "";
443 static char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
444 static char opt_editor[SIZEOF_STR] = "";
445
446 enum option_type {
447 OPT_NONE,
448 OPT_INT,
449 };
450
451 static bool
452 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
453 {
454 va_list args;
455 char *value = "";
456 int *number;
457
458 if (opt[0] != '-')
459 return FALSE;
460
461 if (opt[1] == '-') {
462 int namelen = strlen(name);
463
464 opt += 2;
465
466 if (strncmp(opt, name, namelen))
467 return FALSE;
468
469 if (opt[namelen] == '=')
470 value = opt + namelen + 1;
471
472 } else {
473 if (!short_name || opt[1] != short_name)
474 return FALSE;
475 value = opt + 2;
476 }
477
478 va_start(args, type);
479 if (type == OPT_INT) {
480 number = va_arg(args, int *);
481 if (isdigit(*value))
482 *number = atoi(value);
483 }
484 va_end(args);
485
486 return TRUE;
487 }
488
489 /* Returns the index of log or diff command or -1 to exit. */
490 static bool
491 parse_options(int argc, char *argv[])
492 {
493 int i;
494
495 for (i = 1; i < argc; i++) {
496 char *opt = argv[i];
497
498 if (!strcmp(opt, "log") ||
499 !strcmp(opt, "diff") ||
500 !strcmp(opt, "show")) {
501 opt_request = opt[0] == 'l'
502 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
503 break;
504 }
505
506 if (opt[0] && opt[0] != '-')
507 break;
508
509 if (!strcmp(opt, "-l")) {
510 opt_request = REQ_VIEW_LOG;
511 continue;
512 }
513
514 if (!strcmp(opt, "-d")) {
515 opt_request = REQ_VIEW_DIFF;
516 continue;
517 }
518
519 if (!strcmp(opt, "-S")) {
520 opt_request = REQ_VIEW_STATUS;
521 continue;
522 }
523
524 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
525 opt_line_number = TRUE;
526 continue;
527 }
528
529 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
530 opt_tab_size = MIN(opt_tab_size, TABSIZE);
531 continue;
532 }
533
534 if (check_option(opt, 'v', "version", OPT_NONE)) {
535 printf("tig version %s\n", TIG_VERSION);
536 return FALSE;
537 }
538
539 if (check_option(opt, 'h', "help", OPT_NONE)) {
540 printf(usage);
541 return FALSE;
542 }
543
544 if (!strcmp(opt, "--")) {
545 i++;
546 break;
547 }
548
549 die("unknown option '%s'\n\n%s", opt, usage);
550 }
551
552 if (!isatty(STDIN_FILENO)) {
553 opt_request = REQ_VIEW_PAGER;
554 opt_pipe = stdin;
555
556 } else if (i < argc) {
557 size_t buf_size;
558
559 if (opt_request == REQ_VIEW_MAIN)
560 /* XXX: This is vulnerable to the user overriding
561 * options required for the main view parser. */
562 string_copy(opt_cmd, "git log --pretty=raw");
563 else
564 string_copy(opt_cmd, "git");
565 buf_size = strlen(opt_cmd);
566
567 while (buf_size < sizeof(opt_cmd) && i < argc) {
568 opt_cmd[buf_size++] = ' ';
569 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
570 }
571
572 if (buf_size >= sizeof(opt_cmd))
573 die("command too long");
574
575 opt_cmd[buf_size] = 0;
576 }
577
578 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
579 opt_utf8 = FALSE;
580
581 return TRUE;
582 }
583
584
585 /*
586 * Line-oriented content detection.
587 */
588
589 #define LINE_INFO \
590 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
591 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
592 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
593 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
594 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
595 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
596 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
597 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
598 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
599 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
600 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
601 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
602 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
603 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
604 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
605 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
606 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
607 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
608 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
609 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
610 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
611 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
612 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
613 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
614 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
615 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
616 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
617 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
618 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
619 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
620 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
621 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
622 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
623 LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
624 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
625 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
626 LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
627 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
628 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
629 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
630 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
631 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
632 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
633 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
634 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
635 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
636 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
637
638 enum line_type {
639 #define LINE(type, line, fg, bg, attr) \
640 LINE_##type
641 LINE_INFO
642 #undef LINE
643 };
644
645 struct line_info {
646 const char *name; /* Option name. */
647 int namelen; /* Size of option name. */
648 const char *line; /* The start of line to match. */
649 int linelen; /* Size of string to match. */
650 int fg, bg, attr; /* Color and text attributes for the lines. */
651 };
652
653 static struct line_info line_info[] = {
654 #define LINE(type, line, fg, bg, attr) \
655 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
656 LINE_INFO
657 #undef LINE
658 };
659
660 static enum line_type
661 get_line_type(char *line)
662 {
663 int linelen = strlen(line);
664 enum line_type type;
665
666 for (type = 0; type < ARRAY_SIZE(line_info); type++)
667 /* Case insensitive search matches Signed-off-by lines better. */
668 if (linelen >= line_info[type].linelen &&
669 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
670 return type;
671
672 return LINE_DEFAULT;
673 }
674
675 static inline int
676 get_line_attr(enum line_type type)
677 {
678 assert(type < ARRAY_SIZE(line_info));
679 return COLOR_PAIR(type) | line_info[type].attr;
680 }
681
682 static struct line_info *
683 get_line_info(char *name, int namelen)
684 {
685 enum line_type type;
686
687 for (type = 0; type < ARRAY_SIZE(line_info); type++)
688 if (namelen == line_info[type].namelen &&
689 !string_enum_compare(line_info[type].name, name, namelen))
690 return &line_info[type];
691
692 return NULL;
693 }
694
695 static void
696 init_colors(void)
697 {
698 int default_bg = COLOR_BLACK;
699 int default_fg = COLOR_WHITE;
700 enum line_type type;
701
702 start_color();
703
704 if (use_default_colors() != ERR) {
705 default_bg = -1;
706 default_fg = -1;
707 }
708
709 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
710 struct line_info *info = &line_info[type];
711 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
712 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
713
714 init_pair(type, fg, bg);
715 }
716 }
717
718 struct line {
719 enum line_type type;
720
721 /* State flags */
722 unsigned int selected:1;
723
724 void *data; /* User data */
725 };
726
727
728 /*
729 * Keys
730 */
731
732 struct keybinding {
733 int alias;
734 enum request request;
735 struct keybinding *next;
736 };
737
738 static struct keybinding default_keybindings[] = {
739 /* View switching */
740 { 'm', REQ_VIEW_MAIN },
741 { 'd', REQ_VIEW_DIFF },
742 { 'l', REQ_VIEW_LOG },
743 { 't', REQ_VIEW_TREE },
744 { 'f', REQ_VIEW_BLOB },
745 { 'p', REQ_VIEW_PAGER },
746 { 'h', REQ_VIEW_HELP },
747 { 'S', REQ_VIEW_STATUS },
748 { 'c', REQ_VIEW_STAGE },
749
750 /* View manipulation */
751 { 'q', REQ_VIEW_CLOSE },
752 { KEY_TAB, REQ_VIEW_NEXT },
753 { KEY_RETURN, REQ_ENTER },
754 { KEY_UP, REQ_PREVIOUS },
755 { KEY_DOWN, REQ_NEXT },
756 { 'R', REQ_REFRESH },
757
758 /* Cursor navigation */
759 { 'k', REQ_MOVE_UP },
760 { 'j', REQ_MOVE_DOWN },
761 { KEY_HOME, REQ_MOVE_FIRST_LINE },
762 { KEY_END, REQ_MOVE_LAST_LINE },
763 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
764 { ' ', REQ_MOVE_PAGE_DOWN },
765 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
766 { 'b', REQ_MOVE_PAGE_UP },
767 { '-', REQ_MOVE_PAGE_UP },
768
769 /* Scrolling */
770 { KEY_IC, REQ_SCROLL_LINE_UP },
771 { KEY_DC, REQ_SCROLL_LINE_DOWN },
772 { 'w', REQ_SCROLL_PAGE_UP },
773 { 's', REQ_SCROLL_PAGE_DOWN },
774
775 /* Searching */
776 { '/', REQ_SEARCH },
777 { '?', REQ_SEARCH_BACK },
778 { 'n', REQ_FIND_NEXT },
779 { 'N', REQ_FIND_PREV },
780
781 /* Misc */
782 { 'Q', REQ_QUIT },
783 { 'z', REQ_STOP_LOADING },
784 { 'v', REQ_SHOW_VERSION },
785 { 'r', REQ_SCREEN_REDRAW },
786 { '.', REQ_TOGGLE_LINENO },
787 { 'g', REQ_TOGGLE_REV_GRAPH },
788 { ':', REQ_PROMPT },
789 { 'u', REQ_STATUS_UPDATE },
790 { 'e', REQ_EDIT },
791 { 'C', REQ_CHERRY_PICK },
792
793 /* Using the ncurses SIGWINCH handler. */
794 { KEY_RESIZE, REQ_SCREEN_RESIZE },
795 };
796
797 #define KEYMAP_INFO \
798 KEYMAP_(GENERIC), \
799 KEYMAP_(MAIN), \
800 KEYMAP_(DIFF), \
801 KEYMAP_(LOG), \
802 KEYMAP_(TREE), \
803 KEYMAP_(BLOB), \
804 KEYMAP_(PAGER), \
805 KEYMAP_(HELP), \
806 KEYMAP_(STATUS), \
807 KEYMAP_(STAGE)
808
809 enum keymap {
810 #define KEYMAP_(name) KEYMAP_##name
811 KEYMAP_INFO
812 #undef KEYMAP_
813 };
814
815 static struct int_map keymap_table[] = {
816 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
817 KEYMAP_INFO
818 #undef KEYMAP_
819 };
820
821 #define set_keymap(map, name) \
822 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
823
824 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
825
826 static void
827 add_keybinding(enum keymap keymap, enum request request, int key)
828 {
829 struct keybinding *keybinding;
830
831 keybinding = calloc(1, sizeof(*keybinding));
832 if (!keybinding)
833 die("Failed to allocate keybinding");
834
835 keybinding->alias = key;
836 keybinding->request = request;
837 keybinding->next = keybindings[keymap];
838 keybindings[keymap] = keybinding;
839 }
840
841 /* Looks for a key binding first in the given map, then in the generic map, and
842 * lastly in the default keybindings. */
843 static enum request
844 get_keybinding(enum keymap keymap, int key)
845 {
846 struct keybinding *kbd;
847 int i;
848
849 for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
850 if (kbd->alias == key)
851 return kbd->request;
852
853 for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
854 if (kbd->alias == key)
855 return kbd->request;
856
857 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
858 if (default_keybindings[i].alias == key)
859 return default_keybindings[i].request;
860
861 return (enum request) key;
862 }
863
864
865 struct key {
866 char *name;
867 int value;
868 };
869
870 static struct key key_table[] = {
871 { "Enter", KEY_RETURN },
872 { "Space", ' ' },
873 { "Backspace", KEY_BACKSPACE },
874 { "Tab", KEY_TAB },
875 { "Escape", KEY_ESC },
876 { "Left", KEY_LEFT },
877 { "Right", KEY_RIGHT },
878 { "Up", KEY_UP },
879 { "Down", KEY_DOWN },
880 { "Insert", KEY_IC },
881 { "Delete", KEY_DC },
882 { "Hash", '#' },
883 { "Home", KEY_HOME },
884 { "End", KEY_END },
885 { "PageUp", KEY_PPAGE },
886 { "PageDown", KEY_NPAGE },
887 { "F1", KEY_F(1) },
888 { "F2", KEY_F(2) },
889 { "F3", KEY_F(3) },
890 { "F4", KEY_F(4) },
891 { "F5", KEY_F(5) },
892 { "F6", KEY_F(6) },
893 { "F7", KEY_F(7) },
894 { "F8", KEY_F(8) },
895 { "F9", KEY_F(9) },
896 { "F10", KEY_F(10) },
897 { "F11", KEY_F(11) },
898 { "F12", KEY_F(12) },
899 };
900
901 static int
902 get_key_value(const char *name)
903 {
904 int i;
905
906 for (i = 0; i < ARRAY_SIZE(key_table); i++)
907 if (!strcasecmp(key_table[i].name, name))
908 return key_table[i].value;
909
910 if (strlen(name) == 1 && isprint(*name))
911 return (int) *name;
912
913 return ERR;
914 }
915
916 static char *
917 get_key(enum request request)
918 {
919 static char buf[BUFSIZ];
920 static char key_char[] = "'X'";
921 size_t pos = 0;
922 char *sep = "";
923 int i;
924
925 buf[pos] = 0;
926
927 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
928 struct keybinding *keybinding = &default_keybindings[i];
929 char *seq = NULL;
930 int key;
931
932 if (keybinding->request != request)
933 continue;
934
935 for (key = 0; key < ARRAY_SIZE(key_table); key++)
936 if (key_table[key].value == keybinding->alias)
937 seq = key_table[key].name;
938
939 if (seq == NULL &&
940 keybinding->alias < 127 &&
941 isprint(keybinding->alias)) {
942 key_char[1] = (char) keybinding->alias;
943 seq = key_char;
944 }
945
946 if (!seq)
947 seq = "'?'";
948
949 if (!string_format_from(buf, &pos, "%s%s", sep, seq))
950 return "Too many keybindings!";
951 sep = ", ";
952 }
953
954 return buf;
955 }
956
957
958 /*
959 * User config file handling.
960 */
961
962 static struct int_map color_map[] = {
963 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
964 COLOR_MAP(DEFAULT),
965 COLOR_MAP(BLACK),
966 COLOR_MAP(BLUE),
967 COLOR_MAP(CYAN),
968 COLOR_MAP(GREEN),
969 COLOR_MAP(MAGENTA),
970 COLOR_MAP(RED),
971 COLOR_MAP(WHITE),
972 COLOR_MAP(YELLOW),
973 };
974
975 #define set_color(color, name) \
976 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
977
978 static struct int_map attr_map[] = {
979 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
980 ATTR_MAP(NORMAL),
981 ATTR_MAP(BLINK),
982 ATTR_MAP(BOLD),
983 ATTR_MAP(DIM),
984 ATTR_MAP(REVERSE),
985 ATTR_MAP(STANDOUT),
986 ATTR_MAP(UNDERLINE),
987 };
988
989 #define set_attribute(attr, name) \
990 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
991
992 static int config_lineno;
993 static bool config_errors;
994 static char *config_msg;
995
996 /* Wants: object fgcolor bgcolor [attr] */
997 static int
998 option_color_command(int argc, char *argv[])
999 {
1000 struct line_info *info;
1001
1002 if (argc != 3 && argc != 4) {
1003 config_msg = "Wrong number of arguments given to color command";
1004 return ERR;
1005 }
1006
1007 info = get_line_info(argv[0], strlen(argv[0]));
1008 if (!info) {
1009 config_msg = "Unknown color name";
1010 return ERR;
1011 }
1012
1013 if (set_color(&info->fg, argv[1]) == ERR ||
1014 set_color(&info->bg, argv[2]) == ERR) {
1015 config_msg = "Unknown color";
1016 return ERR;
1017 }
1018
1019 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1020 config_msg = "Unknown attribute";
1021 return ERR;
1022 }
1023
1024 return OK;
1025 }
1026
1027 /* Wants: name = value */
1028 static int
1029 option_set_command(int argc, char *argv[])
1030 {
1031 if (argc != 3) {
1032 config_msg = "Wrong number of arguments given to set command";
1033 return ERR;
1034 }
1035
1036 if (strcmp(argv[1], "=")) {
1037 config_msg = "No value assigned";
1038 return ERR;
1039 }
1040
1041 if (!strcmp(argv[0], "show-rev-graph")) {
1042 opt_rev_graph = (!strcmp(argv[2], "1") ||
1043 !strcmp(argv[2], "true") ||
1044 !strcmp(argv[2], "yes"));
1045 return OK;
1046 }
1047
1048 if (!strcmp(argv[0], "line-number-interval")) {
1049 opt_num_interval = atoi(argv[2]);
1050 return OK;
1051 }
1052
1053 if (!strcmp(argv[0], "tab-size")) {
1054 opt_tab_size = atoi(argv[2]);
1055 return OK;
1056 }
1057
1058 if (!strcmp(argv[0], "commit-encoding")) {
1059 char *arg = argv[2];
1060 int delimiter = *arg;
1061 int i;
1062
1063 switch (delimiter) {
1064 case '"':
1065 case '\'':
1066 for (arg++, i = 0; arg[i]; i++)
1067 if (arg[i] == delimiter) {
1068 arg[i] = 0;
1069 break;
1070 }
1071 default:
1072 string_ncopy(opt_encoding, arg, strlen(arg));
1073 return OK;
1074 }
1075 }
1076
1077 config_msg = "Unknown variable name";
1078 return ERR;
1079 }
1080
1081 /* Wants: mode request key */
1082 static int
1083 option_bind_command(int argc, char *argv[])
1084 {
1085 enum request request;
1086 int keymap;
1087 int key;
1088
1089 if (argc != 3) {
1090 config_msg = "Wrong number of arguments given to bind command";
1091 return ERR;
1092 }
1093
1094 if (set_keymap(&keymap, argv[0]) == ERR) {
1095 config_msg = "Unknown key map";
1096 return ERR;
1097 }
1098
1099 key = get_key_value(argv[1]);
1100 if (key == ERR) {
1101 config_msg = "Unknown key";
1102 return ERR;
1103 }
1104
1105 request = get_request(argv[2]);
1106 if (request == REQ_UNKNOWN) {
1107 config_msg = "Unknown request name";
1108 return ERR;
1109 }
1110
1111 add_keybinding(keymap, request, key);
1112
1113 return OK;
1114 }
1115
1116 static int
1117 set_option(char *opt, char *value)
1118 {
1119 char *argv[16];
1120 int valuelen;
1121 int argc = 0;
1122
1123 /* Tokenize */
1124 while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1125 argv[argc++] = value;
1126
1127 value += valuelen;
1128 if (!*value)
1129 break;
1130
1131 *value++ = 0;
1132 while (isspace(*value))
1133 value++;
1134 }
1135
1136 if (!strcmp(opt, "color"))
1137 return option_color_command(argc, argv);
1138
1139 if (!strcmp(opt, "set"))
1140 return option_set_command(argc, argv);
1141
1142 if (!strcmp(opt, "bind"))
1143 return option_bind_command(argc, argv);
1144
1145 config_msg = "Unknown option command";
1146 return ERR;
1147 }
1148
1149 static int
1150 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1151 {
1152 int status = OK;
1153
1154 config_lineno++;
1155 config_msg = "Internal error";
1156
1157 /* Check for comment markers, since read_properties() will
1158 * only ensure opt and value are split at first " \t". */
1159 optlen = strcspn(opt, "#");
1160 if (optlen == 0)
1161 return OK;
1162
1163 if (opt[optlen] != 0) {
1164 config_msg = "No option value";
1165 status = ERR;
1166
1167 } else {
1168 /* Look for comment endings in the value. */
1169 size_t len = strcspn(value, "#");
1170
1171 if (len < valuelen) {
1172 valuelen = len;
1173 value[valuelen] = 0;
1174 }
1175
1176 status = set_option(opt, value);
1177 }
1178
1179 if (status == ERR) {
1180 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1181 config_lineno, (int) optlen, opt, config_msg);
1182 config_errors = TRUE;
1183 }
1184
1185 /* Always keep going if errors are encountered. */
1186 return OK;
1187 }
1188
1189 static int
1190 load_options(void)
1191 {
1192 char *home = getenv("HOME");
1193 char buf[SIZEOF_STR];
1194 FILE *file;
1195
1196 config_lineno = 0;
1197 config_errors = FALSE;
1198
1199 if (!home || !string_format(buf, "%s/.tigrc", home))
1200 return ERR;
1201
1202 /* It's ok that the file doesn't exist. */
1203 file = fopen(buf, "r");
1204 if (!file)
1205 return OK;
1206
1207 if (read_properties(file, " \t", read_option) == ERR ||
1208 config_errors == TRUE)
1209 fprintf(stderr, "Errors while loading %s.\n", buf);
1210
1211 return OK;
1212 }
1213
1214
1215 /*
1216 * The viewer
1217 */
1218
1219 struct view;
1220 struct view_ops;
1221
1222 /* The display array of active views and the index of the current view. */
1223 static struct view *display[2];
1224 static unsigned int current_view;
1225
1226 /* Reading from the prompt? */
1227 static bool input_mode = FALSE;
1228
1229 #define foreach_displayed_view(view, i) \
1230 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1231
1232 #define displayed_views() (display[1] != NULL ? 2 : 1)
1233
1234 /* Current head and commit ID */
1235 static char ref_blob[SIZEOF_REF] = "";
1236 static char ref_commit[SIZEOF_REF] = "HEAD";
1237 static char ref_head[SIZEOF_REF] = "HEAD";
1238
1239 struct view {
1240 const char *name; /* View name */
1241 const char *cmd_fmt; /* Default command line format */
1242 const char *cmd_env; /* Command line set via environment */
1243 const char *id; /* Points to either of ref_{head,commit,blob} */
1244
1245 struct view_ops *ops; /* View operations */
1246
1247 enum keymap keymap; /* What keymap does this view have */
1248
1249 char cmd[SIZEOF_STR]; /* Command buffer */
1250 char ref[SIZEOF_REF]; /* Hovered commit reference */
1251 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1252
1253 int height, width; /* The width and height of the main window */
1254 WINDOW *win; /* The main window */
1255 WINDOW *title; /* The title window living below the main window */
1256
1257 /* Navigation */
1258 unsigned long offset; /* Offset of the window top */
1259 unsigned long lineno; /* Current line number */
1260
1261 /* Searching */
1262 char grep[SIZEOF_STR]; /* Search string */
1263 regex_t *regex; /* Pre-compiled regex */
1264
1265 /* If non-NULL, points to the view that opened this view. If this view
1266 * is closed tig will switch back to the parent view. */
1267 struct view *parent;
1268
1269 /* Buffering */
1270 unsigned long lines; /* Total number of lines */
1271 struct line *line; /* Line index */
1272 unsigned long line_size;/* Total number of allocated lines */
1273 unsigned int digits; /* Number of digits in the lines member. */
1274
1275 /* Loading */
1276 FILE *pipe;
1277 time_t start_time;
1278 };
1279
1280 struct view_ops {
1281 /* What type of content being displayed. Used in the title bar. */
1282 const char *type;
1283 /* Open and reads in all view content. */
1284 bool (*open)(struct view *view);
1285 /* Read one line; updates view->line. */
1286 bool (*read)(struct view *view, char *data);
1287 /* Draw one line; @lineno must be < view->height. */
1288 bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1289 /* Depending on view handle a special requests. */
1290 enum request (*request)(struct view *view, enum request request, struct line *line);
1291 /* Search for regex in a line. */
1292 bool (*grep)(struct view *view, struct line *line);
1293 /* Select line */
1294 void (*select)(struct view *view, struct line *line);
1295 };
1296
1297 static struct view_ops pager_ops;
1298 static struct view_ops main_ops;
1299 static struct view_ops tree_ops;
1300 static struct view_ops blob_ops;
1301 static struct view_ops help_ops;
1302 static struct view_ops status_ops;
1303 static struct view_ops stage_ops;
1304
1305 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1306 { name, cmd, #env, ref, ops, map}
1307
1308 #define VIEW_(id, name, ops, ref) \
1309 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1310
1311
1312 static struct view views[] = {
1313 VIEW_(MAIN, "main", &main_ops, ref_head),
1314 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
1315 VIEW_(LOG, "log", &pager_ops, ref_head),
1316 VIEW_(TREE, "tree", &tree_ops, ref_commit),
1317 VIEW_(BLOB, "blob", &blob_ops, ref_blob),
1318 VIEW_(HELP, "help", &help_ops, ""),
1319 VIEW_(PAGER, "pager", &pager_ops, "stdin"),
1320 VIEW_(STATUS, "status", &status_ops, ""),
1321 VIEW_(STAGE, "stage", &stage_ops, ""),
1322 };
1323
1324 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1325
1326 #define foreach_view(view, i) \
1327 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1328
1329 #define view_is_displayed(view) \
1330 (view == display[0] || view == display[1])
1331
1332 static bool
1333 draw_view_line(struct view *view, unsigned int lineno)
1334 {
1335 struct line *line;
1336 bool selected = (view->offset + lineno == view->lineno);
1337 bool draw_ok;
1338
1339 assert(view_is_displayed(view));
1340
1341 if (view->offset + lineno >= view->lines)
1342 return FALSE;
1343
1344 line = &view->line[view->offset + lineno];
1345
1346 if (selected) {
1347 line->selected = TRUE;
1348 view->ops->select(view, line);
1349 } else if (line->selected) {
1350 line->selected = FALSE;
1351 wmove(view->win, lineno, 0);
1352 wclrtoeol(view->win);
1353 }
1354
1355 scrollok(view->win, FALSE);
1356 draw_ok = view->ops->draw(view, line, lineno, selected);
1357 scrollok(view->win, TRUE);
1358
1359 return draw_ok;
1360 }
1361
1362 static void
1363 redraw_view_from(struct view *view, int lineno)
1364 {
1365 assert(0 <= lineno && lineno < view->height);
1366
1367 for (; lineno < view->height; lineno++) {
1368 if (!draw_view_line(view, lineno))
1369 break;
1370 }
1371
1372 redrawwin(view->win);
1373 if (input_mode)
1374 wnoutrefresh(view->win);
1375 else
1376 wrefresh(view->win);
1377 }
1378
1379 static void
1380 redraw_view(struct view *view)
1381 {
1382 wclear(view->win);
1383 redraw_view_from(view, 0);
1384 }
1385
1386
1387 static void
1388 update_view_title(struct view *view)
1389 {
1390 char buf[SIZEOF_STR];
1391 char state[SIZEOF_STR];
1392 size_t bufpos = 0, statelen = 0;
1393
1394 assert(view_is_displayed(view));
1395
1396 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1397 unsigned int view_lines = view->offset + view->height;
1398 unsigned int lines = view->lines
1399 ? MIN(view_lines, view->lines) * 100 / view->lines
1400 : 0;
1401
1402 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1403 view->ops->type,
1404 view->lineno + 1,
1405 view->lines,
1406 lines);
1407
1408 if (view->pipe) {
1409 time_t secs = time(NULL) - view->start_time;
1410
1411 /* Three git seconds are a long time ... */
1412 if (secs > 2)
1413 string_format_from(state, &statelen, " %lds", secs);
1414 }
1415 }
1416
1417 string_format_from(buf, &bufpos, "[%s]", view->name);
1418 if (*view->ref && bufpos < view->width) {
1419 size_t refsize = strlen(view->ref);
1420 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1421
1422 if (minsize < view->width)
1423 refsize = view->width - minsize + 7;
1424 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1425 }
1426
1427 if (statelen && bufpos < view->width) {
1428 string_format_from(buf, &bufpos, " %s", state);
1429 }
1430
1431 if (view == display[current_view])
1432 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1433 else
1434 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1435
1436 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1437 wclrtoeol(view->title);
1438 wmove(view->title, 0, view->width - 1);
1439
1440 if (input_mode)
1441 wnoutrefresh(view->title);
1442 else
1443 wrefresh(view->title);
1444 }
1445
1446 static void
1447 resize_display(void)
1448 {
1449 int offset, i;
1450 struct view *base = display[0];
1451 struct view *view = display[1] ? display[1] : display[0];
1452
1453 /* Setup window dimensions */
1454
1455 getmaxyx(stdscr, base->height, base->width);
1456
1457 /* Make room for the status window. */
1458 base->height -= 1;
1459
1460 if (view != base) {
1461 /* Horizontal split. */
1462 view->width = base->width;
1463 view->height = SCALE_SPLIT_VIEW(base->height);
1464 base->height -= view->height;
1465
1466 /* Make room for the title bar. */
1467 view->height -= 1;
1468 }
1469
1470 /* Make room for the title bar. */
1471 base->height -= 1;
1472
1473 offset = 0;
1474
1475 foreach_displayed_view (view, i) {
1476 if (!view->win) {
1477 view->win = newwin(view->height, 0, offset, 0);
1478 if (!view->win)
1479 die("Failed to create %s view", view->name);
1480
1481 scrollok(view->win, TRUE);
1482
1483 view->title = newwin(1, 0, offset + view->height, 0);
1484 if (!view->title)
1485 die("Failed to create title window");
1486
1487 } else {
1488 wresize(view->win, view->height, view->width);
1489 mvwin(view->win, offset, 0);
1490 mvwin(view->title, offset + view->height, 0);
1491 }
1492
1493 offset += view->height + 1;
1494 }
1495 }
1496
1497 static void
1498 redraw_display(void)
1499 {
1500 struct view *view;
1501 int i;
1502
1503 foreach_displayed_view (view, i) {
1504 redraw_view(view);
1505 update_view_title(view);
1506 }
1507 }
1508
1509 static void
1510 update_display_cursor(struct view *view)
1511 {
1512 /* Move the cursor to the right-most column of the cursor line.
1513 *
1514 * XXX: This could turn out to be a bit expensive, but it ensures that
1515 * the cursor does not jump around. */
1516 if (view->lines) {
1517 wmove(view->win, view->lineno - view->offset, view->width - 1);
1518 wrefresh(view->win);
1519 }
1520 }
1521
1522 /*
1523 * Navigation
1524 */
1525
1526 /* Scrolling backend */
1527 static void
1528 do_scroll_view(struct view *view, int lines)
1529 {
1530 bool redraw_current_line = FALSE;
1531
1532 /* The rendering expects the new offset. */
1533 view->offset += lines;
1534
1535 assert(0 <= view->offset && view->offset < view->lines);
1536 assert(lines);
1537
1538 /* Move current line into the view. */
1539 if (view->lineno < view->offset) {
1540 view->lineno = view->offset;
1541 redraw_current_line = TRUE;
1542 } else if (view->lineno >= view->offset + view->height) {
1543 view->lineno = view->offset + view->height - 1;
1544 redraw_current_line = TRUE;
1545 }
1546
1547 assert(view->offset <= view->lineno && view->lineno < view->lines);
1548
1549 /* Redraw the whole screen if scrolling is pointless. */
1550 if (view->height < ABS(lines)) {
1551 redraw_view(view);
1552
1553 } else {
1554 int line = lines > 0 ? view->height - lines : 0;
1555 int end = line + ABS(lines);
1556
1557 wscrl(view->win, lines);
1558
1559 for (; line < end; line++) {
1560 if (!draw_view_line(view, line))
1561 break;
1562 }
1563
1564 if (redraw_current_line)
1565 draw_view_line(view, view->lineno - view->offset);
1566 }
1567
1568 redrawwin(view->win);
1569 wrefresh(view->win);
1570 report("");
1571 }
1572
1573 /* Scroll frontend */
1574 static void
1575 scroll_view(struct view *view, enum request request)
1576 {
1577 int lines = 1;
1578
1579 assert(view_is_displayed(view));
1580
1581 switch (request) {
1582 case REQ_SCROLL_PAGE_DOWN:
1583 lines = view->height;
1584 case REQ_SCROLL_LINE_DOWN:
1585 if (view->offset + lines > view->lines)
1586 lines = view->lines - view->offset;
1587
1588 if (lines == 0 || view->offset + view->height >= view->lines) {
1589 report("Cannot scroll beyond the last line");
1590 return;
1591 }
1592 break;
1593
1594 case REQ_SCROLL_PAGE_UP:
1595 lines = view->height;
1596 case REQ_SCROLL_LINE_UP:
1597 if (lines > view->offset)
1598 lines = view->offset;
1599
1600 if (lines == 0) {
1601 report("Cannot scroll beyond the first line");
1602 return;
1603 }
1604
1605 lines = -lines;
1606 break;
1607
1608 default:
1609 die("request %d not handled in switch", request);
1610 }
1611
1612 do_scroll_view(view, lines);
1613 }
1614
1615 /* Cursor moving */
1616 static void
1617 move_view(struct view *view, enum request request)
1618 {
1619 int scroll_steps = 0;
1620 int steps;
1621
1622 switch (request) {
1623 case REQ_MOVE_FIRST_LINE:
1624 steps = -view->lineno;
1625 break;
1626
1627 case REQ_MOVE_LAST_LINE:
1628 steps = view->lines - view->lineno - 1;
1629 break;
1630
1631 case REQ_MOVE_PAGE_UP:
1632 steps = view->height > view->lineno
1633 ? -view->lineno : -view->height;
1634 break;
1635
1636 case REQ_MOVE_PAGE_DOWN:
1637 steps = view->lineno + view->height >= view->lines
1638 ? view->lines - view->lineno - 1 : view->height;
1639 break;
1640
1641 case REQ_MOVE_UP:
1642 steps = -1;
1643 break;
1644
1645 case REQ_MOVE_DOWN:
1646 steps = 1;
1647 break;
1648
1649 default:
1650 die("request %d not handled in switch", request);
1651 }
1652
1653 if (steps <= 0 && view->lineno == 0) {
1654 report("Cannot move beyond the first line");
1655 return;
1656
1657 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1658 report("Cannot move beyond the last line");
1659 return;
1660 }
1661
1662 /* Move the current line */
1663 view->lineno += steps;
1664 assert(0 <= view->lineno && view->lineno < view->lines);
1665
1666 /* Check whether the view needs to be scrolled */
1667 if (view->lineno < view->offset ||
1668 view->lineno >= view->offset + view->height) {
1669 scroll_steps = steps;
1670 if (steps < 0 && -steps > view->offset) {
1671 scroll_steps = -view->offset;
1672
1673 } else if (steps > 0) {
1674 if (view->lineno == view->lines - 1 &&
1675 view->lines > view->height) {
1676 scroll_steps = view->lines - view->offset - 1;
1677 if (scroll_steps >= view->height)
1678 scroll_steps -= view->height - 1;
1679 }
1680 }
1681 }
1682
1683 if (!view_is_displayed(view)) {
1684 view->offset += scroll_steps;
1685 assert(0 <= view->offset && view->offset < view->lines);
1686 view->ops->select(view, &view->line[view->lineno]);
1687 return;
1688 }
1689
1690 /* Repaint the old "current" line if we be scrolling */
1691 if (ABS(steps) < view->height)
1692 draw_view_line(view, view->lineno - steps - view->offset);
1693
1694 if (scroll_steps) {
1695 do_scroll_view(view, scroll_steps);
1696 return;
1697 }
1698
1699 /* Draw the current line */
1700 draw_view_line(view, view->lineno - view->offset);
1701
1702 redrawwin(view->win);
1703 wrefresh(view->win);
1704 report("");
1705 }
1706
1707
1708 /*
1709 * Searching
1710 */
1711
1712 static void search_view(struct view *view, enum request request);
1713
1714 static bool
1715 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1716 {
1717 assert(view_is_displayed(view));
1718
1719 if (!view->ops->grep(view, line))
1720 return FALSE;
1721
1722 if (lineno - view->offset >= view->height) {
1723 view->offset = lineno;
1724 view->lineno = lineno;
1725 redraw_view(view);
1726
1727 } else {
1728 unsigned long old_lineno = view->lineno - view->offset;
1729
1730 view->lineno = lineno;
1731 draw_view_line(view, old_lineno);
1732
1733 draw_view_line(view, view->lineno - view->offset);
1734 redrawwin(view->win);
1735 wrefresh(view->win);
1736 }
1737
1738 report("Line %ld matches '%s'", lineno + 1, view->grep);
1739 return TRUE;
1740 }
1741
1742 static void
1743 find_next(struct view *view, enum request request)
1744 {
1745 unsigned long lineno = view->lineno;
1746 int direction;
1747
1748 if (!*view->grep) {
1749 if (!*opt_search)
1750 report("No previous search");
1751 else
1752 search_view(view, request);
1753 return;
1754 }
1755
1756 switch (request) {
1757 case REQ_SEARCH:
1758 case REQ_FIND_NEXT:
1759 direction = 1;
1760 break;
1761
1762 case REQ_SEARCH_BACK:
1763 case REQ_FIND_PREV:
1764 direction = -1;
1765 break;
1766
1767 default:
1768 return;
1769 }
1770
1771 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1772 lineno += direction;
1773
1774 /* Note, lineno is unsigned long so will wrap around in which case it
1775 * will become bigger than view->lines. */
1776 for (; lineno < view->lines; lineno += direction) {
1777 struct line *line = &view->line[lineno];
1778
1779 if (find_next_line(view, lineno, line))
1780 return;
1781 }
1782
1783 report("No match found for '%s'", view->grep);
1784 }
1785
1786 static void
1787 search_view(struct view *view, enum request request)
1788 {
1789 int regex_err;
1790
1791 if (view->regex) {
1792 regfree(view->regex);
1793 *view->grep = 0;
1794 } else {
1795 view->regex = calloc(1, sizeof(*view->regex));
1796 if (!view->regex)
1797 return;
1798 }
1799
1800 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1801 if (regex_err != 0) {
1802 char buf[SIZEOF_STR] = "unknown error";
1803
1804 regerror(regex_err, view->regex, buf, sizeof(buf));
1805 report("Search failed: %s", buf);
1806 return;
1807 }
1808
1809 string_copy(view->grep, opt_search);
1810
1811 find_next(view, request);
1812 }
1813
1814 /*
1815 * Incremental updating
1816 */
1817
1818 static void
1819 end_update(struct view *view)
1820 {
1821 if (!view->pipe)
1822 return;
1823 set_nonblocking_input(FALSE);
1824 if (view->pipe == stdin)
1825 fclose(view->pipe);
1826 else
1827 pclose(view->pipe);
1828 view->pipe = NULL;
1829 }
1830
1831 static bool
1832 begin_update(struct view *view)
1833 {
1834 if (view->pipe)
1835 end_update(view);
1836
1837 if (opt_cmd[0]) {
1838 string_copy(view->cmd, opt_cmd);
1839 opt_cmd[0] = 0;
1840 /* When running random commands, initially show the
1841 * command in the title. However, it maybe later be
1842 * overwritten if a commit line is selected. */
1843 if (view == VIEW(REQ_VIEW_PAGER))
1844 string_copy(view->ref, view->cmd);
1845 else
1846 view->ref[0] = 0;
1847
1848 } else if (view == VIEW(REQ_VIEW_TREE)) {
1849 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1850 char path[SIZEOF_STR];
1851
1852 if (strcmp(view->vid, view->id))
1853 opt_path[0] = path[0] = 0;
1854 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1855 return FALSE;
1856
1857 if (!string_format(view->cmd, format, view->id, path))
1858 return FALSE;
1859
1860 } else {
1861 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1862 const char *id = view->id;
1863
1864 if (!string_format(view->cmd, format, id, id, id, id, id))
1865 return FALSE;
1866
1867 /* Put the current ref_* value to the view title ref
1868 * member. This is needed by the blob view. Most other
1869 * views sets it automatically after loading because the
1870 * first line is a commit line. */
1871 string_copy_rev(view->ref, view->id);
1872 }
1873
1874 /* Special case for the pager view. */
1875 if (opt_pipe) {
1876 view->pipe = opt_pipe;
1877 opt_pipe = NULL;
1878 } else {
1879 view->pipe = popen(view->cmd, "r");
1880 }
1881
1882 if (!view->pipe)
1883 return FALSE;
1884
1885 set_nonblocking_input(TRUE);
1886
1887 view->offset = 0;
1888 view->lines = 0;
1889 view->lineno = 0;
1890 string_copy_rev(view->vid, view->id);
1891
1892 if (view->line) {
1893 int i;
1894
1895 for (i = 0; i < view->lines; i++)
1896 if (view->line[i].data)
1897 free(view->line[i].data);
1898
1899 free(view->line);
1900 view->line = NULL;
1901 }
1902
1903 view->start_time = time(NULL);
1904
1905 return TRUE;
1906 }
1907
1908 static struct line *
1909 realloc_lines(struct view *view, size_t line_size)
1910 {
1911 struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1912
1913 if (!tmp)
1914 return NULL;
1915
1916 view->line = tmp;
1917 view->line_size = line_size;
1918 return view->line;
1919 }
1920
1921 static bool
1922 update_view(struct view *view)
1923 {
1924 char in_buffer[BUFSIZ];
1925 char out_buffer[BUFSIZ * 2];
1926 char *line;
1927 /* The number of lines to read. If too low it will cause too much
1928 * redrawing (and possible flickering), if too high responsiveness
1929 * will suffer. */
1930 unsigned long lines = view->height;
1931 int redraw_from = -1;
1932
1933 if (!view->pipe)
1934 return TRUE;
1935
1936 /* Only redraw if lines are visible. */
1937 if (view->offset + view->height >= view->lines)
1938 redraw_from = view->lines - view->offset;
1939
1940 /* FIXME: This is probably not perfect for backgrounded views. */
1941 if (!realloc_lines(view, view->lines + lines))
1942 goto alloc_error;
1943
1944 while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
1945 size_t linelen = strlen(line);
1946
1947 if (linelen)
1948 line[linelen - 1] = 0;
1949
1950 if (opt_iconv != ICONV_NONE) {
1951 ICONV_CONST char *inbuf = line;
1952 size_t inlen = linelen;
1953
1954 char *outbuf = out_buffer;
1955 size_t outlen = sizeof(out_buffer);
1956
1957 size_t ret;
1958
1959 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
1960 if (ret != (size_t) -1) {
1961 line = out_buffer;
1962 linelen = strlen(out_buffer);
1963 }
1964 }
1965
1966 if (!view->ops->read(view, line))
1967 goto alloc_error;
1968
1969 if (lines-- == 1)
1970 break;
1971 }
1972
1973 {
1974 int digits;
1975
1976 lines = view->lines;
1977 for (digits = 0; lines; digits++)
1978 lines /= 10;
1979
1980 /* Keep the displayed view in sync with line number scaling. */
1981 if (digits != view->digits) {
1982 view->digits = digits;
1983 redraw_from = 0;
1984 }
1985 }
1986
1987 if (!view_is_displayed(view))
1988 goto check_pipe;
1989
1990 if (view == VIEW(REQ_VIEW_TREE)) {
1991 /* Clear the view and redraw everything since the tree sorting
1992 * might have rearranged things. */
1993 redraw_view(view);
1994
1995 } else if (redraw_from >= 0) {
1996 /* If this is an incremental update, redraw the previous line
1997 * since for commits some members could have changed when
1998 * loading the main view. */
1999 if (redraw_from > 0)
2000 redraw_from--;
2001
2002 /* Since revision graph visualization requires knowledge
2003 * about the parent commit, it causes a further one-off
2004 * needed to be redrawn for incremental updates. */
2005 if (redraw_from > 0 && opt_rev_graph)
2006 redraw_from--;
2007
2008 /* Incrementally draw avoids flickering. */
2009 redraw_view_from(view, redraw_from);
2010 }
2011
2012 /* Update the title _after_ the redraw so that if the redraw picks up a
2013 * commit reference in view->ref it'll be available here. */
2014 update_view_title(view);
2015
2016 check_pipe:
2017 if (ferror(view->pipe)) {
2018 report("Failed to read: %s", strerror(errno));
2019 goto end;
2020
2021 } else if (feof(view->pipe)) {
2022 report("");
2023 goto end;
2024 }
2025
2026 return TRUE;
2027
2028 alloc_error:
2029 report("Allocation failure");
2030
2031 end:
2032 view->ops->read(view, NULL);
2033 end_update(view);
2034 return FALSE;
2035 }
2036
2037 static struct line *
2038 add_line_data(struct view *view, void *data, enum line_type type)
2039 {
2040 struct line *line = &view->line[view->lines++];
2041
2042 memset(line, 0, sizeof(*line));
2043 line->type = type;
2044 line->data = data;
2045
2046 return line;
2047 }
2048
2049 static struct line *
2050 add_line_text(struct view *view, char *data, enum line_type type)
2051 {
2052 if (data)
2053 data = strdup(data);
2054
2055 return data ? add_line_data(view, data, type) : NULL;
2056 }
2057
2058
2059 /*
2060 * View opening
2061 */
2062
2063 enum open_flags {
2064 OPEN_DEFAULT = 0, /* Use default view switching. */
2065 OPEN_SPLIT = 1, /* Split current view. */
2066 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2067 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2068 };
2069
2070 static void
2071 open_view(struct view *prev, enum request request, enum open_flags flags)
2072 {
2073 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2074 bool split = !!(flags & OPEN_SPLIT);
2075 bool reload = !!(flags & OPEN_RELOAD);
2076 struct view *view = VIEW(request);
2077 int nviews = displayed_views();
2078 struct view *base_view = display[0];
2079
2080 if (view == prev && nviews == 1 && !reload) {
2081 report("Already in %s view", view->name);
2082 return;
2083 }
2084
2085 if (view->ops->open) {
2086 if (!view->ops->open(view)) {
2087 report("Failed to load %s view", view->name);
2088 return;
2089 }
2090
2091 } else if ((reload || strcmp(view->vid, view->id)) &&
2092 !begin_update(view)) {
2093 report("Failed to load %s view", view->name);
2094 return;
2095 }
2096
2097 if (split) {
2098 display[1] = view;
2099 if (!backgrounded)
2100 current_view = 1;
2101 } else {
2102 /* Maximize the current view. */
2103 memset(display, 0, sizeof(display));
2104 current_view = 0;
2105 display[current_view] = view;
2106 }
2107
2108 /* Resize the view when switching between split- and full-screen,
2109 * or when switching between two different full-screen views. */
2110 if (nviews != displayed_views() ||
2111 (nviews == 1 && base_view != display[0]))
2112 resize_display();
2113
2114 if (split && prev->lineno - prev->offset >= prev->height) {
2115 /* Take the title line into account. */
2116 int lines = prev->lineno - prev->offset - prev->height + 1;
2117
2118 /* Scroll the view that was split if the current line is
2119 * outside the new limited view. */
2120 do_scroll_view(prev, lines);
2121 }
2122
2123 if (prev && view != prev) {
2124 if (split && !backgrounded) {
2125 /* "Blur" the previous view. */
2126 update_view_title(prev);
2127 }
2128
2129 view->parent = prev;
2130 }
2131
2132 if (view->pipe && view->lines == 0) {
2133 /* Clear the old view and let the incremental updating refill
2134 * the screen. */
2135 wclear(view->win);
2136 report("");
2137 } else {
2138 redraw_view(view);
2139 report("");
2140 }
2141
2142 /* If the view is backgrounded the above calls to report()
2143 * won't redraw the view title. */
2144 if (backgrounded)
2145 update_view_title(view);
2146 }
2147
2148 static void
2149 open_editor(bool from_root, char *file)
2150 {
2151 char cmd[SIZEOF_STR];
2152 char file_sq[SIZEOF_STR];
2153 char *editor;
2154 char *prefix = from_root ? opt_cdup : "";
2155
2156 editor = getenv("GIT_EDITOR");
2157 if (!editor && *opt_editor)
2158 editor = opt_editor;
2159 if (!editor)
2160 editor = getenv("VISUAL");
2161 if (!editor)
2162 editor = getenv("EDITOR");
2163 if (!editor)
2164 editor = "vi";
2165
2166 if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2167 string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2168 def_prog_mode(); /* save current tty modes */
2169 endwin(); /* restore original tty modes */
2170 system(cmd);
2171 reset_prog_mode();
2172 redraw_display();
2173 }
2174 }
2175
2176 /*
2177 * User request switch noodle
2178 */
2179
2180 static int
2181 view_driver(struct view *view, enum request request)
2182 {
2183 int i;
2184
2185 if (request == REQ_NONE) {
2186 doupdate();
2187 return TRUE;
2188 }
2189
2190 if (view && view->lines) {
2191 request = view->ops->request(view, request, &view->line[view->lineno]);
2192 if (request == REQ_NONE)
2193 return TRUE;
2194 }
2195
2196 switch (request) {
2197 case REQ_MOVE_UP:
2198 case REQ_MOVE_DOWN:
2199 case REQ_MOVE_PAGE_UP:
2200 case REQ_MOVE_PAGE_DOWN:
2201 case REQ_MOVE_FIRST_LINE:
2202 case REQ_MOVE_LAST_LINE:
2203 move_view(view, request);
2204 break;
2205
2206 case REQ_SCROLL_LINE_DOWN:
2207 case REQ_SCROLL_LINE_UP:
2208 case REQ_SCROLL_PAGE_DOWN:
2209 case REQ_SCROLL_PAGE_UP:
2210 scroll_view(view, request);
2211 break;
2212
2213 case REQ_VIEW_BLOB:
2214 if (!ref_blob[0]) {
2215 report("No file chosen, press %s to open tree view",
2216 get_key(REQ_VIEW_TREE));
2217 break;
2218 }
2219 open_view(view, request, OPEN_DEFAULT);
2220 break;
2221
2222 case REQ_VIEW_PAGER:
2223 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2224 report("No pager content, press %s to run command from prompt",
2225 get_key(REQ_PROMPT));
2226 break;
2227 }
2228 open_view(view, request, OPEN_DEFAULT);
2229 break;
2230
2231 case REQ_VIEW_STAGE:
2232 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2233 report("No stage content, press %s to open the status view and choose file",
2234 get_key(REQ_VIEW_STATUS));
2235 break;
2236 }
2237 open_view(view, request, OPEN_DEFAULT);
2238 break;
2239
2240 case REQ_VIEW_STATUS:
2241 if (opt_is_inside_work_tree == FALSE) {
2242 report("The status view requires a working tree");
2243 break;
2244 }
2245 open_view(view, request, OPEN_DEFAULT);
2246 break;
2247
2248 case REQ_VIEW_MAIN:
2249 case REQ_VIEW_DIFF:
2250 case REQ_VIEW_LOG:
2251 case REQ_VIEW_TREE:
2252 case REQ_VIEW_HELP:
2253 open_view(view, request, OPEN_DEFAULT);
2254 break;
2255
2256 case REQ_NEXT:
2257 case REQ_PREVIOUS:
2258 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2259
2260 if ((view == VIEW(REQ_VIEW_DIFF) &&
2261 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2262 (view == VIEW(REQ_VIEW_STAGE) &&
2263 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2264 (view == VIEW(REQ_VIEW_BLOB) &&
2265 view->parent == VIEW(REQ_VIEW_TREE))) {
2266 int line;
2267
2268 view = view->parent;
2269 line = view->lineno;
2270 move_view(view, request);
2271 if (view_is_displayed(view))
2272 update_view_title(view);
2273 if (line != view->lineno)
2274 view->ops->request(view, REQ_ENTER,
2275 &view->line[view->lineno]);
2276
2277 } else {
2278 move_view(view, request);
2279 }
2280 break;
2281
2282 case REQ_VIEW_NEXT:
2283 {
2284 int nviews = displayed_views();
2285 int next_view = (current_view + 1) % nviews;
2286
2287 if (next_view == current_view) {
2288 report("Only one view is displayed");
2289 break;
2290 }
2291
2292 current_view = next_view;
2293 /* Blur out the title of the previous view. */
2294 update_view_title(view);
2295 report("");
2296 break;
2297 }
2298 case REQ_REFRESH:
2299 report("Refreshing is not yet supported for the %s view", view->name);
2300 break;
2301
2302 case REQ_TOGGLE_LINENO:
2303 opt_line_number = !opt_line_number;
2304 redraw_display();
2305 break;
2306
2307 case REQ_TOGGLE_REV_GRAPH:
2308 opt_rev_graph = !opt_rev_graph;
2309 redraw_display();
2310 break;
2311
2312 case REQ_PROMPT:
2313 /* Always reload^Wrerun commands from the prompt. */
2314 open_view(view, opt_request, OPEN_RELOAD);
2315 break;
2316
2317 case REQ_SEARCH:
2318 case REQ_SEARCH_BACK:
2319 search_view(view, request);
2320 break;
2321
2322 case REQ_FIND_NEXT:
2323 case REQ_FIND_PREV:
2324 find_next(view, request);
2325 break;
2326
2327 case REQ_STOP_LOADING:
2328 for (i = 0; i < ARRAY_SIZE(views); i++) {
2329 view = &views[i];
2330 if (view->pipe)
2331 report("Stopped loading the %s view", view->name),
2332 end_update(view);
2333 }
2334 break;
2335
2336 case REQ_SHOW_VERSION:
2337 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2338 return TRUE;
2339
2340 case REQ_SCREEN_RESIZE:
2341 resize_display();
2342 /* Fall-through */
2343 case REQ_SCREEN_REDRAW:
2344 redraw_display();
2345 break;
2346
2347 case REQ_EDIT:
2348 report("Nothing to edit");
2349 break;
2350
2351 case REQ_CHERRY_PICK:
2352 report("Nothing to cherry-pick");
2353 break;
2354
2355 case REQ_ENTER:
2356 report("Nothing to enter");
2357 break;
2358
2359
2360 case REQ_VIEW_CLOSE:
2361 /* XXX: Mark closed views by letting view->parent point to the
2362 * view itself. Parents to closed view should never be
2363 * followed. */
2364 if (view->parent &&
2365 view->parent->parent != view->parent) {
2366 memset(display, 0, sizeof(display));
2367 current_view = 0;
2368 display[current_view] = view->parent;
2369 view->parent = view;
2370 resize_display();
2371 redraw_display();
2372 break;
2373 }
2374 /* Fall-through */
2375 case REQ_QUIT:
2376 return FALSE;
2377
2378 default:
2379 /* An unknown key will show most commonly used commands. */
2380 report("Unknown key, press 'h' for help");
2381 return TRUE;
2382 }
2383
2384 return TRUE;
2385 }
2386
2387
2388 /*
2389 * Pager backend
2390 */
2391
2392 static bool
2393 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2394 {
2395 char *text = line->data;
2396 enum line_type type = line->type;
2397 int textlen = strlen(text);
2398 int attr;
2399
2400 wmove(view->win, lineno, 0);
2401
2402 if (selected) {
2403 type = LINE_CURSOR;
2404 wchgat(view->win, -1, 0, type, NULL);
2405 }
2406
2407 attr = get_line_attr(type);
2408 wattrset(view->win, attr);
2409
2410 if (opt_line_number || opt_tab_size < TABSIZE) {
2411 static char spaces[] = " ";
2412 int col_offset = 0, col = 0;
2413
2414 if (opt_line_number) {
2415 unsigned long real_lineno = view->offset + lineno + 1;
2416
2417 if (real_lineno == 1 ||
2418 (real_lineno % opt_num_interval) == 0) {
2419 wprintw(view->win, "%.*d", view->digits, real_lineno);
2420
2421 } else {
2422 waddnstr(view->win, spaces,
2423 MIN(view->digits, STRING_SIZE(spaces)));
2424 }
2425 waddstr(view->win, ": ");
2426 col_offset = view->digits + 2;
2427 }
2428
2429 while (text && col_offset + col < view->width) {
2430 int cols_max = view->width - col_offset - col;
2431 char *pos = text;
2432 int cols;
2433
2434 if (*text == '\t') {
2435 text++;
2436 assert(sizeof(spaces) > TABSIZE);
2437 pos = spaces;
2438 cols = opt_tab_size - (col % opt_tab_size);
2439
2440 } else {
2441 text = strchr(text, '\t');
2442 cols = line ? text - pos : strlen(pos);
2443 }
2444
2445 waddnstr(view->win, pos, MIN(cols, cols_max));
2446 col += cols;
2447 }
2448
2449 } else {
2450 int col = 0, pos = 0;
2451
2452 for (; pos < textlen && col < view->width; pos++, col++)
2453 if (text[pos] == '\t')
2454 col += TABSIZE - (col % TABSIZE) - 1;
2455
2456 waddnstr(view->win, text, pos);
2457 }
2458
2459 return TRUE;
2460 }
2461
2462 static bool
2463 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2464 {
2465 char refbuf[SIZEOF_STR];
2466 char *ref = NULL;
2467 FILE *pipe;
2468
2469 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2470 return TRUE;
2471
2472 pipe = popen(refbuf, "r");
2473 if (!pipe)
2474 return TRUE;
2475
2476 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2477 ref = chomp_string(ref);
2478 pclose(pipe);
2479
2480 if (!ref || !*ref)
2481 return TRUE;
2482
2483 /* This is the only fatal call, since it can "corrupt" the buffer. */
2484 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2485 return FALSE;
2486
2487 return TRUE;
2488 }
2489
2490 static void
2491 add_pager_refs(struct view *view, struct line *line)
2492 {
2493 char buf[SIZEOF_STR];
2494 char *commit_id = line->data + STRING_SIZE("commit ");
2495 struct ref **refs;
2496 size_t bufpos = 0, refpos = 0;
2497 const char *sep = "Refs: ";
2498 bool is_tag = FALSE;
2499
2500 assert(line->type == LINE_COMMIT);
2501
2502 refs = get_refs(commit_id);
2503 if (!refs) {
2504 if (view == VIEW(REQ_VIEW_DIFF))
2505 goto try_add_describe_ref;
2506 return;
2507 }
2508
2509 do {
2510 struct ref *ref = refs[refpos];
2511 char *fmt = ref->tag ? "%s[%s]" :
2512 ref->remote ? "%s<%s>" : "%s%s";
2513
2514 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2515 return;
2516 sep = ", ";
2517 if (ref->tag)
2518 is_tag = TRUE;
2519 } while (refs[refpos++]->next);
2520
2521 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2522 try_add_describe_ref:
2523 /* Add <tag>-g<commit_id> "fake" reference. */
2524 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2525 return;
2526 }
2527
2528 if (bufpos == 0)
2529 return;
2530
2531 if (!realloc_lines(view, view->line_size + 1))
2532 return;
2533
2534 add_line_text(view, buf, LINE_PP_REFS);
2535 }
2536
2537 static bool
2538 pager_read(struct view *view, char *data)
2539 {
2540 struct line *line;
2541
2542 if (!data)
2543 return TRUE;
2544
2545 line = add_line_text(view, data, get_line_type(data));
2546 if (!line)
2547 return FALSE;
2548
2549 if (line->type == LINE_COMMIT &&
2550 (view == VIEW(REQ_VIEW_DIFF) ||
2551 view == VIEW(REQ_VIEW_LOG)))
2552 add_pager_refs(view, line);
2553
2554 return TRUE;
2555 }
2556
2557 static enum request
2558 pager_request(struct view *view, enum request request, struct line *line)
2559 {
2560 int split = 0;
2561
2562 if (request != REQ_ENTER)
2563 return request;
2564
2565 if (line->type == LINE_COMMIT &&
2566 (view == VIEW(REQ_VIEW_LOG) ||
2567 view == VIEW(REQ_VIEW_PAGER))) {
2568 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2569 split = 1;
2570 }
2571
2572 /* Always scroll the view even if it was split. That way
2573 * you can use Enter to scroll through the log view and
2574 * split open each commit diff. */
2575 scroll_view(view, REQ_SCROLL_LINE_DOWN);
2576
2577 /* FIXME: A minor workaround. Scrolling the view will call report("")
2578 * but if we are scrolling a non-current view this won't properly
2579 * update the view title. */
2580 if (split)
2581 update_view_title(view);
2582
2583 return REQ_NONE;
2584 }
2585
2586 static bool
2587 pager_grep(struct view *view, struct line *line)
2588 {
2589 regmatch_t pmatch;
2590 char *text = line->data;
2591
2592 if (!*text)
2593 return FALSE;
2594
2595 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2596 return FALSE;
2597
2598 return TRUE;
2599 }
2600
2601 static void
2602 pager_select(struct view *view, struct line *line)
2603 {
2604 if (line->type == LINE_COMMIT) {
2605 char *text = line->data + STRING_SIZE("commit ");
2606
2607 if (view != VIEW(REQ_VIEW_PAGER))
2608 string_copy_rev(view->ref, text);
2609 string_copy_rev(ref_commit, text);
2610 }
2611 }
2612
2613 static struct view_ops pager_ops = {
2614 "line",
2615 NULL,
2616 pager_read,
2617 pager_draw,
2618 pager_request,
2619 pager_grep,
2620 pager_select,
2621 };
2622
2623
2624 /*
2625 * Help backend
2626 */
2627
2628 static bool
2629 help_open(struct view *view)
2630 {
2631 char buf[BUFSIZ];
2632 int lines = ARRAY_SIZE(req_info) + 2;
2633 int i;
2634
2635 if (view->lines > 0)
2636 return TRUE;
2637
2638 for (i = 0; i < ARRAY_SIZE(req_info); i++)
2639 if (!req_info[i].request)
2640 lines++;
2641
2642 view->line = calloc(lines, sizeof(*view->line));
2643 if (!view->line)
2644 return FALSE;
2645
2646 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2647
2648 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2649 char *key;
2650
2651 if (req_info[i].request == REQ_NONE)
2652 continue;
2653
2654 if (!req_info[i].request) {
2655 add_line_text(view, "", LINE_DEFAULT);
2656 add_line_text(view, req_info[i].help, LINE_DEFAULT);
2657 continue;
2658 }
2659
2660 key = get_key(req_info[i].request);
2661 if (!*key)
2662 key = "(no key defined)";
2663
2664 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
2665 continue;
2666
2667 add_line_text(view, buf, LINE_DEFAULT);
2668 }
2669
2670 return TRUE;
2671 }
2672
2673 static struct view_ops help_ops = {
2674 "line",
2675 help_open,
2676 NULL,
2677 pager_draw,
2678 pager_request,
2679 pager_grep,
2680 pager_select,
2681 };
2682
2683
2684 /*
2685 * Tree backend
2686 */
2687
2688 struct tree_stack_entry {
2689 struct tree_stack_entry *prev; /* Entry below this in the stack */
2690 unsigned long lineno; /* Line number to restore */
2691 char *name; /* Position of name in opt_path */
2692 };
2693
2694 /* The top of the path stack. */
2695 static struct tree_stack_entry *tree_stack = NULL;
2696 unsigned long tree_lineno = 0;
2697
2698 static void
2699 pop_tree_stack_entry(void)
2700 {
2701 struct tree_stack_entry *entry = tree_stack;
2702
2703 tree_lineno = entry->lineno;
2704 entry->name[0] = 0;
2705 tree_stack = entry->prev;
2706 free(entry);
2707 }
2708
2709 static void
2710 push_tree_stack_entry(char *name, unsigned long lineno)
2711 {
2712 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2713 size_t pathlen = strlen(opt_path);
2714
2715 if (!entry)
2716 return;
2717
2718 entry->prev = tree_stack;
2719 entry->name = opt_path + pathlen;
2720 tree_stack = entry;
2721
2722 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2723 pop_tree_stack_entry();
2724 return;
2725 }
2726
2727 /* Move the current line to the first tree entry. */
2728 tree_lineno = 1;
2729 entry->lineno = lineno;
2730 }
2731
2732 /* Parse output from git-ls-tree(1):
2733 *
2734 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2735 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2736 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2737 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2738 */
2739
2740 #define SIZEOF_TREE_ATTR \
2741 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2742
2743 #define TREE_UP_FORMAT "040000 tree %s\t.."
2744
2745 static int
2746 tree_compare_entry(enum line_type type1, char *name1,
2747 enum line_type type2, char *name2)
2748 {
2749 if (type1 != type2) {
2750 if (type1 == LINE_TREE_DIR)
2751 return -1;
2752 return 1;
2753 }
2754
2755 return strcmp(name1, name2);
2756 }
2757
2758 static bool
2759 tree_read(struct view *view, char *text)
2760 {
2761 size_t textlen = text ? strlen(text) : 0;
2762 char buf[SIZEOF_STR];
2763 unsigned long pos;
2764 enum line_type type;
2765 bool first_read = view->lines == 0;
2766
2767 if (textlen <= SIZEOF_TREE_ATTR)
2768 return FALSE;
2769
2770 type = text[STRING_SIZE("100644 ")] == 't'
2771 ? LINE_TREE_DIR : LINE_TREE_FILE;
2772
2773 if (first_read) {
2774 /* Add path info line */
2775 if (!string_format(buf, "Directory path /%s", opt_path) ||
2776 !realloc_lines(view, view->line_size + 1) ||
2777 !add_line_text(view, buf, LINE_DEFAULT))
2778 return FALSE;
2779
2780 /* Insert "link" to parent directory. */
2781 if (*opt_path) {
2782 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2783 !realloc_lines(view, view->line_size + 1) ||
2784 !add_line_text(view, buf, LINE_TREE_DIR))
2785 return FALSE;
2786 }
2787 }
2788
2789 /* Strip the path part ... */
2790 if (*opt_path) {
2791 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2792 size_t striplen = strlen(opt_path);
2793 char *path = text + SIZEOF_TREE_ATTR;
2794
2795 if (pathlen > striplen)
2796 memmove(path, path + striplen,
2797 pathlen - striplen + 1);
2798 }
2799
2800 /* Skip "Directory ..." and ".." line. */
2801 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2802 struct line *line = &view->line[pos];
2803 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2804 char *path2 = text + SIZEOF_TREE_ATTR;
2805 int cmp = tree_compare_entry(line->type, path1, type, path2);
2806
2807 if (cmp <= 0)
2808 continue;
2809
2810 text = strdup(text);
2811 if (!text)
2812 return FALSE;
2813
2814 if (view->lines > pos)
2815 memmove(&view->line[pos + 1], &view->line[pos],
2816 (view->lines - pos) * sizeof(*line));
2817
2818 line = &view->line[pos];
2819 line->data = text;
2820 line->type = type;
2821 view->lines++;
2822 return TRUE;
2823 }
2824
2825 if (!add_line_text(view, text, type))
2826 return FALSE;
2827
2828 if (tree_lineno > view->lineno) {
2829 view->lineno = tree_lineno;
2830 tree_lineno = 0;
2831 }
2832
2833 return TRUE;
2834 }
2835
2836 static enum request
2837 tree_request(struct view *view, enum request request, struct line *line)
2838 {
2839 enum open_flags flags;
2840
2841 if (request != REQ_ENTER)
2842 return request;
2843
2844 /* Cleanup the stack if the tree view is at a different tree. */
2845 while (!*opt_path && tree_stack)
2846 pop_tree_stack_entry();
2847
2848 switch (line->type) {
2849 case LINE_TREE_DIR:
2850 /* Depending on whether it is a subdir or parent (updir?) link
2851 * mangle the path buffer. */
2852 if (line == &view->line[1] && *opt_path) {
2853 pop_tree_stack_entry();
2854
2855 } else {
2856 char *data = line->data;
2857 char *basename = data + SIZEOF_TREE_ATTR;
2858
2859 push_tree_stack_entry(basename, view->lineno);
2860 }
2861
2862 /* Trees and subtrees share the same ID, so they are not not
2863 * unique like blobs. */
2864 flags = OPEN_RELOAD;
2865 request = REQ_VIEW_TREE;
2866 break;
2867
2868 case LINE_TREE_FILE:
2869 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
2870 request = REQ_VIEW_BLOB;
2871 break;
2872
2873 default:
2874 return TRUE;
2875 }
2876
2877 open_view(view, request, flags);
2878 if (request == REQ_VIEW_TREE) {
2879 view->lineno = tree_lineno;
2880 }
2881
2882 return REQ_NONE;
2883 }
2884
2885 static void
2886 tree_select(struct view *view, struct line *line)
2887 {
2888 char *text = line->data + STRING_SIZE("100644 blob ");
2889
2890 if (line->type == LINE_TREE_FILE) {
2891 string_copy_rev(ref_blob, text);
2892
2893 } else if (line->type != LINE_TREE_DIR) {
2894 return;
2895 }
2896
2897 string_copy_rev(view->ref, text);
2898 }
2899
2900 static struct view_ops tree_ops = {
2901 "file",
2902 NULL,
2903 tree_read,
2904 pager_draw,
2905 tree_request,
2906 pager_grep,
2907 tree_select,
2908 };
2909
2910 static bool
2911 blob_read(struct view *view, char *line)
2912 {
2913 return add_line_text(view, line, LINE_DEFAULT) != NULL;
2914 }
2915
2916 static struct view_ops blob_ops = {
2917 "line",
2918 NULL,
2919 blob_read,
2920 pager_draw,
2921 pager_request,
2922 pager_grep,
2923 pager_select,
2924 };
2925
2926
2927 /*
2928 * Status backend
2929 */
2930
2931 struct status {
2932 char status;
2933 struct {
2934 mode_t mode;
2935 char rev[SIZEOF_REV];
2936 } old;
2937 struct {
2938 mode_t mode;
2939 char rev[SIZEOF_REV];
2940 } new;
2941 char name[SIZEOF_STR];
2942 };
2943
2944 static struct status stage_status;
2945 static enum line_type stage_line_type;
2946
2947 /* Get fields from the diff line:
2948 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
2949 */
2950 static inline bool
2951 status_get_diff(struct status *file, char *buf, size_t bufsize)
2952 {
2953 char *old_mode = buf + 1;
2954 char *new_mode = buf + 8;
2955 char *old_rev = buf + 15;
2956 char *new_rev = buf + 56;
2957 char *status = buf + 97;
2958
2959 if (bufsize != 99 ||
2960 old_mode[-1] != ':' ||
2961 new_mode[-1] != ' ' ||
2962 old_rev[-1] != ' ' ||
2963 new_rev[-1] != ' ' ||
2964 status[-1] != ' ')
2965 return FALSE;
2966
2967 file->status = *status;
2968
2969 string_copy_rev(file->old.rev, old_rev);
2970 string_copy_rev(file->new.rev, new_rev);
2971
2972 file->old.mode = strtoul(old_mode, NULL, 8);
2973 file->new.mode = strtoul(new_mode, NULL, 8);
2974
2975 file->name[0] = 0;
2976
2977 return TRUE;
2978 }
2979
2980 static bool
2981 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
2982 {
2983 struct status *file = NULL;
2984 char buf[SIZEOF_STR * 4];
2985 size_t bufsize = 0;
2986 FILE *pipe;
2987
2988 pipe = popen(cmd, "r");
2989 if (!pipe)
2990 return FALSE;
2991
2992 add_line_data(view, NULL, type);
2993
2994 while (!feof(pipe) && !ferror(pipe)) {
2995 char *sep;
2996 size_t readsize;
2997
2998 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
2999 if (!readsize)
3000 break;
3001 bufsize += readsize;
3002
3003 /* Process while we have NUL chars. */
3004 while ((sep = memchr(buf, 0, bufsize))) {
3005 size_t sepsize = sep - buf + 1;
3006
3007 if (!file) {
3008 if (!realloc_lines(view, view->line_size + 1))
3009 goto error_out;
3010
3011 file = calloc(1, sizeof(*file));
3012 if (!file)
3013 goto error_out;
3014
3015 add_line_data(view, file, type);
3016 }
3017
3018 /* Parse diff info part. */
3019 if (!diff) {
3020 file->status = '?';
3021
3022 } else if (!file->status) {
3023 if (!status_get_diff(file, buf, sepsize))
3024 goto error_out;
3025
3026 bufsize -= sepsize;
3027 memmove(buf, sep + 1, bufsize);
3028
3029 sep = memchr(buf, 0, bufsize);
3030 if (!sep)
3031 break;
3032 sepsize = sep - buf + 1;
3033 }
3034
3035 /* git-ls-files just delivers a NUL separated
3036 * list of file names similar to the second half
3037 * of the git-diff-* output. */
3038 string_ncopy(file->name, buf, sepsize);
3039 bufsize -= sepsize;
3040 memmove(buf, sep + 1, bufsize);
3041 file = NULL;
3042 }
3043 }
3044
3045 if (ferror(pipe)) {
3046 error_out:
3047 pclose(pipe);
3048 return FALSE;
3049 }
3050
3051 if (!view->line[view->lines - 1].data)
3052 add_line_data(view, NULL, LINE_STAT_NONE);
3053
3054 pclose(pipe);
3055 return TRUE;
3056 }
3057
3058 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
3059 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3060 #define STATUS_LIST_OTHER_CMD \
3061 "git ls-files -z --others --exclude-per-directory=.gitignore"
3062
3063 #define STATUS_DIFF_SHOW_CMD \
3064 "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
3065
3066 /* First parse staged info using git-diff-index(1), then parse unstaged
3067 * info using git-diff-files(1), and finally untracked files using
3068 * git-ls-files(1). */
3069 static bool
3070 status_open(struct view *view)
3071 {
3072 struct stat statbuf;
3073 char exclude[SIZEOF_STR];
3074 char cmd[SIZEOF_STR];
3075 unsigned long prev_lineno = view->lineno;
3076 size_t i;
3077
3078 for (i = 0; i < view->lines; i++)
3079 free(view->line[i].data);
3080 free(view->line);
3081 view->lines = view->line_size = view->lineno = 0;
3082 view->line = NULL;
3083
3084 if (!realloc_lines(view, view->line_size + 6))
3085 return FALSE;
3086
3087 if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3088 return FALSE;
3089
3090 string_copy(cmd, STATUS_LIST_OTHER_CMD);
3091
3092 if (stat(exclude, &statbuf) >= 0) {
3093 size_t cmdsize = strlen(cmd);
3094
3095 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3096 sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3097 return FALSE;
3098 }
3099
3100 if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3101 !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3102 !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3103 return FALSE;
3104
3105 /* If all went well restore the previous line number to stay in
3106 * the context. */
3107 if (prev_lineno < view->lines)
3108 view->lineno = prev_lineno;
3109 else
3110 view->lineno = view->lines - 1;
3111
3112 return TRUE;
3113 }
3114
3115 static bool
3116 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3117 {
3118 struct status *status = line->data;
3119
3120 wmove(view->win, lineno, 0);
3121
3122 if (selected) {
3123 wattrset(view->win, get_line_attr(LINE_CURSOR));
3124 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3125
3126 } else if (!status && line->type != LINE_STAT_NONE) {
3127 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3128 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3129
3130 } else {
3131 wattrset(view->win, get_line_attr(line->type));
3132 }
3133
3134 if (!status) {
3135 char *text;
3136
3137 switch (line->type) {
3138 case LINE_STAT_STAGED:
3139 text = "Changes to be committed:";
3140 break;
3141
3142 case LINE_STAT_UNSTAGED:
3143 text = "Changed but not updated:";
3144 break;
3145
3146 case LINE_STAT_UNTRACKED:
3147 text = "Untracked files:";
3148 break;
3149
3150 case LINE_STAT_NONE:
3151 text = " (no files)";
3152 break;
3153
3154 default:
3155 return FALSE;
3156 }
3157
3158 waddstr(view->win, text);
3159 return TRUE;
3160 }
3161
3162 waddch(view->win, status->status);
3163 if (!selected)
3164 wattrset(view->win, A_NORMAL);
3165 wmove(view->win, lineno, 4);
3166 waddstr(view->win, status->name);
3167
3168 return TRUE;
3169 }
3170
3171 static enum request
3172 status_enter(struct view *view, struct line *line)
3173 {
3174 struct status *status = line->data;
3175 char path[SIZEOF_STR] = "";
3176 char *info;
3177 size_t cmdsize = 0;
3178
3179 if (line->type == LINE_STAT_NONE ||
3180 (!status && line[1].type == LINE_STAT_NONE)) {
3181 report("No file to diff");
3182 return REQ_NONE;
3183 }
3184
3185 if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3186 return REQ_QUIT;
3187
3188 if (opt_cdup[0] &&
3189 line->type != LINE_STAT_UNTRACKED &&
3190 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3191 return REQ_QUIT;
3192
3193 switch (line->type) {
3194 case LINE_STAT_STAGED:
3195 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3196 "--cached", path))
3197 return REQ_QUIT;
3198 if (status)
3199 info = "Staged changes to %s";
3200 else
3201 info = "Staged changes";
3202 break;
3203
3204 case LINE_STAT_UNSTAGED:
3205 if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
3206 "", path))
3207 return REQ_QUIT;
3208 if (status)
3209 info = "Unstaged changes to %s";
3210 else
3211 info = "Unstaged changes";
3212 break;
3213
3214 case LINE_STAT_UNTRACKED:
3215 if (opt_pipe)
3216 return REQ_QUIT;
3217
3218
3219 if (!status) {
3220 report("No file to show");
3221 return REQ_NONE;
3222 }
3223
3224 opt_pipe = fopen(status->name, "r");
3225 info = "Untracked file %s";
3226 break;
3227
3228 default:
3229 die("w00t");
3230 }
3231
3232 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3233 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3234 if (status) {
3235 stage_status = *status;
3236 } else {
3237 memset(&stage_status, 0, sizeof(stage_status));
3238 }
3239
3240 stage_line_type = line->type;
3241 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3242 }
3243
3244 return REQ_NONE;
3245 }
3246
3247
3248 static bool
3249 status_update_file(struct view *view, struct status *status, enum line_type type)
3250 {
3251 char cmd[SIZEOF_STR];
3252 char buf[SIZEOF_STR];
3253 size_t cmdsize = 0;
3254 size_t bufsize = 0;
3255 size_t written = 0;
3256 FILE *pipe;
3257
3258 if (opt_cdup[0] &&
3259 type != LINE_STAT_UNTRACKED &&
3260 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3261 return FALSE;
3262
3263 switch (type) {
3264 case LINE_STAT_STAGED:
3265 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3266 status->old.mode,
3267 status->old.rev,
3268 status->name, 0))
3269 return FALSE;
3270
3271 string_add(cmd, cmdsize, "git update-index -z --index-info");
3272 break;
3273
3274 case LINE_STAT_UNSTAGED:
3275 case LINE_STAT_UNTRACKED:
3276 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3277 return FALSE;
3278
3279 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3280 break;
3281
3282 default:
3283 die("w00t");
3284 }
3285
3286 pipe = popen(cmd, "w");
3287 if (!pipe)
3288 return FALSE;
3289
3290 while (!ferror(pipe) && written < bufsize) {
3291 written += fwrite(buf + written, 1, bufsize - written, pipe);
3292 }
3293
3294 pclose(pipe);
3295
3296 if (written != bufsize)
3297 return FALSE;
3298
3299 return TRUE;
3300 }
3301
3302 static void
3303 status_update(struct view *view)
3304 {
3305 struct line *line = &view->line[view->lineno];
3306
3307 assert(view->lines);
3308
3309 if (!line->data) {
3310 while (++line < view->line + view->lines && line->data) {
3311 if (!status_update_file(view, line->data, line->type))
3312 report("Failed to update file status");
3313 }
3314
3315 if (!line[-1].data) {
3316 report("Nothing to update");
3317 return;
3318 }
3319
3320 } else if (!status_update_file(view, line->data, line->type)) {
3321 report("Failed to update file status");
3322 }
3323
3324 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3325 }
3326
3327 static enum request
3328 status_request(struct view *view, enum request request, struct line *line)
3329 {
3330 struct status *status = line->data;
3331
3332 switch (request) {
3333 case REQ_STATUS_UPDATE:
3334 status_update(view);
3335 break;
3336
3337 case REQ_EDIT:
3338 if (!status)
3339 return request;
3340
3341 open_editor(status->status != '?', status->name);
3342 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3343 break;
3344
3345 case REQ_ENTER:
3346 status_enter(view, line);
3347 break;
3348
3349 case REQ_REFRESH:
3350 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3351 break;
3352
3353 default:
3354 return request;
3355 }
3356
3357 return REQ_NONE;
3358 }
3359
3360 static void
3361 status_select(struct view *view, struct line *line)
3362 {
3363 struct status *status = line->data;
3364 char file[SIZEOF_STR] = "all files";
3365 char *text;
3366
3367 if (status && !string_format(file, "'%s'", status->name))
3368 return;
3369
3370 if (!status && line[1].type == LINE_STAT_NONE)
3371 line++;
3372
3373 switch (line->type) {
3374 case LINE_STAT_STAGED:
3375 text = "Press %s to unstage %s for commit";
3376 break;
3377
3378 case LINE_STAT_UNSTAGED:
3379 text = "Press %s to stage %s for commit";
3380 break;
3381
3382 case LINE_STAT_UNTRACKED:
3383 text = "Press %s to stage %s for addition";
3384 break;
3385
3386 case LINE_STAT_NONE:
3387 text = "Nothing to update";
3388 break;
3389
3390 default:
3391 die("w00t");
3392 }
3393
3394 string_format(view->ref, text, get_key(REQ_STATUS_UPDATE), file);
3395 }
3396
3397 static bool
3398 status_grep(struct view *view, struct line *line)
3399 {
3400 struct status *status = line->data;
3401 enum { S_STATUS, S_NAME, S_END } state;
3402 char buf[2] = "?";
3403 regmatch_t pmatch;
3404
3405 if (!status)
3406 return FALSE;
3407
3408 for (state = S_STATUS; state < S_END; state++) {
3409 char *text;
3410
3411 switch (state) {
3412 case S_NAME: text = status->name; break;
3413 case S_STATUS:
3414 buf[0] = status->status;
3415 text = buf;
3416 break;
3417
3418 default:
3419 return FALSE;
3420 }
3421
3422 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3423 return TRUE;
3424 }
3425
3426 return FALSE;
3427 }
3428
3429 static struct view_ops status_ops = {
3430 "file",
3431 status_open,
3432 NULL,
3433 status_draw,
3434 status_request,
3435 status_grep,
3436 status_select,
3437 };
3438
3439
3440 static bool
3441 stage_diff_line(FILE *pipe, struct line *line)
3442 {
3443 char *buf = line->data;
3444 size_t bufsize = strlen(buf);
3445 size_t written = 0;
3446
3447 while (!ferror(pipe) && written < bufsize) {
3448 written += fwrite(buf + written, 1, bufsize - written, pipe);
3449 }
3450
3451 fputc('\n', pipe);
3452
3453 return written == bufsize;
3454 }
3455
3456 static struct line *
3457 stage_diff_hdr(struct view *view, struct line *line)
3458 {
3459 int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3460 struct line *diff_hdr;
3461
3462 if (line->type == LINE_DIFF_CHUNK)
3463 diff_hdr = line - 1;
3464 else
3465 diff_hdr = view->line + 1;
3466
3467 while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3468 if (diff_hdr->type == LINE_DIFF_HEADER)
3469 return diff_hdr;
3470
3471 diff_hdr += diff_hdr_dir;
3472 }
3473
3474 return NULL;
3475 }
3476
3477 static bool
3478 stage_update_chunk(struct view *view, struct line *line)
3479 {
3480 char cmd[SIZEOF_STR];
3481 size_t cmdsize = 0;
3482 struct line *diff_hdr, *diff_chunk, *diff_end;
3483 FILE *pipe;
3484
3485 diff_hdr = stage_diff_hdr(view, line);
3486 if (!diff_hdr)
3487 return FALSE;
3488
3489 if (opt_cdup[0] &&
3490 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3491 return FALSE;
3492
3493 if (!string_format_from(cmd, &cmdsize,
3494 "git apply --cached %s - && "
3495 "git update-index -q --unmerged --refresh 2>/dev/null",
3496 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3497 return FALSE;
3498
3499 pipe = popen(cmd, "w");
3500 if (!pipe)
3501 return FALSE;
3502
3503 diff_end = view->line + view->lines;
3504 if (line->type != LINE_DIFF_CHUNK) {
3505 diff_chunk = diff_hdr;
3506
3507 } else {
3508 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3509 if (diff_chunk->type == LINE_DIFF_CHUNK ||
3510 diff_chunk->type == LINE_DIFF_HEADER)
3511 diff_end = diff_chunk;
3512
3513 diff_chunk = line;
3514
3515 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3516 switch (diff_hdr->type) {
3517 case LINE_DIFF_HEADER:
3518 case LINE_DIFF_INDEX:
3519 case LINE_DIFF_ADD:
3520 case LINE_DIFF_DEL:
3521 break;
3522
3523 default:
3524 diff_hdr++;
3525 continue;
3526 }
3527
3528 if (!stage_diff_line(pipe, diff_hdr++)) {
3529 pclose(pipe);
3530 return FALSE;
3531 }
3532 }
3533 }
3534
3535 while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3536 diff_chunk++;
3537
3538 pclose(pipe);
3539
3540 if (diff_chunk != diff_end)
3541 return FALSE;
3542
3543 return TRUE;
3544 }
3545
3546 static void
3547 stage_update(struct view *view, struct line *line)
3548 {
3549 if (stage_line_type != LINE_STAT_UNTRACKED &&
3550 (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3551 if (!stage_update_chunk(view, line)) {
3552 report("Failed to apply chunk");
3553 return;
3554 }
3555
3556 } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3557 report("Failed to update file");
3558 return;
3559 }
3560
3561 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3562
3563 view = VIEW(REQ_VIEW_STATUS);
3564 if (view_is_displayed(view))
3565 status_enter(view, &view->line[view->lineno]);
3566 }
3567
3568 static enum request
3569 stage_request(struct view *view, enum request request, struct line *line)
3570 {
3571 switch (request) {
3572 case REQ_STATUS_UPDATE:
3573 stage_update(view, line);
3574 break;
3575
3576 case REQ_EDIT:
3577 if (!stage_status.name[0])
3578 return request;
3579
3580 open_editor(stage_status.status != '?', stage_status.name);
3581 break;
3582
3583 case REQ_ENTER:
3584 pager_request(view, request, line);
3585 break;
3586
3587 default:
3588 return request;
3589 }
3590
3591 return REQ_NONE;
3592 }
3593
3594 static struct view_ops stage_ops = {
3595 "line",
3596 NULL,
3597 pager_read,
3598 pager_draw,
3599 stage_request,
3600 pager_grep,
3601 pager_select,
3602 };
3603
3604
3605 /*
3606 * Revision graph
3607 */
3608
3609 struct commit {
3610 char id[SIZEOF_REV]; /* SHA1 ID. */
3611 char title[128]; /* First line of the commit message. */
3612 char author[75]; /* Author of the commit. */
3613 struct tm time; /* Date from the author ident. */
3614 struct ref **refs; /* Repository references. */
3615 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
3616 size_t graph_size; /* The width of the graph array. */
3617 };
3618
3619 /* Size of rev graph with no "padding" columns */
3620 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3621
3622 struct rev_graph {
3623 struct rev_graph *prev, *next, *parents;
3624 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3625 size_t size;
3626 struct commit *commit;
3627 size_t pos;
3628 };
3629
3630 /* Parents of the commit being visualized. */
3631 static struct rev_graph graph_parents[4];
3632
3633 /* The current stack of revisions on the graph. */
3634 static struct rev_graph graph_stacks[4] = {
3635 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3636 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3637 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3638 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3639 };
3640
3641 static inline bool
3642 graph_parent_is_merge(struct rev_graph *graph)
3643 {
3644 return graph->parents->size > 1;
3645 }
3646
3647 static inline void
3648 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3649 {
3650 struct commit *commit = graph->commit;
3651
3652 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3653 commit->graph[commit->graph_size++] = symbol;
3654 }
3655
3656 static void
3657 done_rev_graph(struct rev_graph *graph)
3658 {
3659 if (graph_parent_is_merge(graph) &&
3660 graph->pos < graph->size - 1 &&
3661 graph->next->size == graph->size + graph->parents->size - 1) {
3662 size_t i = graph->pos + graph->parents->size - 1;
3663
3664 graph->commit->graph_size = i * 2;
3665 while (i < graph->next->size - 1) {
3666 append_to_rev_graph(graph, ' ');
3667 append_to_rev_graph(graph, '\\');
3668 i++;
3669 }
3670 }
3671
3672 graph->size = graph->pos = 0;
3673 graph->commit = NULL;
3674 memset(graph->parents, 0, sizeof(*graph->parents));
3675 }
3676
3677 static void
3678 push_rev_graph(struct rev_graph *graph, char *parent)
3679 {
3680 int i;
3681
3682 /* "Collapse" duplicate parents lines.
3683 *
3684 * FIXME: This needs to also update update the drawn graph but
3685 * for now it just serves as a method for pruning graph lines. */
3686 for (i = 0; i < graph->size; i++)
3687 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3688 return;
3689
3690 if (graph->size < SIZEOF_REVITEMS) {
3691 string_copy_rev(graph->rev[graph->size++], parent);
3692 }
3693 }
3694
3695 static chtype
3696 get_rev_graph_symbol(struct rev_graph *graph)
3697 {
3698 chtype symbol;
3699
3700 if (graph->parents->size == 0)
3701 symbol = REVGRAPH_INIT;
3702 else if (graph_parent_is_merge(graph))
3703 symbol = REVGRAPH_MERGE;
3704 else if (graph->pos >= graph->size)
3705 symbol = REVGRAPH_BRANCH;
3706 else
3707 symbol = REVGRAPH_COMMIT;
3708
3709 return symbol;
3710 }
3711
3712 static void
3713 draw_rev_graph(struct rev_graph *graph)
3714 {
3715 struct rev_filler {
3716 chtype separator, line;
3717 };
3718 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3719 static struct rev_filler fillers[] = {
3720 { ' ', REVGRAPH_LINE },
3721 { '`', '.' },
3722 { '\'', ' ' },
3723 { '/', ' ' },
3724 };
3725 chtype symbol = get_rev_graph_symbol(graph);
3726 struct rev_filler *filler;
3727 size_t i;
3728
3729 filler = &fillers[DEFAULT];
3730
3731 for (i = 0; i < graph->pos; i++) {
3732 append_to_rev_graph(graph, filler->line);
3733 if (graph_parent_is_merge(graph->prev) &&
3734 graph->prev->pos == i)
3735 filler = &fillers[RSHARP];
3736
3737 append_to_rev_graph(graph, filler->separator);
3738 }
3739
3740 /* Place the symbol for this revision. */
3741 append_to_rev_graph(graph, symbol);
3742
3743 if (graph->prev->size > graph->size)
3744 filler = &fillers[RDIAG];
3745 else
3746 filler = &fillers[DEFAULT];
3747
3748 i++;
3749
3750 for (; i < graph->size; i++) {
3751 append_to_rev_graph(graph, filler->separator);
3752 append_to_rev_graph(graph, filler->line);
3753 if (graph_parent_is_merge(graph->prev) &&
3754 i < graph->prev->pos + graph->parents->size)
3755 filler = &fillers[RSHARP];
3756 if (graph->prev->size > graph->size)
3757 filler = &fillers[LDIAG];
3758 }
3759
3760 if (graph->prev->size > graph->size) {
3761 append_to_rev_graph(graph, filler->separator);
3762 if (filler->line != ' ')
3763 append_to_rev_graph(graph, filler->line);
3764 }
3765 }
3766
3767 /* Prepare the next rev graph */
3768 static void
3769 prepare_rev_graph(struct rev_graph *graph)
3770 {
3771 size_t i;
3772
3773 /* First, traverse all lines of revisions up to the active one. */
3774 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
3775 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
3776 break;
3777
3778 push_rev_graph(graph->next, graph->rev[graph->pos]);
3779 }
3780
3781 /* Interleave the new revision parent(s). */
3782 for (i = 0; i < graph->parents->size; i++)
3783 push_rev_graph(graph->next, graph->parents->rev[i]);
3784
3785 /* Lastly, put any remaining revisions. */
3786 for (i = graph->pos + 1; i < graph->size; i++)
3787 push_rev_graph(graph->next, graph->rev[i]);
3788 }
3789
3790 static void
3791 update_rev_graph(struct rev_graph *graph)
3792 {
3793 /* If this is the finalizing update ... */
3794 if (graph->commit)
3795 prepare_rev_graph(graph);
3796
3797 /* Graph visualization needs a one rev look-ahead,
3798 * so the first update doesn't visualize anything. */
3799 if (!graph->prev->commit)
3800 return;
3801
3802 draw_rev_graph(graph->prev);
3803 done_rev_graph(graph->prev->prev);
3804 }
3805
3806
3807 /*
3808 * Main view backend
3809 */
3810
3811 static bool
3812 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3813 {
3814 char buf[DATE_COLS + 1];
3815 struct commit *commit = line->data;
3816 enum line_type type;
3817 int col = 0;
3818 size_t timelen;
3819 size_t authorlen;
3820 int trimmed = 1;
3821
3822 if (!*commit->author)
3823 return FALSE;
3824
3825 wmove(view->win, lineno, col);
3826
3827 if (selected) {
3828 type = LINE_CURSOR;
3829 wattrset(view->win, get_line_attr(type));
3830 wchgat(view->win, -1, 0, type, NULL);
3831
3832 } else {
3833 type = LINE_MAIN_COMMIT;
3834 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3835 }
3836
3837 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
3838 waddnstr(view->win, buf, timelen);
3839 waddstr(view->win, " ");
3840
3841 col += DATE_COLS;
3842 wmove(view->win, lineno, col);
3843 if (type != LINE_CURSOR)
3844 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3845
3846 if (opt_utf8) {
3847 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
3848 } else {
3849 authorlen = strlen(commit->author);
3850 if (authorlen > AUTHOR_COLS - 2) {
3851 authorlen = AUTHOR_COLS - 2;
3852 trimmed = 1;
3853 }
3854 }
3855
3856 if (trimmed) {
3857 waddnstr(view->win, commit->author, authorlen);
3858 if (type != LINE_CURSOR)
3859 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
3860 waddch(view->win, '~');
3861 } else {
3862 waddstr(view->win, commit->author);
3863 }
3864
3865 col += AUTHOR_COLS;
3866 if (type != LINE_CURSOR)
3867 wattrset(view->win, A_NORMAL);
3868
3869 if (opt_rev_graph && commit->graph_size) {
3870 size_t i;
3871
3872 wmove(view->win, lineno, col);
3873 /* Using waddch() instead of waddnstr() ensures that
3874 * they'll be rendered correctly for the cursor line. */
3875 for (i = 0; i < commit->graph_size; i++)
3876 waddch(view->win, commit->graph[i]);
3877
3878 waddch(view->win, ' ');
3879 col += commit->graph_size + 1;
3880 }
3881
3882 wmove(view->win, lineno, col);
3883
3884 if (commit->refs) {
3885 size_t i = 0;
3886
3887 do {
3888 if (type == LINE_CURSOR)
3889 ;
3890 else if (commit->refs[i]->tag)
3891 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
3892 else if (commit->refs[i]->remote)
3893 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
3894 else
3895 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
3896 waddstr(view->win, "[");
3897 waddstr(view->win, commit->refs[i]->name);
3898 waddstr(view->win, "]");
3899 if (type != LINE_CURSOR)
3900 wattrset(view->win, A_NORMAL);
3901 waddstr(view->win, " ");
3902 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
3903 } while (commit->refs[i++]->next);
3904 }
3905
3906 if (type != LINE_CURSOR)
3907 wattrset(view->win, get_line_attr(type));
3908
3909 {
3910 int titlelen = strlen(commit->title);
3911
3912 if (col + titlelen > view->width)
3913 titlelen = view->width - col;
3914
3915 waddnstr(view->win, commit->title, titlelen);
3916 }
3917
3918 return TRUE;
3919 }
3920
3921 /* Reads git log --pretty=raw output and parses it into the commit struct. */
3922 static bool
3923 main_read(struct view *view, char *line)
3924 {
3925 static struct rev_graph *graph = graph_stacks;
3926 enum line_type type;
3927 struct commit *commit;
3928
3929 if (!line) {
3930 update_rev_graph(graph);
3931 return TRUE;
3932 }
3933
3934 type = get_line_type(line);
3935 if (type == LINE_COMMIT) {
3936 commit = calloc(1, sizeof(struct commit));
3937 if (!commit)
3938 return FALSE;
3939
3940 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
3941 commit->refs = get_refs(commit->id);
3942 graph->commit = commit;
3943 add_line_data(view, commit, LINE_MAIN_COMMIT);
3944 return TRUE;
3945 }
3946
3947 if (!view->lines)
3948 return TRUE;
3949 commit = view->line[view->lines - 1].data;
3950
3951 switch (type) {
3952 case LINE_PARENT:
3953 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
3954 break;
3955
3956 case LINE_AUTHOR:
3957 {
3958 /* Parse author lines where the name may be empty:
3959 * author <email@address.tld> 1138474660 +0100
3960 */
3961 char *ident = line + STRING_SIZE("author ");
3962 char *nameend = strchr(ident, '<');
3963 char *emailend = strchr(ident, '>');
3964
3965 if (!nameend || !emailend)
3966 break;
3967
3968 update_rev_graph(graph);
3969 graph = graph->next;
3970
3971 *nameend = *emailend = 0;
3972 ident = chomp_string(ident);
3973 if (!*ident) {
3974 ident = chomp_string(nameend + 1);
3975 if (!*ident)
3976 ident = "Unknown";
3977 }
3978
3979 string_ncopy(commit->author, ident, strlen(ident));
3980
3981 /* Parse epoch and timezone */
3982 if (emailend[1] == ' ') {
3983 char *secs = emailend + 2;
3984 char *zone = strchr(secs, ' ');
3985 time_t time = (time_t) atol(secs);
3986
3987 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3988 long tz;
3989
3990 zone++;
3991 tz = ('0' - zone[1]) * 60 * 60 * 10;
3992 tz += ('0' - zone[2]) * 60 * 60;
3993 tz += ('0' - zone[3]) * 60;
3994 tz += ('0' - zone[4]) * 60;
3995
3996 if (zone[0] == '-')
3997 tz = -tz;
3998
3999 time -= tz;
4000 }
4001
4002 gmtime_r(&time, &commit->time);
4003 }
4004 break;
4005 }
4006 default:
4007 /* Fill in the commit title if it has not already been set. */
4008 if (commit->title[0])
4009 break;
4010
4011 /* Require titles to start with a non-space character at the
4012 * offset used by git log. */
4013 if (strncmp(line, " ", 4))
4014 break;
4015 line += 4;
4016 /* Well, if the title starts with a whitespace character,
4017 * try to be forgiving. Otherwise we end up with no title. */
4018 while (isspace(*line))
4019 line++;
4020 if (*line == '\0')
4021 break;
4022 /* FIXME: More graceful handling of titles; append "..." to
4023 * shortened titles, etc. */
4024
4025 string_ncopy(commit->title, line, strlen(line));
4026 }
4027
4028 return TRUE;
4029 }
4030
4031 static void
4032 cherry_pick_commit(struct commit *commit)
4033 {
4034 char cmd[SIZEOF_STR];
4035 char *cherry_pick = getenv("TIG_CHERRY_PICK");
4036
4037 if (!cherry_pick)
4038 cherry_pick = "git cherry-pick";
4039
4040 if (string_format(cmd, "%s %s", cherry_pick, commit->id)) {
4041 def_prog_mode(); /* save current tty modes */
4042 endwin(); /* restore original tty modes */
4043 system(cmd);
4044 fprintf(stderr, "Press Enter to continue");
4045 getc(stdin);
4046 reset_prog_mode();
4047 redraw_display();
4048 }
4049 }
4050
4051 static enum request
4052 main_request(struct view *view, enum request request, struct line *line)
4053 {
4054 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4055
4056 if (request == REQ_ENTER)
4057 open_view(view, REQ_VIEW_DIFF, flags);
4058 else if (request == REQ_CHERRY_PICK)
4059 cherry_pick_commit(line->data);
4060 else
4061 return request;
4062
4063 return REQ_NONE;
4064 }
4065
4066 static bool
4067 main_grep(struct view *view, struct line *line)
4068 {
4069 struct commit *commit = line->data;
4070 enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4071 char buf[DATE_COLS + 1];
4072 regmatch_t pmatch;
4073
4074 for (state = S_TITLE; state < S_END; state++) {
4075 char *text;
4076
4077 switch (state) {
4078 case S_TITLE: text = commit->title; break;
4079 case S_AUTHOR: text = commit->author; break;
4080 case S_DATE:
4081 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4082 continue;
4083 text = buf;
4084 break;
4085
4086 default:
4087 return FALSE;
4088 }
4089
4090 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4091 return TRUE;
4092 }
4093
4094 return FALSE;
4095 }
4096
4097 static void
4098 main_select(struct view *view, struct line *line)
4099 {
4100 struct commit *commit = line->data;
4101
4102 string_copy_rev(view->ref, commit->id);
4103 string_copy_rev(ref_commit, view->ref);
4104 }
4105
4106 static struct view_ops main_ops = {
4107 "commit",
4108 NULL,
4109 main_read,
4110 main_draw,
4111 main_request,
4112 main_grep,
4113 main_select,
4114 };
4115
4116
4117 /*
4118 * Unicode / UTF-8 handling
4119 *
4120 * NOTE: Much of the following code for dealing with unicode is derived from
4121 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4122 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4123 */
4124
4125 /* I've (over)annotated a lot of code snippets because I am not entirely
4126 * confident that the approach taken by this small UTF-8 interface is correct.
4127 * --jonas */
4128
4129 static inline int
4130 unicode_width(unsigned long c)
4131 {
4132 if (c >= 0x1100 &&
4133 (c <= 0x115f /* Hangul Jamo */
4134 || c == 0x2329
4135 || c == 0x232a
4136 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
4137 /* CJK ... Yi */
4138 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
4139 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
4140 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
4141 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
4142 || (c >= 0xffe0 && c <= 0xffe6)
4143 || (c >= 0x20000 && c <= 0x2fffd)
4144 || (c >= 0x30000 && c <= 0x3fffd)))
4145 return 2;
4146
4147 return 1;
4148 }
4149
4150 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4151 * Illegal bytes are set one. */
4152 static const unsigned char utf8_bytes[256] = {
4153 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,
4154 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,
4155 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,
4156 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,
4157 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,
4158 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,
4159 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,
4160 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,
4161 };
4162
4163 /* Decode UTF-8 multi-byte representation into a unicode character. */
4164 static inline unsigned long
4165 utf8_to_unicode(const char *string, size_t length)
4166 {
4167 unsigned long unicode;
4168
4169 switch (length) {
4170 case 1:
4171 unicode = string[0];
4172 break;
4173 case 2:
4174 unicode = (string[0] & 0x1f) << 6;
4175 unicode += (string[1] & 0x3f);
4176 break;
4177 case 3:
4178 unicode = (string[0] & 0x0f) << 12;
4179 unicode += ((string[1] & 0x3f) << 6);
4180 unicode += (string[2] & 0x3f);
4181 break;
4182 case 4:
4183 unicode = (string[0] & 0x0f) << 18;
4184 unicode += ((string[1] & 0x3f) << 12);
4185 unicode += ((string[2] & 0x3f) << 6);
4186 unicode += (string[3] & 0x3f);
4187 break;
4188 case 5:
4189 unicode = (string[0] & 0x0f) << 24;
4190 unicode += ((string[1] & 0x3f) << 18);
4191 unicode += ((string[2] & 0x3f) << 12);
4192 unicode += ((string[3] & 0x3f) << 6);
4193 unicode += (string[4] & 0x3f);
4194 break;
4195 case 6:
4196 unicode = (string[0] & 0x01) << 30;
4197 unicode += ((string[1] & 0x3f) << 24);
4198 unicode += ((string[2] & 0x3f) << 18);
4199 unicode += ((string[3] & 0x3f) << 12);
4200 unicode += ((string[4] & 0x3f) << 6);
4201 unicode += (string[5] & 0x3f);
4202 break;
4203 default:
4204 die("Invalid unicode length");
4205 }
4206
4207 /* Invalid characters could return the special 0xfffd value but NUL
4208 * should be just as good. */
4209 return unicode > 0xffff ? 0 : unicode;
4210 }
4211
4212 /* Calculates how much of string can be shown within the given maximum width
4213 * and sets trimmed parameter to non-zero value if all of string could not be
4214 * shown.
4215 *
4216 * Additionally, adds to coloffset how many many columns to move to align with
4217 * the expected position. Takes into account how multi-byte and double-width
4218 * characters will effect the cursor position.
4219 *
4220 * Returns the number of bytes to output from string to satisfy max_width. */
4221 static size_t
4222 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4223 {
4224 const char *start = string;
4225 const char *end = strchr(string, '\0');
4226 size_t mbwidth = 0;
4227 size_t width = 0;
4228
4229 *trimmed = 0;
4230
4231 while (string < end) {
4232 int c = *(unsigned char *) string;
4233 unsigned char bytes = utf8_bytes[c];
4234 size_t ucwidth;
4235 unsigned long unicode;
4236
4237 if (string + bytes > end)
4238 break;
4239
4240 /* Change representation to figure out whether
4241 * it is a single- or double-width character. */
4242
4243 unicode = utf8_to_unicode(string, bytes);
4244 /* FIXME: Graceful handling of invalid unicode character. */
4245 if (!unicode)
4246 break;
4247
4248 ucwidth = unicode_width(unicode);
4249 width += ucwidth;
4250 if (width > max_width) {
4251 *trimmed = 1;
4252 break;
4253 }
4254
4255 /* The column offset collects the differences between the
4256 * number of bytes encoding a character and the number of
4257 * columns will be used for rendering said character.
4258 *
4259 * So if some character A is encoded in 2 bytes, but will be
4260 * represented on the screen using only 1 byte this will and up
4261 * adding 1 to the multi-byte column offset.
4262 *
4263 * Assumes that no double-width character can be encoding in
4264 * less than two bytes. */
4265 if (bytes > ucwidth)
4266 mbwidth += bytes - ucwidth;
4267
4268 string += bytes;
4269 }
4270
4271 *coloffset += mbwidth;
4272
4273 return string - start;
4274 }
4275
4276
4277 /*
4278 * Status management
4279 */
4280
4281 /* Whether or not the curses interface has been initialized. */
4282 static bool cursed = FALSE;
4283
4284 /* The status window is used for polling keystrokes. */
4285 static WINDOW *status_win;
4286
4287 static bool status_empty = TRUE;
4288
4289 /* Update status and title window. */
4290 static void
4291 report(const char *msg, ...)
4292 {
4293 struct view *view = display[current_view];
4294
4295 if (input_mode)
4296 return;
4297
4298 if (!view) {
4299 char buf[SIZEOF_STR];
4300 va_list args;
4301
4302 va_start(args, msg);
4303 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4304 buf[sizeof(buf) - 1] = 0;
4305 buf[sizeof(buf) - 2] = '.';
4306 buf[sizeof(buf) - 3] = '.';
4307 buf[sizeof(buf) - 4] = '.';
4308 }
4309 va_end(args);
4310 die("%s", buf);
4311 }
4312
4313 if (!status_empty || *msg) {
4314 va_list args;
4315
4316 va_start(args, msg);
4317
4318 wmove(status_win, 0, 0);
4319 if (*msg) {
4320 vwprintw(status_win, msg, args);
4321 status_empty = FALSE;
4322 } else {
4323 status_empty = TRUE;
4324 }
4325 wclrtoeol(status_win);
4326 wrefresh(status_win);
4327
4328 va_end(args);
4329 }
4330
4331 update_view_title(view);
4332 update_display_cursor(view);
4333 }
4334
4335 /* Controls when nodelay should be in effect when polling user input. */
4336 static void
4337 set_nonblocking_input(bool loading)
4338 {
4339 static unsigned int loading_views;
4340
4341 if ((loading == FALSE && loading_views-- == 1) ||
4342 (loading == TRUE && loading_views++ == 0))
4343 nodelay(status_win, loading);
4344 }
4345
4346 static void
4347 init_display(void)
4348 {
4349 int x, y;
4350
4351 /* Initialize the curses library */
4352 if (isatty(STDIN_FILENO)) {
4353 cursed = !!initscr();
4354 } else {
4355 /* Leave stdin and stdout alone when acting as a pager. */
4356 FILE *io = fopen("/dev/tty", "r+");
4357
4358 if (!io)
4359 die("Failed to open /dev/tty");
4360 cursed = !!newterm(NULL, io, io);
4361 }
4362
4363 if (!cursed)
4364 die("Failed to initialize curses");
4365
4366 nonl(); /* Tell curses not to do NL->CR/NL on output */
4367 cbreak(); /* Take input chars one at a time, no wait for \n */
4368 noecho(); /* Don't echo input */
4369 leaveok(stdscr, TRUE);
4370
4371 if (has_colors())
4372 init_colors();
4373
4374 getmaxyx(stdscr, y, x);
4375 status_win = newwin(1, 0, y - 1, 0);
4376 if (!status_win)
4377 die("Failed to create status window");
4378
4379 /* Enable keyboard mapping */
4380 keypad(status_win, TRUE);
4381 wbkgdset(status_win, get_line_attr(LINE_STATUS));
4382 }
4383
4384 static char *
4385 read_prompt(const char *prompt)
4386 {
4387 enum { READING, STOP, CANCEL } status = READING;
4388 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4389 int pos = 0;
4390
4391 while (status == READING) {
4392 struct view *view;
4393 int i, key;
4394
4395 input_mode = TRUE;
4396
4397 foreach_view (view, i)
4398 update_view(view);
4399
4400 input_mode = FALSE;
4401
4402 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4403 wclrtoeol(status_win);
4404
4405 /* Refresh, accept single keystroke of input */
4406 key = wgetch(status_win);
4407 switch (key) {
4408 case KEY_RETURN:
4409 case KEY_ENTER:
4410 case '\n':
4411 status = pos ? STOP : CANCEL;
4412 break;
4413
4414 case KEY_BACKSPACE:
4415 if (pos > 0)
4416 pos--;
4417 else
4418 status = CANCEL;
4419 break;
4420
4421 case KEY_ESC:
4422 status = CANCEL;
4423 break;
4424
4425 case ERR:
4426 break;
4427
4428 default:
4429 if (pos >= sizeof(buf)) {
4430 report("Input string too long");
4431 return NULL;
4432 }
4433
4434 if (isprint(key))
4435 buf[pos++] = (char) key;
4436 }
4437 }
4438
4439 /* Clear the status window */
4440 status_empty = FALSE;
4441 report("");
4442
4443 if (status == CANCEL)
4444 return NULL;
4445
4446 buf[pos++] = 0;
4447
4448 return buf;
4449 }
4450
4451 /*
4452 * Repository references
4453 */
4454
4455 static struct ref *refs;
4456 static size_t refs_size;
4457
4458 /* Id <-> ref store */
4459 static struct ref ***id_refs;
4460 static size_t id_refs_size;
4461
4462 static struct ref **
4463 get_refs(char *id)
4464 {
4465 struct ref ***tmp_id_refs;
4466 struct ref **ref_list = NULL;
4467 size_t ref_list_size = 0;
4468 size_t i;
4469
4470 for (i = 0; i < id_refs_size; i++)
4471 if (!strcmp(id, id_refs[i][0]->id))
4472 return id_refs[i];
4473
4474 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4475 if (!tmp_id_refs)
4476 return NULL;
4477
4478 id_refs = tmp_id_refs;
4479
4480 for (i = 0; i < refs_size; i++) {
4481 struct ref **tmp;
4482
4483 if (strcmp(id, refs[i].id))
4484 continue;
4485
4486 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4487 if (!tmp) {
4488 if (ref_list)
4489 free(ref_list);
4490 return NULL;
4491 }
4492
4493 ref_list = tmp;
4494 if (ref_list_size > 0)
4495 ref_list[ref_list_size - 1]->next = 1;
4496 ref_list[ref_list_size] = &refs[i];
4497
4498 /* XXX: The properties of the commit chains ensures that we can
4499 * safely modify the shared ref. The repo references will
4500 * always be similar for the same id. */
4501 ref_list[ref_list_size]->next = 0;
4502 ref_list_size++;
4503 }
4504
4505 if (ref_list)
4506 id_refs[id_refs_size++] = ref_list;
4507
4508 return ref_list;
4509 }
4510
4511 static int
4512 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4513 {
4514 struct ref *ref;
4515 bool tag = FALSE;
4516 bool remote = FALSE;
4517
4518 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4519 /* Commits referenced by tags has "^{}" appended. */
4520 if (name[namelen - 1] != '}')
4521 return OK;
4522
4523 while (namelen > 0 && name[namelen] != '^')
4524 namelen--;
4525
4526 tag = TRUE;
4527 namelen -= STRING_SIZE("refs/tags/");
4528 name += STRING_SIZE("refs/tags/");
4529
4530 } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4531 remote = TRUE;
4532 namelen -= STRING_SIZE("refs/remotes/");
4533 name += STRING_SIZE("refs/remotes/");
4534
4535 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4536 namelen -= STRING_SIZE("refs/heads/");
4537 name += STRING_SIZE("refs/heads/");
4538
4539 } else if (!strcmp(name, "HEAD")) {
4540 return OK;
4541 }
4542
4543 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4544 if (!refs)
4545 return ERR;
4546
4547 ref = &refs[refs_size++];
4548 ref->name = malloc(namelen + 1);
4549 if (!ref->name)
4550 return ERR;
4551
4552 strncpy(ref->name, name, namelen);
4553 ref->name[namelen] = 0;
4554 ref->tag = tag;
4555 ref->remote = remote;
4556 string_copy_rev(ref->id, id);
4557
4558 return OK;
4559 }
4560
4561 static int
4562 load_refs(void)
4563 {
4564 const char *cmd_env = getenv("TIG_LS_REMOTE");
4565 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4566
4567 return read_properties(popen(cmd, "r"), "\t", read_ref);
4568 }
4569
4570 static int
4571 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4572 {
4573 if (!strcmp(name, "i18n.commitencoding"))
4574 string_ncopy(opt_encoding, value, valuelen);
4575
4576 if (!strcmp(name, "core.editor"))
4577 string_ncopy(opt_editor, value, valuelen);
4578
4579 return OK;
4580 }
4581
4582 static int
4583 load_repo_config(void)
4584 {
4585 return read_properties(popen(GIT_CONFIG " --list", "r"),
4586 "=", read_repo_config_option);
4587 }
4588
4589 static int
4590 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4591 {
4592 if (!opt_git_dir[0]) {
4593 string_ncopy(opt_git_dir, name, namelen);
4594
4595 } else if (opt_is_inside_work_tree == -1) {
4596 /* This can be 3 different values depending on the
4597 * version of git being used. If git-rev-parse does not
4598 * understand --is-inside-work-tree it will simply echo
4599 * the option else either "true" or "false" is printed.
4600 * Default to true for the unknown case. */
4601 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4602
4603 } else {
4604 string_ncopy(opt_cdup, name, namelen);
4605 }
4606
4607 return OK;
4608 }
4609
4610 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4611 * must be the last one! */
4612 static int
4613 load_repo_info(void)
4614 {
4615 return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4616 "=", read_repo_info);
4617 }
4618
4619 static int
4620 read_properties(FILE *pipe, const char *separators,
4621 int (*read_property)(char *, size_t, char *, size_t))
4622 {
4623 char buffer[BUFSIZ];
4624 char *name;
4625 int state = OK;
4626
4627 if (!pipe)
4628 return ERR;
4629
4630 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4631 char *value;
4632 size_t namelen;
4633 size_t valuelen;
4634
4635 name = chomp_string(name);
4636 namelen = strcspn(name, separators);
4637
4638 if (name[namelen]) {
4639 name[namelen] = 0;
4640 value = chomp_string(name + namelen + 1);
4641 valuelen = strlen(value);
4642
4643 } else {
4644 value = "";
4645 valuelen = 0;
4646 }
4647
4648 state = read_property(name, namelen, value, valuelen);
4649 }
4650
4651 if (state != ERR && ferror(pipe))
4652 state = ERR;
4653
4654 pclose(pipe);
4655
4656 return state;
4657 }
4658
4659
4660 /*
4661 * Main
4662 */
4663
4664 static void __NORETURN
4665 quit(int sig)
4666 {
4667 /* XXX: Restore tty modes and let the OS cleanup the rest! */
4668 if (cursed)
4669 endwin();
4670 exit(0);
4671 }
4672
4673 static void __NORETURN
4674 die(const char *err, ...)
4675 {
4676 va_list args;
4677
4678 endwin();
4679
4680 va_start(args, err);
4681 fputs("tig: ", stderr);
4682 vfprintf(stderr, err, args);
4683 fputs("\n", stderr);
4684 va_end(args);
4685
4686 exit(1);
4687 }
4688
4689 int
4690 main(int argc, char *argv[])
4691 {
4692 struct view *view;
4693 enum request request;
4694 size_t i;
4695
4696 signal(SIGINT, quit);
4697
4698 if (setlocale(LC_ALL, "")) {
4699 char *codeset = nl_langinfo(CODESET);
4700
4701 string_ncopy(opt_codeset, codeset, strlen(codeset));
4702 }
4703
4704 if (load_repo_info() == ERR)
4705 die("Failed to load repo info.");
4706
4707 if (load_options() == ERR)
4708 die("Failed to load user config.");
4709
4710 /* Load the repo config file so options can be overwritten from
4711 * the command line. */
4712 if (load_repo_config() == ERR)
4713 die("Failed to load repo config.");
4714
4715 if (!parse_options(argc, argv))
4716 return 0;
4717
4718 /* Require a git repository unless when running in pager mode. */
4719 if (!opt_git_dir[0])
4720 die("Not a git repository");
4721
4722 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4723 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4724 if (opt_iconv == ICONV_NONE)
4725 die("Failed to initialize character set conversion");
4726 }
4727
4728 if (load_refs() == ERR)
4729 die("Failed to load refs.");
4730
4731 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4732 view->cmd_env = getenv(view->cmd_env);
4733
4734 request = opt_request;
4735
4736 init_display();
4737
4738 while (view_driver(display[current_view], request)) {
4739 int key;
4740 int i;
4741
4742 foreach_view (view, i)
4743 update_view(view);
4744
4745 /* Refresh, accept single keystroke of input */
4746 key = wgetch(status_win);
4747
4748 /* wgetch() with nodelay() enabled returns ERR when there's no
4749 * input. */
4750 if (key == ERR) {
4751 request = REQ_NONE;
4752 continue;
4753 }
4754
4755 request = get_keybinding(display[current_view]->keymap, key);
4756
4757 /* Some low-level request handling. This keeps access to
4758 * status_win restricted. */
4759 switch (request) {
4760 case REQ_PROMPT:
4761 {
4762 char *cmd = read_prompt(":");
4763
4764 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4765 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4766 opt_request = REQ_VIEW_DIFF;
4767 } else {
4768 opt_request = REQ_VIEW_PAGER;
4769 }
4770 break;
4771 }
4772
4773 request = REQ_NONE;
4774 break;
4775 }
4776 case REQ_SEARCH:
4777 case REQ_SEARCH_BACK:
4778 {
4779 const char *prompt = request == REQ_SEARCH
4780 ? "/" : "?";
4781 char *search = read_prompt(prompt);
4782
4783 if (search)
4784 string_ncopy(opt_search, search, strlen(search));
4785 else
4786 request = REQ_NONE;
4787 break;
4788 }
4789 case REQ_SCREEN_RESIZE:
4790 {
4791 int height, width;
4792
4793 getmaxyx(stdscr, height, width);
4794
4795 /* Resize the status view and let the view driver take
4796 * care of resizing the displayed views. */
4797 wresize(status_win, 1, width);
4798 mvwin(status_win, height - 1, 0);
4799 wrefresh(status_win);
4800 break;
4801 }
4802 default:
4803 break;
4804 }
4805 }
4806
4807 quit(0);
4808
4809 return 0;
4810 }