Add simple window stack, tracking view relation ship via parent member
[tig] / tig.c
CommitLineData
4a2909a7
JF
1/* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
2 * See license info at the bottom. */
b801d8b2
JF
3/**
4 * TIG(1)
5 * ======
6 *
7 * NAME
8 * ----
9 * tig - text-mode interface for git
10 *
11 * SYNOPSIS
12 * --------
13 * [verse]
03a93dbb
JF
14 * tig [options]
15 * tig [options] [--] [git log options]
b76c2afc
JF
16 * tig [options] log [git log options]
17 * tig [options] diff [git diff options]
8855ada4
JF
18 * tig [options] show [git show options]
19 * tig [options] < [git command output]
b801d8b2
JF
20 *
21 * DESCRIPTION
22 * -----------
6706b2ba
JF
23 * Browse changes in a git repository. Additionally, tig(1) can also act
24 * as a pager for output of various git commands.
25 *
57bdf034 26 * When browsing repositories, tig(1) uses the underlying git commands
468876c9
JF
27 * to present the user with various views, such as summarized commit log
28 * and showing the commit with the log message, diffstat, and the diff.
6706b2ba 29 *
468876c9
JF
30 * Using tig(1) as a pager, it will display input from stdin and try
31 * to colorize it.
b801d8b2
JF
32 **/
33
b76c2afc 34#ifndef VERSION
8c11094f 35#define VERSION "tig-0.3"
b76c2afc
JF
36#endif
37
8855ada4
JF
38#ifndef DEBUG
39#define NDEBUG
40#endif
41
22f66b0a 42#include <assert.h>
4c6fabc2 43#include <errno.h>
22f66b0a
JF
44#include <ctype.h>
45#include <signal.h>
b801d8b2 46#include <stdarg.h>
b801d8b2 47#include <stdio.h>
22f66b0a 48#include <stdlib.h>
b801d8b2 49#include <string.h>
6908bdbd 50#include <unistd.h>
b76c2afc 51#include <time.h>
b801d8b2
JF
52
53#include <curses.h>
b801d8b2
JF
54
55static void die(const char *err, ...);
56static void report(const char *msg, ...);
1ba2ae4b 57static void set_nonblocking_input(bool loading);
10e290ee 58static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
6b161b31
JF
59
60#define ABS(x) ((x) >= 0 ? (x) : -(x))
61#define MIN(x, y) ((x) < (y) ? (x) : (y))
62
63#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
64#define STRING_SIZE(x) (sizeof(x) - 1)
b76c2afc 65
2e8488b4
JF
66#define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
67#define SIZEOF_CMD 1024 /* Size of command buffer. */
b801d8b2 68
82e78006
JF
69/* This color name can be used to refer to the default term colors. */
70#define COLOR_DEFAULT (-1)
78c70acd 71
67e48ac5 72#define TIG_HELP "(d)iff, (l)og, (m)ain, (q)uit, (h)elp"
468876c9 73
82e78006 74/* The format and size of the date column in the main view. */
4c6fabc2 75#define DATE_FORMAT "%Y-%m-%d %H:%M"
6b161b31 76#define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
4c6fabc2 77
10e290ee
JF
78#define AUTHOR_COLS 20
79
a28bcc22 80/* The default interval between line numbers. */
4a2909a7 81#define NUMBER_INTERVAL 1
82e78006 82
6706b2ba
JF
83#define TABSIZE 8
84
a28bcc22
JF
85#define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
86
8855ada4 87/* Some ascii-shorthands fitted into the ncurses namespace. */
a28bcc22
JF
88#define KEY_TAB '\t'
89#define KEY_RETURN '\r'
4a2909a7
JF
90#define KEY_ESC 27
91
6706b2ba 92
a28bcc22 93/* User action requests. */
4a2909a7 94enum request {
a28bcc22
JF
95 /* Offset all requests to avoid conflicts with ncurses getch values. */
96 REQ_OFFSET = KEY_MAX + 1,
97
4a2909a7 98 /* XXX: Keep the view request first and in sync with views[]. */
2e8488b4 99 REQ_VIEW_MAIN,
4a2909a7
JF
100 REQ_VIEW_DIFF,
101 REQ_VIEW_LOG,
2e8488b4 102 REQ_VIEW_HELP,
6908bdbd 103 REQ_VIEW_PAGER,
4a2909a7 104
6b161b31 105 REQ_ENTER,
03a93dbb
JF
106 REQ_QUIT,
107 REQ_PROMPT,
4a2909a7 108 REQ_SCREEN_REDRAW,
fac7db6c 109 REQ_SCREEN_RESIZE,
4a2909a7 110 REQ_SCREEN_UPDATE,
03a93dbb
JF
111 REQ_SHOW_VERSION,
112 REQ_STOP_LOADING,
4a2909a7 113 REQ_TOGGLE_LINE_NUMBERS,
03a93dbb 114 REQ_VIEW_NEXT,
4f9b667a 115 REQ_VIEW_CLOSE,
4a2909a7
JF
116
117 REQ_MOVE_UP,
6706b2ba 118 REQ_MOVE_UP_ENTER,
4a2909a7 119 REQ_MOVE_DOWN,
6706b2ba 120 REQ_MOVE_DOWN_ENTER,
4a2909a7
JF
121 REQ_MOVE_PAGE_UP,
122 REQ_MOVE_PAGE_DOWN,
123 REQ_MOVE_FIRST_LINE,
124 REQ_MOVE_LAST_LINE,
125
126 REQ_SCROLL_LINE_UP,
127 REQ_SCROLL_LINE_DOWN,
128 REQ_SCROLL_PAGE_UP,
129 REQ_SCROLL_PAGE_DOWN,
130};
131
c34d9c9f 132struct ref {
468876c9
JF
133 char *name; /* Ref name; tag or head names are shortened. */
134 char id[41]; /* Commit SHA1 ID */
135 unsigned int tag:1; /* Is it a tag? */
136 unsigned int next:1; /* For ref lists: are there more refs? */
c34d9c9f
JF
137};
138
4c6fabc2 139struct commit {
a28bcc22 140 char id[41]; /* SHA1 ID. */
6b161b31 141 char title[75]; /* The first line of the commit message. */
a28bcc22
JF
142 char author[75]; /* The author of the commit. */
143 struct tm time; /* Date from the author ident. */
c34d9c9f 144 struct ref **refs; /* Repository references; tags & branch heads. */
4c6fabc2
JF
145};
146
6706b2ba 147
03a93dbb
JF
148/*
149 * String helpers
150 */
78c70acd 151
82e78006 152static inline void
4685845e 153string_ncopy(char *dst, const char *src, int dstlen)
82e78006
JF
154{
155 strncpy(dst, src, dstlen - 1);
156 dst[dstlen - 1] = 0;
03a93dbb 157
82e78006
JF
158}
159
160/* Shorthand for safely copying into a fixed buffer. */
161#define string_copy(dst, src) \
162 string_ncopy(dst, src, sizeof(dst))
163
6706b2ba 164
03a93dbb
JF
165/* Shell quoting
166 *
167 * NOTE: The following is a slightly modified copy of the git project's shell
168 * quoting routines found in the quote.c file.
169 *
170 * Help to copy the thing properly quoted for the shell safety. any single
171 * quote is replaced with '\'', any exclamation point is replaced with '\!',
172 * and the whole thing is enclosed in a
173 *
174 * E.g.
175 * original sq_quote result
176 * name ==> name ==> 'name'
177 * a b ==> a b ==> 'a b'
178 * a'b ==> a'\''b ==> 'a'\''b'
179 * a!b ==> a'\!'b ==> 'a'\!'b'
180 */
181
182static size_t
183sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src)
184{
185 char c;
186
a9274b95 187#define BUFPUT(x) do { if (bufsize < SIZEOF_CMD) buf[bufsize++] = (x); } while (0)
03a93dbb
JF
188
189 BUFPUT('\'');
190 while ((c = *src++)) {
191 if (c == '\'' || c == '!') {
192 BUFPUT('\'');
193 BUFPUT('\\');
194 BUFPUT(c);
195 BUFPUT('\'');
196 } else {
197 BUFPUT(c);
198 }
199 }
200 BUFPUT('\'');
201
202 return bufsize;
203}
204
82e78006 205
b76c2afc
JF
206/**
207 * OPTIONS
208 * -------
209 **/
210
4b8c01a3
JF
211static const char usage[] =
212VERSION " (" __DATE__ ")\n"
213"\n"
214"Usage: tig [options]\n"
215" or: tig [options] [--] [git log options]\n"
216" or: tig [options] log [git log options]\n"
217" or: tig [options] diff [git diff options]\n"
218" or: tig [options] show [git show options]\n"
219" or: tig [options] < [git command output]\n"
220"\n"
221"Options:\n"
222" -l Start up in log view\n"
223" -d Start up in diff view\n"
224" -n[I], --line-number[=I] Show line numbers with given interval\n"
225" -t[N], --tab-size[=N] Set number of spaces for tab expansion\n"
226" -- Mark end of tig options\n"
227" -v, --version Show version and exit\n"
228" -h, --help Show help message and exit\n";
229
6706b2ba
JF
230/* Option and state variables. */
231static bool opt_line_number = FALSE;
2e8488b4 232static int opt_num_interval = NUMBER_INTERVAL;
6706b2ba 233static int opt_tab_size = TABSIZE;
6b161b31 234static enum request opt_request = REQ_VIEW_MAIN;
03a93dbb 235static char opt_cmd[SIZEOF_CMD] = "";
6908bdbd 236static FILE *opt_pipe = NULL;
b76c2afc 237
b76c2afc 238/* Returns the index of log or diff command or -1 to exit. */
8855ada4 239static bool
b76c2afc
JF
240parse_options(int argc, char *argv[])
241{
242 int i;
243
244 for (i = 1; i < argc; i++) {
245 char *opt = argv[i];
246
247 /**
b76c2afc 248 * -l::
1ba2ae4b 249 * Start up in log view using the internal log command.
b76c2afc 250 **/
6b161b31 251 if (!strcmp(opt, "-l")) {
4a2909a7 252 opt_request = REQ_VIEW_LOG;
6b161b31
JF
253 continue;
254 }
b76c2afc
JF
255
256 /**
257 * -d::
1ba2ae4b 258 * Start up in diff view using the internal diff command.
b76c2afc 259 **/
6b161b31 260 if (!strcmp(opt, "-d")) {
4a2909a7 261 opt_request = REQ_VIEW_DIFF;
6b161b31
JF
262 continue;
263 }
b76c2afc
JF
264
265 /**
2e8488b4 266 * -n[INTERVAL], --line-number[=INTERVAL]::
b76c2afc 267 * Prefix line numbers in log and diff view.
2e8488b4 268 * Optionally, with interval different than each line.
b76c2afc 269 **/
6b161b31 270 if (!strncmp(opt, "-n", 2) ||
8855ada4 271 !strncmp(opt, "--line-number", 13)) {
2e8488b4
JF
272 char *num = opt;
273
274 if (opt[1] == 'n') {
275 num = opt + 2;
276
277 } else if (opt[STRING_SIZE("--line-number")] == '=') {
278 num = opt + STRING_SIZE("--line-number=");
279 }
280
281 if (isdigit(*num))
282 opt_num_interval = atoi(num);
283
6b161b31
JF
284 opt_line_number = TRUE;
285 continue;
286 }
b76c2afc
JF
287
288 /**
6706b2ba
JF
289 * -t[NSPACES], --tab-size[=NSPACES]::
290 * Set the number of spaces tabs should be expanded to.
291 **/
292 if (!strncmp(opt, "-t", 2) ||
293 !strncmp(opt, "--tab-size", 10)) {
294 char *num = opt;
295
296 if (opt[1] == 't') {
297 num = opt + 2;
298
299 } else if (opt[STRING_SIZE("--tab-size")] == '=') {
300 num = opt + STRING_SIZE("--tab-size=");
301 }
302
303 if (isdigit(*num))
304 opt_tab_size = MIN(atoi(num), TABSIZE);
305 continue;
306 }
307
308 /**
b76c2afc
JF
309 * -v, --version::
310 * Show version and exit.
311 **/
6b161b31 312 if (!strcmp(opt, "-v") ||
8855ada4 313 !strcmp(opt, "--version")) {
b76c2afc 314 printf("tig version %s\n", VERSION);
8855ada4 315 return FALSE;
6b161b31 316 }
b76c2afc
JF
317
318 /**
4b8c01a3
JF
319 * -h, --help::
320 * Show help message and exit.
321 **/
322 if (!strcmp(opt, "-h") ||
323 !strcmp(opt, "--help")) {
324 printf(usage);
325 return FALSE;
326 }
327
328 /**
03a93dbb 329 * \--::
0721c53a 330 * End of tig(1) options. Useful when specifying command
d44f34bf 331 * options for the main view. Example:
03a93dbb 332 *
889cb462 333 * $ tig -- --since=1.month
b76c2afc 334 **/
6908bdbd
JF
335 if (!strcmp(opt, "--")) {
336 i++;
337 break;
338 }
03a93dbb 339
8855ada4 340 /**
d44f34bf 341 * log [git log options]::
8855ada4
JF
342 * Open log view using the given git log options.
343 *
d44f34bf 344 * diff [git diff options]::
8855ada4
JF
345 * Open diff view using the given git diff options.
346 *
d44f34bf 347 * show [git show options]::
8855ada4
JF
348 * Open diff view using the given git show options.
349 **/
350 if (!strcmp(opt, "log") ||
351 !strcmp(opt, "diff") ||
352 !strcmp(opt, "show")) {
353 opt_request = opt[0] == 'l'
354 ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
355 break;
356 }
357
d44f34bf 358 /**
57bdf034 359 * [git log options]::
d44f34bf
JF
360 * tig(1) will stop the option parsing when the first
361 * command line parameter not starting with "-" is
57bdf034
JF
362 * encountered. All options including this one will be
363 * passed to git log when loading the main view.
364 * This makes it possible to say:
889cb462 365 *
03a93dbb 366 * $ tig tag-1.0..HEAD
d44f34bf 367 **/
03a93dbb 368 if (opt[0] && opt[0] != '-')
6908bdbd 369 break;
6b161b31
JF
370
371 die("unknown command '%s'", opt);
b76c2afc
JF
372 }
373
6908bdbd 374 if (!isatty(STDIN_FILENO)) {
8855ada4
JF
375 /**
376 * Pager mode
377 * ~~~~~~~~~~
378 * If stdin is a pipe, any log or diff options will be ignored and the
379 * pager view will be opened loading data from stdin. The pager mode
380 * can be used for colorizing output from various git commands.
381 *
382 * Example on how to colorize the output of git-show(1):
383 *
384 * $ git show | tig
385 **/
6908bdbd
JF
386 opt_request = REQ_VIEW_PAGER;
387 opt_pipe = stdin;
388
389 } else if (i < argc) {
390 size_t buf_size;
391
8855ada4
JF
392 /**
393 * Git command options
394 * ~~~~~~~~~~~~~~~~~~~
8855ada4
JF
395 * All git command options specified on the command line will
396 * be passed to the given command and all will be shell quoted
468876c9 397 * before they are passed to the shell.
8855ada4 398 *
468876c9
JF
399 * NOTE: If you specify options for the main view, you should
400 * not use the `--pretty` option as this option will be set
401 * automatically to the format expected by the main view.
8855ada4
JF
402 *
403 * Example on how to open the log view and show both author and
404 * committer information:
405 *
406 * $ tig log --pretty=fuller
468876c9 407 *
0721c53a
JF
408 * See the <<refspec, "Specifying revisions">> section below
409 * for an introduction to revision options supported by the git
410 * commands. For details on specific git command options, refer
411 * to the man page of the command in question.
8855ada4
JF
412 **/
413
6908bdbd 414 if (opt_request == REQ_VIEW_MAIN)
8855ada4
JF
415 /* XXX: This is vulnerable to the user overriding
416 * options required for the main view parser. */
6908bdbd
JF
417 string_copy(opt_cmd, "git log --stat --pretty=raw");
418 else
419 string_copy(opt_cmd, "git");
420 buf_size = strlen(opt_cmd);
421
422 while (buf_size < sizeof(opt_cmd) && i < argc) {
423 opt_cmd[buf_size++] = ' ';
424 buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
425 }
426
427 if (buf_size >= sizeof(opt_cmd))
428 die("command too long");
429
430 opt_cmd[buf_size] = 0;
431
432 }
433
8855ada4 434 return TRUE;
b76c2afc
JF
435}
436
437
438/*
439 * Line-oriented content detection.
6b161b31
JF
440 */
441
2e8488b4 442#define LINE_INFO \
6b161b31
JF
443/* Line type String to match Foreground Background Attributes
444 * --------- --------------- ---------- ---------- ---------- */ \
a28bcc22
JF
445/* Diff markup */ \
446LINE(DIFF, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
8855ada4 447LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
a28bcc22
JF
448LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
449LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
450LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
3af8774e
JF
451LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
452LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
a28bcc22
JF
453LINE(DIFF_COPY, "copy ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
454LINE(DIFF_RENAME, "rename ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
455LINE(DIFF_SIM, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
456LINE(DIFF_DISSIM, "dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
457/* Pretty print commit header */ \
6908bdbd 458LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
8855ada4 459LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
6908bdbd
JF
460LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
461LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
8855ada4
JF
462LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
463LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
a28bcc22
JF
464/* Raw commit header */ \
465LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
466LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
467LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
8855ada4 468LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
a28bcc22
JF
469LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
470/* Misc */ \
471LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
472LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
473/* UI colors */ \
474LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
475LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
476LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
6b161b31
JF
477LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
478LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
a28bcc22
JF
479LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
480LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
481LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
c34d9c9f
JF
482LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
483LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
484LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD),
2e8488b4 485
78c70acd 486enum line_type {
2e8488b4
JF
487#define LINE(type, line, fg, bg, attr) \
488 LINE_##type
489 LINE_INFO
490#undef LINE
78c70acd
JF
491};
492
493struct line_info {
4685845e 494 const char *line; /* The start of line to match. */
2e8488b4
JF
495 int linelen; /* Size of string to match. */
496 int fg, bg, attr; /* Color and text attributes for the lines. */
78c70acd
JF
497};
498
2e8488b4 499static struct line_info line_info[] = {
78c70acd 500#define LINE(type, line, fg, bg, attr) \
a28bcc22 501 { (line), STRING_SIZE(line), (fg), (bg), (attr) }
2e8488b4
JF
502 LINE_INFO
503#undef LINE
78c70acd
JF
504};
505
2e8488b4
JF
506static enum line_type
507get_line_type(char *line)
78c70acd
JF
508{
509 int linelen = strlen(line);
a28bcc22 510 enum line_type type;
78c70acd 511
a28bcc22 512 for (type = 0; type < ARRAY_SIZE(line_info); type++)
2e8488b4 513 /* Case insensitive search matches Signed-off-by lines better. */
a28bcc22
JF
514 if (linelen >= line_info[type].linelen &&
515 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
516 return type;
78c70acd 517
2e8488b4 518 return LINE_DEFAULT;
78c70acd
JF
519}
520
2e8488b4 521static inline int
78c70acd
JF
522get_line_attr(enum line_type type)
523{
2e8488b4
JF
524 assert(type < ARRAY_SIZE(line_info));
525 return COLOR_PAIR(type) | line_info[type].attr;
78c70acd
JF
526}
527
528static void
529init_colors(void)
530{
82e78006
JF
531 int default_bg = COLOR_BLACK;
532 int default_fg = COLOR_WHITE;
a28bcc22 533 enum line_type type;
78c70acd
JF
534
535 start_color();
536
537 if (use_default_colors() != ERR) {
82e78006
JF
538 default_bg = -1;
539 default_fg = -1;
78c70acd
JF
540 }
541
a28bcc22
JF
542 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
543 struct line_info *info = &line_info[type];
82e78006
JF
544 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
545 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
78c70acd 546
a28bcc22 547 init_pair(type, fg, bg);
78c70acd
JF
548 }
549}
550
551
1ba2ae4b
JF
552/**
553 * ENVIRONMENT VARIABLES
554 * ---------------------
c34d9c9f
JF
555 * Several options related to the interface with git can be configured
556 * via environment options.
557 *
558 * Repository references
559 * ~~~~~~~~~~~~~~~~~~~~~
560 * Commits that are referenced by tags and branch heads will be marked
561 * by the reference name surrounded by '[' and ']':
562 *
468876c9 563 * 2006-03-26 19:42 Petr Baudis | [cogito-0.17.1] Cogito 0.17.1
c34d9c9f
JF
564 *
565 * If you want to filter out certain directories under `.git/refs/`, say
566 * `tmp` you can do it by setting the following variable:
567 *
6706b2ba 568 * $ TIG_LS_REMOTE="git ls-remote . | sed /\/tmp\//d" tig
c34d9c9f
JF
569 *
570 * Or set the variable permanently in your environment.
571 *
572 * TIG_LS_REMOTE::
6706b2ba
JF
573 * Set command for retrieving all repository references. The command
574 * should output data in the same format as git-ls-remote(1).
c34d9c9f
JF
575 **/
576
577#define TIG_LS_REMOTE \
1b4b0bd9 578 "git ls-remote . 2>/dev/null"
c34d9c9f
JF
579
580/**
0721c53a 581 * [[view-commands]]
c34d9c9f
JF
582 * View commands
583 * ~~~~~~~~~~~~~
1ba2ae4b 584 * It is possible to alter which commands are used for the different views.
6706b2ba
JF
585 * If for example you prefer commits in the main view to be sorted by date
586 * and only show 500 commits, use:
1ba2ae4b
JF
587 *
588 * $ TIG_MAIN_CMD="git log --date-order -n500 --pretty=raw %s" tig
589 *
590 * Or set the variable permanently in your environment.
591 *
592 * Notice, how `%s` is used to specify the commit reference. There can
593 * be a maximum of 5 `%s` ref specifications.
594 *
595 * TIG_DIFF_CMD::
596 * The command used for the diff view. By default, git show is used
597 * as a backend.
598 *
599 * TIG_LOG_CMD::
6706b2ba
JF
600 * The command used for the log view. If you prefer to have both
601 * author and committer shown in the log view be sure to pass
602 * `--pretty=fuller` to git log.
1ba2ae4b
JF
603 *
604 * TIG_MAIN_CMD::
605 * The command used for the main view. Note, you must always specify
606 * the option: `--pretty=raw` since the main view parser expects to
607 * read that format.
608 **/
609
610#define TIG_DIFF_CMD \
611 "git show --patch-with-stat --find-copies-harder -B -C %s"
612
613#define TIG_LOG_CMD \
614 "git log --cc --stat -n100 %s"
615
616#define TIG_MAIN_CMD \
617 "git log --topo-order --stat --pretty=raw %s"
618
0721c53a 619/* ... silently ignore that the following are also exported. */
1ba2ae4b
JF
620
621#define TIG_HELP_CMD \
1b4b0bd9 622 "man tig 2>/dev/null"
1ba2ae4b
JF
623
624#define TIG_PAGER_CMD \
625 ""
626
c2124ccd 627
468876c9
JF
628/**
629 * The viewer
630 * ----------
c2124ccd
JF
631 * The display consists of a status window on the last line of the screen and
632 * one or more views. The default is to only show one view at the time but it
633 * is possible to split both the main and log view to also show the commit
634 * diff.
468876c9 635 *
c2124ccd
JF
636 * If you are in the log view and press 'Enter' when the current line is a
637 * commit line, such as:
468876c9 638 *
c2124ccd 639 * commit 4d55caff4cc89335192f3e566004b4ceef572521
468876c9 640 *
c2124ccd
JF
641 * You will split the view so that the log view is displayed in the top window
642 * and the diff view in the bottom window. You can switch between the two
643 * views by pressing 'Tab'. To maximize the log view again, simply press 'l'.
644 **/
645
646struct view;
647
648/* The display array of active views and the index of the current view. */
649static struct view *display[2];
650static unsigned int current_view;
651
652#define foreach_view(view, i) \
653 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
654
655
656/**
657 * Current head and commit ID
658 * ~~~~~~~~~~~~~~~~~~~~~~~~~~
659 * The viewer keeps track of both what head and commit ID you are currently
660 * viewing. The commit ID will follow the cursor line and change everytime time
661 * you highlight a different commit. Whenever you reopen the diff view it
662 * will be reloaded, if the commit ID changed.
468876c9 663 *
c2124ccd
JF
664 * The head ID is used when opening the main and log view to indicate from
665 * what revision to show history.
468876c9 666 **/
b801d8b2 667
c2124ccd
JF
668static char ref_commit[SIZEOF_REF] = "HEAD";
669static char ref_head[SIZEOF_REF] = "HEAD";
670
671
b801d8b2 672struct view {
03a93dbb 673 const char *name; /* View name */
4685845e
TH
674 const char *cmd_fmt; /* Default command line format */
675 const char *cmd_env; /* Command line set via environment */
676 const char *id; /* Points to either of ref_{head,commit} */
6b161b31
JF
677
678 struct view_ops {
6734f6b9
JF
679 /* What type of content being displayed. Used in the
680 * title bar. */
4685845e 681 const char *type;
8855ada4 682 /* Draw one line; @lineno must be < view->height. */
6b161b31 683 bool (*draw)(struct view *view, unsigned int lineno);
8855ada4 684 /* Read one line; updates view->line. */
6b161b31 685 bool (*read)(struct view *view, char *line);
8855ada4 686 /* Depending on view, change display based on current line. */
6b161b31
JF
687 bool (*enter)(struct view *view);
688 } *ops;
22f66b0a 689
03a93dbb 690 char cmd[SIZEOF_CMD]; /* Command buffer */
49f2b43f
JF
691 char ref[SIZEOF_REF]; /* Hovered commit reference */
692 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2e8488b4 693
8855ada4
JF
694 int height, width; /* The width and height of the main window */
695 WINDOW *win; /* The main window */
696 WINDOW *title; /* The title window living below the main window */
b801d8b2
JF
697
698 /* Navigation */
699 unsigned long offset; /* Offset of the window top */
700 unsigned long lineno; /* Current line number */
701
f6da0b66
JF
702 /* If non-NULL, points to the view that opened this view. If this view
703 * is closed tig will switch back to the parent view. */
704 struct view *parent;
705
b801d8b2
JF
706 /* Buffering */
707 unsigned long lines; /* Total number of lines */
8855ada4
JF
708 void **line; /* Line index; each line contains user data */
709 unsigned int digits; /* Number of digits in the lines member. */
b801d8b2
JF
710
711 /* Loading */
712 FILE *pipe;
2e8488b4 713 time_t start_time;
b801d8b2
JF
714};
715
6b161b31
JF
716static struct view_ops pager_ops;
717static struct view_ops main_ops;
a28bcc22 718
95d7ddcd
JF
719#define VIEW_STR(name, cmd, env, ref, ops) \
720 { name, cmd, #env, ref, ops }
1ba2ae4b 721
95d7ddcd
JF
722#define VIEW_(id, name, ops, ref) \
723 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops)
1ba2ae4b 724
c2124ccd
JF
725/**
726 * Views
727 * ~~~~~
728 * tig(1) presents various 'views' of a repository. Each view is based on output
729 * from an external command, most often 'git log', 'git diff', or 'git show'.
730 *
731 * The main view::
732 * Is the default view, and it shows a one line summary of each commit
733 * in the chosen list of revisions. The summary includes commit date,
734 * author, and the first line of the log message. Additionally, any
735 * repository references, such as tags, will be shown.
736 *
737 * The log view::
738 * Presents a more rich view of the revision log showing the whole log
739 * message and the diffstat.
740 *
741 * The diff view::
742 * Shows either the diff of the current working tree, that is, what
743 * has changed since the last commit, or the commit diff complete
744 * with log message, diffstat and diff.
745 *
746 * The pager view::
747 * Is used for displaying both input from stdin and output from git
748 * commands entered in the internal prompt.
749 *
750 * The help view::
751 * Displays the information from the tig(1) man page. For the help view
752 * to work you need to have the tig(1) man page installed.
753 **/
754
b801d8b2 755static struct view views[] = {
95d7ddcd
JF
756 VIEW_(MAIN, "main", &main_ops, ref_head),
757 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
758 VIEW_(LOG, "log", &pager_ops, ref_head),
759 VIEW_(HELP, "help", &pager_ops, "static"),
760 VIEW_(PAGER, "pager", &pager_ops, "static"),
b801d8b2
JF
761};
762
a28bcc22
JF
763#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
764
4c6fabc2 765
b801d8b2 766static void
82e78006 767redraw_view_from(struct view *view, int lineno)
b801d8b2 768{
82e78006 769 assert(0 <= lineno && lineno < view->height);
b801d8b2 770
82e78006 771 for (; lineno < view->height; lineno++) {
6b161b31 772 if (!view->ops->draw(view, lineno))
fd85fef1 773 break;
b801d8b2
JF
774 }
775
776 redrawwin(view->win);
777 wrefresh(view->win);
778}
779
b76c2afc 780static void
82e78006
JF
781redraw_view(struct view *view)
782{
783 wclear(view->win);
784 redraw_view_from(view, 0);
785}
786
c2124ccd
JF
787
788/**
789 * Title windows
790 * ~~~~~~~~~~~~~
791 * Each view has a title window which shows the name of the view, current
792 * commit ID if available, and where the view is positioned:
793 *
794 * [main] c622eefaa485995320bc743431bae0d497b1d875 - commit 1 of 61 (1%)
795 *
796 * By default, the title of the current view is highlighted using bold font.
797 **/
798
6b161b31 799static void
81030ec8
JF
800update_view_title(struct view *view)
801{
802 if (view == display[current_view])
803 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
804 else
805 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
806
807 werase(view->title);
808 wmove(view->title, 0, 0);
809
81030ec8
JF
810 if (*view->ref)
811 wprintw(view->title, "[%s] %s", view->name, view->ref);
812 else
813 wprintw(view->title, "[%s]", view->name);
814
815 if (view->lines) {
816 wprintw(view->title, " - %s %d of %d (%d%%)",
817 view->ops->type,
818 view->lineno + 1,
819 view->lines,
820 (view->lineno + 1) * 100 / view->lines);
821 }
822
823 wrefresh(view->title);
824}
825
826static void
6b161b31 827resize_display(void)
b76c2afc 828{
03a93dbb 829 int offset, i;
6b161b31
JF
830 struct view *base = display[0];
831 struct view *view = display[1] ? display[1] : display[0];
b76c2afc 832
6b161b31 833 /* Setup window dimensions */
b76c2afc 834
03a93dbb 835 getmaxyx(stdscr, base->height, base->width);
b76c2afc 836
6b161b31 837 /* Make room for the status window. */
03a93dbb 838 base->height -= 1;
6b161b31
JF
839
840 if (view != base) {
03a93dbb
JF
841 /* Horizontal split. */
842 view->width = base->width;
6b161b31
JF
843 view->height = SCALE_SPLIT_VIEW(base->height);
844 base->height -= view->height;
845
846 /* Make room for the title bar. */
847 view->height -= 1;
848 }
849
850 /* Make room for the title bar. */
851 base->height -= 1;
852
853 offset = 0;
854
855 foreach_view (view, i) {
4d55caff
JF
856 /* Keep the height of all view->win windows one larger than is
857 * required so that the cursor can wrap-around on the last line
858 * without scrolling the window. */
b76c2afc 859 if (!view->win) {
6706b2ba 860 view->win = newwin(view->height + 1, 0, offset, 0);
6b161b31
JF
861 if (!view->win)
862 die("Failed to create %s view", view->name);
863
864 scrollok(view->win, TRUE);
865
866 view->title = newwin(1, 0, offset + view->height, 0);
867 if (!view->title)
868 die("Failed to create title window");
869
870 } else {
6706b2ba 871 wresize(view->win, view->height + 1, view->width);
6b161b31
JF
872 mvwin(view->win, offset, 0);
873 mvwin(view->title, offset + view->height, 0);
874 wrefresh(view->win);
a28bcc22 875 }
a28bcc22 876
6b161b31 877 offset += view->height + 1;
b76c2afc 878 }
6b161b31 879}
b76c2afc 880
6b161b31 881static void
20bb5e18
JF
882redraw_display(void)
883{
884 struct view *view;
885 int i;
886
887 foreach_view (view, i) {
888 redraw_view(view);
889 update_view_title(view);
890 }
891}
892
893
2e8488b4
JF
894/*
895 * Navigation
896 */
897
4a2909a7 898/* Scrolling backend */
b801d8b2 899static void
4a2909a7 900do_scroll_view(struct view *view, int lines)
b801d8b2 901{
fd85fef1
JF
902 /* The rendering expects the new offset. */
903 view->offset += lines;
904
905 assert(0 <= view->offset && view->offset < view->lines);
906 assert(lines);
b801d8b2 907
82e78006 908 /* Redraw the whole screen if scrolling is pointless. */
4c6fabc2 909 if (view->height < ABS(lines)) {
b76c2afc
JF
910 redraw_view(view);
911
912 } else {
22f66b0a 913 int line = lines > 0 ? view->height - lines : 0;
82e78006 914 int end = line + ABS(lines);
fd85fef1
JF
915
916 wscrl(view->win, lines);
917
22f66b0a 918 for (; line < end; line++) {
6b161b31 919 if (!view->ops->draw(view, line))
fd85fef1
JF
920 break;
921 }
922 }
923
924 /* Move current line into the view. */
925 if (view->lineno < view->offset) {
926 view->lineno = view->offset;
6b161b31 927 view->ops->draw(view, 0);
fd85fef1
JF
928
929 } else if (view->lineno >= view->offset + view->height) {
6706b2ba
JF
930 if (view->lineno == view->offset + view->height) {
931 /* Clear the hidden line so it doesn't show if the view
932 * is scrolled up. */
933 wmove(view->win, view->height, 0);
934 wclrtoeol(view->win);
935 }
fd85fef1 936 view->lineno = view->offset + view->height - 1;
6b161b31 937 view->ops->draw(view, view->lineno - view->offset);
fd85fef1
JF
938 }
939
4c6fabc2 940 assert(view->offset <= view->lineno && view->lineno < view->lines);
fd85fef1
JF
941
942 redrawwin(view->win);
943 wrefresh(view->win);
9d3f5834 944 report("");
fd85fef1 945}
78c70acd 946
4a2909a7 947/* Scroll frontend */
fd85fef1 948static void
6b161b31 949scroll_view(struct view *view, enum request request)
fd85fef1
JF
950{
951 int lines = 1;
b801d8b2
JF
952
953 switch (request) {
4a2909a7 954 case REQ_SCROLL_PAGE_DOWN:
fd85fef1 955 lines = view->height;
4a2909a7 956 case REQ_SCROLL_LINE_DOWN:
b801d8b2 957 if (view->offset + lines > view->lines)
bde3653a 958 lines = view->lines - view->offset;
b801d8b2 959
fd85fef1 960 if (lines == 0 || view->offset + view->height >= view->lines) {
eb98559e 961 report("Cannot scroll beyond the last line");
b801d8b2
JF
962 return;
963 }
964 break;
965
4a2909a7 966 case REQ_SCROLL_PAGE_UP:
fd85fef1 967 lines = view->height;
4a2909a7 968 case REQ_SCROLL_LINE_UP:
b801d8b2
JF
969 if (lines > view->offset)
970 lines = view->offset;
971
972 if (lines == 0) {
eb98559e 973 report("Cannot scroll beyond the first line");
b801d8b2
JF
974 return;
975 }
976
fd85fef1 977 lines = -lines;
b801d8b2 978 break;
03a93dbb 979
6b161b31
JF
980 default:
981 die("request %d not handled in switch", request);
b801d8b2
JF
982 }
983
4a2909a7 984 do_scroll_view(view, lines);
fd85fef1 985}
b801d8b2 986
4a2909a7 987/* Cursor moving */
fd85fef1 988static void
6b161b31 989move_view(struct view *view, enum request request)
fd85fef1
JF
990{
991 int steps;
b801d8b2 992
fd85fef1 993 switch (request) {
4a2909a7 994 case REQ_MOVE_FIRST_LINE:
78c70acd
JF
995 steps = -view->lineno;
996 break;
997
4a2909a7 998 case REQ_MOVE_LAST_LINE:
78c70acd
JF
999 steps = view->lines - view->lineno - 1;
1000 break;
1001
4a2909a7 1002 case REQ_MOVE_PAGE_UP:
78c70acd
JF
1003 steps = view->height > view->lineno
1004 ? -view->lineno : -view->height;
1005 break;
1006
4a2909a7 1007 case REQ_MOVE_PAGE_DOWN:
78c70acd
JF
1008 steps = view->lineno + view->height >= view->lines
1009 ? view->lines - view->lineno - 1 : view->height;
1010 break;
1011
4a2909a7 1012 case REQ_MOVE_UP:
6706b2ba 1013 case REQ_MOVE_UP_ENTER:
fd85fef1
JF
1014 steps = -1;
1015 break;
b801d8b2 1016
4a2909a7 1017 case REQ_MOVE_DOWN:
6706b2ba 1018 case REQ_MOVE_DOWN_ENTER:
fd85fef1
JF
1019 steps = 1;
1020 break;
6b161b31
JF
1021
1022 default:
1023 die("request %d not handled in switch", request);
78c70acd 1024 }
b801d8b2 1025
4c6fabc2 1026 if (steps <= 0 && view->lineno == 0) {
eb98559e 1027 report("Cannot move beyond the first line");
78c70acd 1028 return;
b801d8b2 1029
6908bdbd 1030 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
eb98559e 1031 report("Cannot move beyond the last line");
78c70acd 1032 return;
fd85fef1
JF
1033 }
1034
4c6fabc2 1035 /* Move the current line */
fd85fef1 1036 view->lineno += steps;
4c6fabc2
JF
1037 assert(0 <= view->lineno && view->lineno < view->lines);
1038
1039 /* Repaint the old "current" line if we be scrolling */
2e8488b4
JF
1040 if (ABS(steps) < view->height) {
1041 int prev_lineno = view->lineno - steps - view->offset;
1042
1043 wmove(view->win, prev_lineno, 0);
1044 wclrtoeol(view->win);
03a93dbb 1045 view->ops->draw(view, prev_lineno);
2e8488b4 1046 }
fd85fef1 1047
4c6fabc2 1048 /* Check whether the view needs to be scrolled */
fd85fef1
JF
1049 if (view->lineno < view->offset ||
1050 view->lineno >= view->offset + view->height) {
1051 if (steps < 0 && -steps > view->offset) {
1052 steps = -view->offset;
b76c2afc
JF
1053
1054 } else if (steps > 0) {
1055 if (view->lineno == view->lines - 1 &&
1056 view->lines > view->height) {
1057 steps = view->lines - view->offset - 1;
1058 if (steps >= view->height)
1059 steps -= view->height - 1;
1060 }
b801d8b2 1061 }
78c70acd 1062
4a2909a7 1063 do_scroll_view(view, steps);
fd85fef1 1064 return;
b801d8b2
JF
1065 }
1066
4c6fabc2 1067 /* Draw the current line */
6b161b31 1068 view->ops->draw(view, view->lineno - view->offset);
fd85fef1 1069
b801d8b2
JF
1070 redrawwin(view->win);
1071 wrefresh(view->win);
9d3f5834 1072 report("");
b801d8b2
JF
1073}
1074
b801d8b2 1075
2e8488b4
JF
1076/*
1077 * Incremental updating
1078 */
b801d8b2 1079
03a93dbb 1080static bool
b801d8b2
JF
1081begin_update(struct view *view)
1082{
4685845e 1083 const char *id = view->id;
fd85fef1 1084
03a93dbb
JF
1085 if (opt_cmd[0]) {
1086 string_copy(view->cmd, opt_cmd);
1087 opt_cmd[0] = 0;
8855ada4
JF
1088 /* When running random commands, the view ref could have become
1089 * invalid so clear it. */
1090 view->ref[0] = 0;
03a93dbb 1091 } else {
4685845e 1092 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1ba2ae4b
JF
1093
1094 if (snprintf(view->cmd, sizeof(view->cmd), format,
1095 id, id, id, id, id) >= sizeof(view->cmd))
03a93dbb
JF
1096 return FALSE;
1097 }
b801d8b2 1098
6908bdbd
JF
1099 /* Special case for the pager view. */
1100 if (opt_pipe) {
1101 view->pipe = opt_pipe;
1102 opt_pipe = NULL;
1103 } else {
1104 view->pipe = popen(view->cmd, "r");
1105 }
1106
2e8488b4
JF
1107 if (!view->pipe)
1108 return FALSE;
b801d8b2 1109
6b161b31 1110 set_nonblocking_input(TRUE);
b801d8b2
JF
1111
1112 view->offset = 0;
1113 view->lines = 0;
1114 view->lineno = 0;
49f2b43f 1115 string_copy(view->vid, id);
b801d8b2 1116
2e8488b4
JF
1117 if (view->line) {
1118 int i;
1119
1120 for (i = 0; i < view->lines; i++)
1121 if (view->line[i])
1122 free(view->line[i]);
1123
1124 free(view->line);
1125 view->line = NULL;
1126 }
1127
1128 view->start_time = time(NULL);
1129
b801d8b2
JF
1130 return TRUE;
1131}
1132
1133static void
1134end_update(struct view *view)
1135{
03a93dbb
JF
1136 if (!view->pipe)
1137 return;
6b161b31 1138 set_nonblocking_input(FALSE);
80ce96ea
JF
1139 if (view->pipe == stdin)
1140 fclose(view->pipe);
1141 else
1142 pclose(view->pipe);
2e8488b4 1143 view->pipe = NULL;
b801d8b2
JF
1144}
1145
03a93dbb 1146static bool
b801d8b2
JF
1147update_view(struct view *view)
1148{
1149 char buffer[BUFSIZ];
1150 char *line;
22f66b0a 1151 void **tmp;
82e78006
JF
1152 /* The number of lines to read. If too low it will cause too much
1153 * redrawing (and possible flickering), if too high responsiveness
1154 * will suffer. */
8855ada4 1155 unsigned long lines = view->height;
82e78006 1156 int redraw_from = -1;
b801d8b2
JF
1157
1158 if (!view->pipe)
1159 return TRUE;
1160
82e78006
JF
1161 /* Only redraw if lines are visible. */
1162 if (view->offset + view->height >= view->lines)
1163 redraw_from = view->lines - view->offset;
b801d8b2
JF
1164
1165 tmp = realloc(view->line, sizeof(*view->line) * (view->lines + lines));
1166 if (!tmp)
1167 goto alloc_error;
1168
1169 view->line = tmp;
1170
1171 while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
c34d9c9f 1172 int linelen = strlen(line);
b801d8b2 1173
b801d8b2
JF
1174 if (linelen)
1175 line[linelen - 1] = 0;
1176
6b161b31 1177 if (!view->ops->read(view, line))
b801d8b2 1178 goto alloc_error;
fd85fef1
JF
1179
1180 if (lines-- == 1)
1181 break;
b801d8b2
JF
1182 }
1183
8855ada4
JF
1184 {
1185 int digits;
1186
1187 lines = view->lines;
1188 for (digits = 0; lines; digits++)
1189 lines /= 10;
1190
1191 /* Keep the displayed view in sync with line number scaling. */
1192 if (digits != view->digits) {
1193 view->digits = digits;
1194 redraw_from = 0;
1195 }
1196 }
1197
82e78006
JF
1198 if (redraw_from >= 0) {
1199 /* If this is an incremental update, redraw the previous line
a28bcc22
JF
1200 * since for commits some members could have changed when
1201 * loading the main view. */
82e78006
JF
1202 if (redraw_from > 0)
1203 redraw_from--;
1204
1205 /* Incrementally draw avoids flickering. */
1206 redraw_view_from(view, redraw_from);
4c6fabc2 1207 }
b801d8b2 1208
eb98559e
JF
1209 /* Update the title _after_ the redraw so that if the redraw picks up a
1210 * commit reference in view->ref it'll be available here. */
1211 update_view_title(view);
1212
b801d8b2 1213 if (ferror(view->pipe)) {
03a93dbb 1214 report("Failed to read: %s", strerror(errno));
b801d8b2
JF
1215 goto end;
1216
1217 } else if (feof(view->pipe)) {
2e8488b4
JF
1218 time_t secs = time(NULL) - view->start_time;
1219
a28bcc22 1220 if (view == VIEW(REQ_VIEW_HELP)) {
4685845e 1221 const char *msg = TIG_HELP;
468876c9
JF
1222
1223 if (view->lines == 0) {
1224 /* Slightly ugly, but abusing view->ref keeps
1225 * the error message. */
1226 string_copy(view->ref, "No help available");
1227 msg = "The tig(1) manpage is not installed";
1228 }
1229
1230 report("%s", msg);
2e8488b4
JF
1231 goto end;
1232 }
1233
1234 report("Loaded %d lines in %ld second%s", view->lines, secs,
1235 secs == 1 ? "" : "s");
b801d8b2
JF
1236 goto end;
1237 }
1238
1239 return TRUE;
1240
1241alloc_error:
2e8488b4 1242 report("Allocation failure");
b801d8b2
JF
1243
1244end:
1245 end_update(view);
1246 return FALSE;
1247}
1248
49f2b43f
JF
1249enum open_flags {
1250 OPEN_DEFAULT = 0, /* Use default view switching. */
1251 OPEN_SPLIT = 1, /* Split current view. */
1252 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
1253 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
1254};
1255
6b161b31 1256static void
49f2b43f 1257open_view(struct view *prev, enum request request, enum open_flags flags)
b801d8b2 1258{
49f2b43f
JF
1259 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1260 bool split = !!(flags & OPEN_SPLIT);
1261 bool reload = !!(flags & OPEN_RELOAD);
a28bcc22 1262 struct view *view = VIEW(request);
b801d8b2 1263 struct view *displayed;
6b161b31
JF
1264 int nviews;
1265
03a93dbb 1266 /* Cycle between displayed views and count the views. */
6b161b31 1267 foreach_view (displayed, nviews) {
03a93dbb
JF
1268 if (prev != view &&
1269 view == displayed &&
8855ada4 1270 !strcmp(view->vid, prev->vid)) {
6b161b31
JF
1271 current_view = nviews;
1272 /* Blur out the title of the previous view. */
1273 update_view_title(prev);
6734f6b9 1274 report("");
6b161b31 1275 return;
a28bcc22 1276 }
6b161b31 1277 }
b801d8b2 1278
49f2b43f 1279 if (view == prev && nviews == 1 && !reload) {
6b161b31
JF
1280 report("Already in %s view", view->name);
1281 return;
1282 }
b801d8b2 1283
8855ada4 1284 if ((reload || strcmp(view->vid, view->id)) &&
03a93dbb 1285 !begin_update(view)) {
6b161b31
JF
1286 report("Failed to load %s view", view->name);
1287 return;
1288 }
a28bcc22 1289
6b161b31
JF
1290 if (split) {
1291 display[current_view + 1] = view;
1292 if (!backgrounded)
a28bcc22 1293 current_view++;
6b161b31
JF
1294 } else {
1295 /* Maximize the current view. */
1296 memset(display, 0, sizeof(display));
1297 current_view = 0;
1298 display[current_view] = view;
a28bcc22 1299 }
b801d8b2 1300
6b161b31 1301 resize_display();
b801d8b2 1302
a8891802 1303 if (split && prev->lineno - prev->offset >= prev->height) {
03a93dbb 1304 /* Take the title line into account. */
eb98559e 1305 int lines = prev->lineno - prev->offset - prev->height + 1;
03a93dbb
JF
1306
1307 /* Scroll the view that was split if the current line is
1308 * outside the new limited view. */
1309 do_scroll_view(prev, lines);
1310 }
1311
6b161b31
JF
1312 if (prev && view != prev) {
1313 /* "Blur" the previous view. */
6706b2ba
JF
1314 if (!backgrounded)
1315 update_view_title(prev);
6b161b31
JF
1316
1317 /* Continue loading split views in the background. */
03a93dbb 1318 if (!split)
6b161b31 1319 end_update(prev);
f6da0b66 1320 view->parent = prev;
b801d8b2
JF
1321 }
1322
03a93dbb
JF
1323 if (view->pipe) {
1324 /* Clear the old view and let the incremental updating refill
1325 * the screen. */
1326 wclear(view->win);
1327 report("Loading...");
1328 } else {
1329 redraw_view(view);
468876c9
JF
1330 if (view == VIEW(REQ_VIEW_HELP))
1331 report("%s", TIG_HELP);
1332 else
1333 report("");
03a93dbb 1334 }
6706b2ba
JF
1335
1336 /* If the view is backgrounded the above calls to report()
1337 * won't redraw the view title. */
1338 if (backgrounded)
1339 update_view_title(view);
b801d8b2
JF
1340}
1341
1342
6b161b31
JF
1343/*
1344 * User request switch noodle
1345 */
1346
b801d8b2 1347static int
6b161b31 1348view_driver(struct view *view, enum request request)
b801d8b2 1349{
b801d8b2
JF
1350 int i;
1351
1352 switch (request) {
4a2909a7
JF
1353 case REQ_MOVE_UP:
1354 case REQ_MOVE_DOWN:
1355 case REQ_MOVE_PAGE_UP:
1356 case REQ_MOVE_PAGE_DOWN:
1357 case REQ_MOVE_FIRST_LINE:
1358 case REQ_MOVE_LAST_LINE:
a28bcc22 1359 move_view(view, request);
fd85fef1
JF
1360 break;
1361
4a2909a7
JF
1362 case REQ_SCROLL_LINE_DOWN:
1363 case REQ_SCROLL_LINE_UP:
1364 case REQ_SCROLL_PAGE_DOWN:
1365 case REQ_SCROLL_PAGE_UP:
a28bcc22 1366 scroll_view(view, request);
b801d8b2
JF
1367 break;
1368
4a2909a7 1369 case REQ_VIEW_MAIN:
4a2909a7 1370 case REQ_VIEW_DIFF:
2e8488b4
JF
1371 case REQ_VIEW_LOG:
1372 case REQ_VIEW_HELP:
6908bdbd 1373 case REQ_VIEW_PAGER:
49f2b43f 1374 open_view(view, request, OPEN_DEFAULT);
b801d8b2
JF
1375 break;
1376
6706b2ba
JF
1377 case REQ_MOVE_UP_ENTER:
1378 case REQ_MOVE_DOWN_ENTER:
1379 move_view(view, request);
1380 /* Fall-through */
1381
6b161b31 1382 case REQ_ENTER:
6908bdbd
JF
1383 if (!view->lines) {
1384 report("Nothing to enter");
1385 break;
1386 }
6b161b31
JF
1387 return view->ops->enter(view);
1388
03a93dbb
JF
1389 case REQ_VIEW_NEXT:
1390 {
1391 int nviews = display[1] ? 2 : 1;
1392 int next_view = (current_view + 1) % nviews;
1393
1394 if (next_view == current_view) {
1395 report("Only one view is displayed");
1396 break;
1397 }
1398
1399 current_view = next_view;
1400 /* Blur out the title of the previous view. */
1401 update_view_title(view);
6734f6b9 1402 report("");
03a93dbb
JF
1403 break;
1404 }
4a2909a7 1405 case REQ_TOGGLE_LINE_NUMBERS:
b76c2afc 1406 opt_line_number = !opt_line_number;
20bb5e18 1407 redraw_display();
b801d8b2
JF
1408 break;
1409
03a93dbb 1410 case REQ_PROMPT:
8855ada4 1411 /* Always reload^Wrerun commands from the prompt. */
49f2b43f 1412 open_view(view, opt_request, OPEN_RELOAD);
03a93dbb
JF
1413 break;
1414
4a2909a7 1415 case REQ_STOP_LOADING:
03a93dbb 1416 foreach_view (view, i) {
2e8488b4 1417 if (view->pipe)
6706b2ba 1418 report("Stopped loaded the %s view", view->name),
03a93dbb
JF
1419 end_update(view);
1420 }
b801d8b2
JF
1421 break;
1422
4a2909a7 1423 case REQ_SHOW_VERSION:
6cb291b7 1424 report("%s (built %s)", VERSION, __DATE__);
b801d8b2
JF
1425 return TRUE;
1426
fac7db6c
JF
1427 case REQ_SCREEN_RESIZE:
1428 resize_display();
1429 /* Fall-through */
4a2909a7 1430 case REQ_SCREEN_REDRAW:
20bb5e18 1431 redraw_display();
4a2909a7
JF
1432 break;
1433
1434 case REQ_SCREEN_UPDATE:
b801d8b2
JF
1435 doupdate();
1436 return TRUE;
1437
4f9b667a 1438 case REQ_VIEW_CLOSE:
f6da0b66 1439 if (view->parent) {
4f9b667a
JF
1440 memset(display, 0, sizeof(display));
1441 current_view = 0;
f6da0b66
JF
1442 display[current_view] = view->parent;
1443 view->parent = NULL;
4f9b667a
JF
1444 resize_display();
1445 redraw_display();
1446 break;
1447 }
1448 /* Fall-through */
b801d8b2
JF
1449 case REQ_QUIT:
1450 return FALSE;
1451
1452 default:
2e8488b4 1453 /* An unknown key will show most commonly used commands. */
468876c9 1454 report("Unknown key, press 'h' for help");
b801d8b2
JF
1455 return TRUE;
1456 }
1457
1458 return TRUE;
1459}
1460
1461
1462/*
6b161b31 1463 * View backend handlers
b801d8b2
JF
1464 */
1465
6b161b31 1466static bool
22f66b0a 1467pager_draw(struct view *view, unsigned int lineno)
b801d8b2 1468{
78c70acd 1469 enum line_type type;
b801d8b2 1470 char *line;
4c6fabc2 1471 int linelen;
78c70acd 1472 int attr;
b801d8b2 1473
fd85fef1
JF
1474 if (view->offset + lineno >= view->lines)
1475 return FALSE;
1476
b801d8b2 1477 line = view->line[view->offset + lineno];
78c70acd 1478 type = get_line_type(line);
b801d8b2 1479
6706b2ba
JF
1480 wmove(view->win, lineno, 0);
1481
fd85fef1 1482 if (view->offset + lineno == view->lineno) {
8855ada4 1483 if (type == LINE_COMMIT) {
03a93dbb
JF
1484 string_copy(view->ref, line + 7);
1485 string_copy(ref_commit, view->ref);
1486 }
8855ada4 1487
78c70acd 1488 type = LINE_CURSOR;
6706b2ba 1489 wchgat(view->win, -1, 0, type, NULL);
fd85fef1
JF
1490 }
1491
78c70acd 1492 attr = get_line_attr(type);
b801d8b2 1493 wattrset(view->win, attr);
b76c2afc 1494
4c6fabc2 1495 linelen = strlen(line);
4c6fabc2 1496
6706b2ba
JF
1497 if (opt_line_number || opt_tab_size < TABSIZE) {
1498 static char spaces[] = " ";
1499 int col_offset = 0, col = 0;
1500
1501 if (opt_line_number) {
1502 unsigned long real_lineno = view->offset + lineno + 1;
82e78006 1503
6706b2ba
JF
1504 if (real_lineno == 1 ||
1505 (real_lineno % opt_num_interval) == 0) {
1506 wprintw(view->win, "%.*d", view->digits, real_lineno);
8855ada4 1507
6706b2ba
JF
1508 } else {
1509 waddnstr(view->win, spaces,
1510 MIN(view->digits, STRING_SIZE(spaces)));
1511 }
1512 waddstr(view->win, ": ");
1513 col_offset = view->digits + 2;
1514 }
8855ada4 1515
6706b2ba
JF
1516 while (line && col_offset + col < view->width) {
1517 int cols_max = view->width - col_offset - col;
1518 char *text = line;
1519 int cols;
4c6fabc2 1520
b76c2afc 1521 if (*line == '\t') {
6706b2ba 1522 assert(sizeof(spaces) > TABSIZE);
b76c2afc 1523 line++;
6706b2ba
JF
1524 text = spaces;
1525 cols = opt_tab_size - (col % opt_tab_size);
82e78006 1526
b76c2afc 1527 } else {
6706b2ba
JF
1528 line = strchr(line, '\t');
1529 cols = line ? line - text : strlen(text);
b76c2afc 1530 }
6706b2ba
JF
1531
1532 waddnstr(view->win, text, MIN(cols, cols_max));
1533 col += cols;
b76c2afc 1534 }
b76c2afc
JF
1535
1536 } else {
6706b2ba 1537 int col = 0, pos = 0;
b801d8b2 1538
6706b2ba
JF
1539 for (; pos < linelen && col < view->width; pos++, col++)
1540 if (line[pos] == '\t')
1541 col += TABSIZE - (col % TABSIZE) - 1;
1542
1543 waddnstr(view->win, line, pos);
1544 }
2e8488b4 1545
b801d8b2
JF
1546 return TRUE;
1547}
1548
6b161b31 1549static bool
22f66b0a
JF
1550pager_read(struct view *view, char *line)
1551{
6706b2ba
JF
1552 /* Compress empty lines in the help view. */
1553 if (view == VIEW(REQ_VIEW_HELP) &&
1554 !*line &&
1555 view->lines &&
1556 !*((char *) view->line[view->lines - 1]))
1557 return TRUE;
1558
22f66b0a
JF
1559 view->line[view->lines] = strdup(line);
1560 if (!view->line[view->lines])
1561 return FALSE;
1562
1563 view->lines++;
1564 return TRUE;
1565}
1566
6b161b31
JF
1567static bool
1568pager_enter(struct view *view)
1569{
1570 char *line = view->line[view->lineno];
91e8e277 1571 int split = 0;
6b161b31 1572
91e8e277
JF
1573 if ((view == VIEW(REQ_VIEW_LOG) ||
1574 view == VIEW(REQ_VIEW_LOG)) &&
1575 get_line_type(line) == LINE_COMMIT) {
1576 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1577 split = 1;
67e48ac5
JF
1578 }
1579
91e8e277
JF
1580 /* Always scroll the view even if it was split. That way
1581 * you can use Enter to scroll through the log view and
1582 * split open each commit diff. */
1583 scroll_view(view, REQ_SCROLL_LINE_DOWN);
1584
1585 /* FIXME: A minor workaround. Scrolling the view will call report("")
1586 * but if we are scolling a non-current view this won't properly update
1587 * the view title. */
1588 if (split)
1589 update_view_title(view);
6b161b31
JF
1590
1591 return TRUE;
1592}
1593
6b161b31 1594static struct view_ops pager_ops = {
6734f6b9 1595 "line",
6b161b31
JF
1596 pager_draw,
1597 pager_read,
1598 pager_enter,
1599};
1600
80ce96ea 1601
c34d9c9f
JF
1602static struct ref **get_refs(char *id);
1603
6b161b31 1604static bool
22f66b0a
JF
1605main_draw(struct view *view, unsigned int lineno)
1606{
2e8488b4 1607 char buf[DATE_COLS + 1];
22f66b0a 1608 struct commit *commit;
78c70acd 1609 enum line_type type;
6706b2ba 1610 int col = 0;
b76c2afc 1611 size_t timelen;
10e290ee
JF
1612 size_t authorlen;
1613 int trimmed;
22f66b0a
JF
1614
1615 if (view->offset + lineno >= view->lines)
1616 return FALSE;
1617
1618 commit = view->line[view->offset + lineno];
4c6fabc2
JF
1619 if (!*commit->author)
1620 return FALSE;
22f66b0a 1621
6706b2ba
JF
1622 wmove(view->win, lineno, col);
1623
22f66b0a 1624 if (view->offset + lineno == view->lineno) {
03a93dbb 1625 string_copy(view->ref, commit->id);
49f2b43f 1626 string_copy(ref_commit, view->ref);
78c70acd 1627 type = LINE_CURSOR;
6706b2ba
JF
1628 wattrset(view->win, get_line_attr(type));
1629 wchgat(view->win, -1, 0, type, NULL);
1630
78c70acd 1631 } else {
b76c2afc 1632 type = LINE_MAIN_COMMIT;
6706b2ba 1633 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
b76c2afc
JF
1634 }
1635
4c6fabc2 1636 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
b76c2afc 1637 waddnstr(view->win, buf, timelen);
4c6fabc2 1638 waddstr(view->win, " ");
b76c2afc 1639
6706b2ba
JF
1640 col += DATE_COLS;
1641 wmove(view->win, lineno, col);
1642 if (type != LINE_CURSOR)
1643 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
b76c2afc 1644
10e290ee
JF
1645 /* FIXME: Make this optional, and add i18n.commitEncoding support. */
1646 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
1647
1648 if (trimmed) {
1649 waddnstr(view->win, commit->author, authorlen);
6706b2ba
JF
1650 if (type != LINE_CURSOR)
1651 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
b76c2afc
JF
1652 waddch(view->win, '~');
1653 } else {
1654 waddstr(view->win, commit->author);
22f66b0a
JF
1655 }
1656
10e290ee 1657 col += AUTHOR_COLS;
6706b2ba
JF
1658 if (type != LINE_CURSOR)
1659 wattrset(view->win, A_NORMAL);
1660
1661 mvwaddch(view->win, lineno, col, ACS_LTEE);
1662 wmove(view->win, lineno, col + 2);
1663 col += 2;
c34d9c9f
JF
1664
1665 if (commit->refs) {
1666 size_t i = 0;
1667
1668 do {
6706b2ba
JF
1669 if (type == LINE_CURSOR)
1670 ;
1671 else if (commit->refs[i]->tag)
c34d9c9f
JF
1672 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
1673 else
1674 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1675 waddstr(view->win, "[");
1676 waddstr(view->win, commit->refs[i]->name);
1677 waddstr(view->win, "]");
6706b2ba
JF
1678 if (type != LINE_CURSOR)
1679 wattrset(view->win, A_NORMAL);
c34d9c9f 1680 waddstr(view->win, " ");
6706b2ba 1681 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
c34d9c9f
JF
1682 } while (commit->refs[i++]->next);
1683 }
1684
6706b2ba
JF
1685 if (type != LINE_CURSOR)
1686 wattrset(view->win, get_line_attr(type));
1687
1688 {
1689 int titlelen = strlen(commit->title);
1690
1691 if (col + titlelen > view->width)
1692 titlelen = view->width - col;
1693
1694 waddnstr(view->win, commit->title, titlelen);
1695 }
22f66b0a
JF
1696
1697 return TRUE;
1698}
1699
4c6fabc2 1700/* Reads git log --pretty=raw output and parses it into the commit struct. */
6b161b31 1701static bool
22f66b0a
JF
1702main_read(struct view *view, char *line)
1703{
78c70acd
JF
1704 enum line_type type = get_line_type(line);
1705 struct commit *commit;
22f66b0a 1706
78c70acd
JF
1707 switch (type) {
1708 case LINE_COMMIT:
22f66b0a
JF
1709 commit = calloc(1, sizeof(struct commit));
1710 if (!commit)
1711 return FALSE;
1712
4c6fabc2 1713 line += STRING_SIZE("commit ");
b76c2afc 1714
22f66b0a 1715 view->line[view->lines++] = commit;
82e78006 1716 string_copy(commit->id, line);
c34d9c9f 1717 commit->refs = get_refs(commit->id);
78c70acd 1718 break;
22f66b0a 1719
8855ada4 1720 case LINE_AUTHOR:
b76c2afc 1721 {
4c6fabc2 1722 char *ident = line + STRING_SIZE("author ");
b76c2afc
JF
1723 char *end = strchr(ident, '<');
1724
1725 if (end) {
1726 for (; end > ident && isspace(end[-1]); end--) ;
1727 *end = 0;
1728 }
1729
1730 commit = view->line[view->lines - 1];
82e78006 1731 string_copy(commit->author, ident);
b76c2afc 1732
4c6fabc2 1733 /* Parse epoch and timezone */
b76c2afc
JF
1734 if (end) {
1735 char *secs = strchr(end + 1, '>');
1736 char *zone;
1737 time_t time;
1738
1739 if (!secs || secs[1] != ' ')
1740 break;
1741
1742 secs += 2;
1743 time = (time_t) atol(secs);
1744 zone = strchr(secs, ' ');
4c6fabc2 1745 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
b76c2afc
JF
1746 long tz;
1747
1748 zone++;
1749 tz = ('0' - zone[1]) * 60 * 60 * 10;
1750 tz += ('0' - zone[2]) * 60 * 60;
1751 tz += ('0' - zone[3]) * 60;
1752 tz += ('0' - zone[4]) * 60;
1753
1754 if (zone[0] == '-')
1755 tz = -tz;
1756
1757 time -= tz;
1758 }
1759 gmtime_r(&time, &commit->time);
1760 }
1761 break;
1762 }
78c70acd 1763 default:
2e8488b4
JF
1764 /* We should only ever end up here if there has already been a
1765 * commit line, however, be safe. */
1766 if (view->lines == 0)
1767 break;
1768
1769 /* Fill in the commit title if it has not already been set. */
78c70acd 1770 commit = view->line[view->lines - 1];
2e8488b4
JF
1771 if (commit->title[0])
1772 break;
1773
1774 /* Require titles to start with a non-space character at the
1775 * offset used by git log. */
eb98559e
JF
1776 /* FIXME: More gracefull handling of titles; append "..." to
1777 * shortened titles, etc. */
2e8488b4 1778 if (strncmp(line, " ", 4) ||
eb98559e 1779 isspace(line[4]))
82e78006
JF
1780 break;
1781
1782 string_copy(commit->title, line + 4);
22f66b0a
JF
1783 }
1784
1785 return TRUE;
1786}
1787
6b161b31
JF
1788static bool
1789main_enter(struct view *view)
b801d8b2 1790{
89e4aafd 1791 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
6b161b31 1792 return TRUE;
b801d8b2
JF
1793}
1794
6b161b31 1795static struct view_ops main_ops = {
6734f6b9 1796 "commit",
6b161b31
JF
1797 main_draw,
1798 main_read,
1799 main_enter,
1800};
2e8488b4 1801
c34d9c9f 1802
468876c9
JF
1803/**
1804 * KEYS
1805 * ----
1806 * Below the default key bindings are shown.
1807 **/
1808
1809struct keymap {
1810 int alias;
1811 int request;
1812};
1813
3a91b75e 1814static struct keymap keymap[] = {
468876c9
JF
1815 /**
1816 * View switching
1817 * ~~~~~~~~~~~~~~
1818 * m::
1819 * Switch to main view.
1820 * d::
1821 * Switch to diff view.
1822 * l::
1823 * Switch to log view.
1824 * p::
1825 * Switch to pager view.
1826 * h::
1827 * Show man page.
4f9b667a 1828 * q::
f6da0b66
JF
1829 * Close view, if multiple views are open it will jump back to the
1830 * previous view in the view stack. If it is the last open view it
1831 * will quit. Use 'Q' to quit all views at once.
67e48ac5
JF
1832 * Enter::
1833 * This key is "context sensitive" depending on what view you are
1834 * currently in. When in log view on a commit line or in the main
1835 * view, split the view and show the commit diff. In the diff view
1836 * pressing Enter will simply scroll the view one line down.
468876c9
JF
1837 * Tab::
1838 * Switch to next view.
1839 **/
1840 { 'm', REQ_VIEW_MAIN },
1841 { 'd', REQ_VIEW_DIFF },
1842 { 'l', REQ_VIEW_LOG },
1843 { 'p', REQ_VIEW_PAGER },
1844 { 'h', REQ_VIEW_HELP },
1845
4f9b667a 1846 { 'q', REQ_VIEW_CLOSE },
468876c9
JF
1847 { KEY_TAB, REQ_VIEW_NEXT },
1848 { KEY_RETURN, REQ_ENTER },
1849
1850 /**
1851 * Cursor navigation
1852 * ~~~~~~~~~~~~~~~~~
1853 * Up::
57bdf034 1854 * Move cursor one line up.
468876c9
JF
1855 * Down::
1856 * Move cursor one line down.
1857 * k::
57bdf034 1858 * Move cursor one line up and enter. When used in the main view
468876c9
JF
1859 * this will always show the diff of the current commit in the
1860 * split diff view.
1861 * j::
1862 * Move cursor one line down and enter.
1863 * PgUp::
c622eefa 1864 * b::
0df811cb 1865 * -::
57bdf034 1866 * Move cursor one page up.
468876c9 1867 * PgDown::
c622eefa 1868 * Space::
468876c9
JF
1869 * Move cursor one page down.
1870 * Home::
1871 * Jump to first line.
1872 * End::
1873 * Jump to last line.
1874 **/
1875 { KEY_UP, REQ_MOVE_UP },
1876 { KEY_DOWN, REQ_MOVE_DOWN },
1877 { 'k', REQ_MOVE_UP_ENTER },
1878 { 'j', REQ_MOVE_DOWN_ENTER },
1879 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1880 { KEY_END, REQ_MOVE_LAST_LINE },
1881 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
c622eefa 1882 { ' ', REQ_MOVE_PAGE_DOWN },
468876c9 1883 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
c622eefa 1884 { 'b', REQ_MOVE_PAGE_UP },
0df811cb 1885 { '-', REQ_MOVE_PAGE_UP },
468876c9
JF
1886
1887 /**
1888 * Scrolling
1889 * ~~~~~~~~~
1890 * Insert::
1891 * Scroll view one line up.
1892 * Delete::
1893 * Scroll view one line down.
1894 * w::
1895 * Scroll view one page up.
1896 * s::
1897 * Scroll view one page down.
1898 **/
1899 { KEY_IC, REQ_SCROLL_LINE_UP },
1900 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1901 { 'w', REQ_SCROLL_PAGE_UP },
1902 { 's', REQ_SCROLL_PAGE_DOWN },
1903
1904 /**
1905 * Misc
1906 * ~~~~
d9d1c722
JF
1907 * Q::
1908 * Quit.
468876c9
JF
1909 * r::
1910 * Redraw screen.
1911 * z::
1912 * Stop all background loading. This can be useful if you use
1913 * tig(1) in a repository with a long history without limiting
0721c53a 1914 * the revision log.
468876c9
JF
1915 * v::
1916 * Show version.
1917 * n::
1918 * Toggle line numbers on/off.
1919 * ':'::
1920 * Open prompt. This allows you to specify what git command
1921 * to run. Example:
1922 *
1923 * :log -p
1924 **/
d9d1c722 1925 { 'Q', REQ_QUIT },
468876c9
JF
1926 { 'z', REQ_STOP_LOADING },
1927 { 'v', REQ_SHOW_VERSION },
1928 { 'r', REQ_SCREEN_REDRAW },
1929 { 'n', REQ_TOGGLE_LINE_NUMBERS },
1930 { ':', REQ_PROMPT },
1931
1932 /* wgetch() with nodelay() enabled returns ERR when there's no input. */
1933 { ERR, REQ_SCREEN_UPDATE },
1934
1935 /* Use the ncurses SIGWINCH handler. */
1936 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1937};
1938
1939static enum request
1940get_request(int key)
1941{
1942 int i;
1943
1944 for (i = 0; i < ARRAY_SIZE(keymap); i++)
1945 if (keymap[i].alias == key)
1946 return keymap[i].request;
1947
1948 return (enum request) key;
1949}
1950
1951
6b161b31 1952/*
10e290ee
JF
1953 * Unicode / UTF-8 handling
1954 *
1955 * NOTE: Much of the following code for dealing with unicode is derived from
1956 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
1957 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
1958 */
1959
1960/* I've (over)annotated a lot of code snippets because I am not entirely
1961 * confident that the approach taken by this small UTF-8 interface is correct.
1962 * --jonas */
1963
1964static inline int
1965unicode_width(unsigned long c)
1966{
1967 if (c >= 0x1100 &&
1968 (c <= 0x115f /* Hangul Jamo */
1969 || c == 0x2329
1970 || c == 0x232a
1971 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
1972 /* CJK ... Yi */
1973 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
1974 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
1975 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
1976 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
1977 || (c >= 0xffe0 && c <= 0xffe6)
1978 || (c >= 0x20000 && c <= 0x2fffd)
1979 || (c >= 0x30000 && c <= 0x3fffd)))
1980 return 2;
1981
1982 return 1;
1983}
1984
1985/* Number of bytes used for encoding a UTF-8 character indexed by first byte.
1986 * Illegal bytes are set one. */
1987static const unsigned char utf8_bytes[256] = {
1988 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,
1989 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,
1990 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,
1991 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,
1992 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,
1993 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,
1994 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,
1995 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,
1996};
1997
1998/* Decode UTF-8 multi-byte representation into a unicode character. */
1999static inline unsigned long
2000utf8_to_unicode(const char *string, size_t length)
2001{
2002 unsigned long unicode;
2003
2004 switch (length) {
2005 case 1:
2006 unicode = string[0];
2007 break;
2008 case 2:
2009 unicode = (string[0] & 0x1f) << 6;
2010 unicode += (string[1] & 0x3f);
2011 break;
2012 case 3:
2013 unicode = (string[0] & 0x0f) << 12;
2014 unicode += ((string[1] & 0x3f) << 6);
2015 unicode += (string[2] & 0x3f);
2016 break;
2017 case 4:
2018 unicode = (string[0] & 0x0f) << 18;
2019 unicode += ((string[1] & 0x3f) << 12);
2020 unicode += ((string[2] & 0x3f) << 6);
2021 unicode += (string[3] & 0x3f);
2022 break;
2023 case 5:
2024 unicode = (string[0] & 0x0f) << 24;
2025 unicode += ((string[1] & 0x3f) << 18);
2026 unicode += ((string[2] & 0x3f) << 12);
2027 unicode += ((string[3] & 0x3f) << 6);
2028 unicode += (string[4] & 0x3f);
2029 break;
68b6e0eb 2030 case 6:
10e290ee
JF
2031 unicode = (string[0] & 0x01) << 30;
2032 unicode += ((string[1] & 0x3f) << 24);
2033 unicode += ((string[2] & 0x3f) << 18);
2034 unicode += ((string[3] & 0x3f) << 12);
2035 unicode += ((string[4] & 0x3f) << 6);
2036 unicode += (string[5] & 0x3f);
2037 break;
2038 default:
2039 die("Invalid unicode length");
2040 }
2041
2042 /* Invalid characters could return the special 0xfffd value but NUL
2043 * should be just as good. */
2044 return unicode > 0xffff ? 0 : unicode;
2045}
2046
2047/* Calculates how much of string can be shown within the given maximum width
2048 * and sets trimmed parameter to non-zero value if all of string could not be
2049 * shown.
2050 *
2051 * Additionally, adds to coloffset how many many columns to move to align with
2052 * the expected position. Takes into account how multi-byte and double-width
2053 * characters will effect the cursor position.
2054 *
2055 * Returns the number of bytes to output from string to satisfy max_width. */
2056static size_t
2057utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2058{
2059 const char *start = string;
2060 const char *end = strchr(string, '\0');
2061 size_t mbwidth = 0;
2062 size_t width = 0;
2063
2064 *trimmed = 0;
2065
2066 while (string < end) {
2067 int c = *(unsigned char *) string;
2068 unsigned char bytes = utf8_bytes[c];
2069 size_t ucwidth;
2070 unsigned long unicode;
2071
2072 if (string + bytes > end)
2073 break;
2074
2075 /* Change representation to figure out whether
2076 * it is a single- or double-width character. */
2077
2078 unicode = utf8_to_unicode(string, bytes);
2079 /* FIXME: Graceful handling of invalid unicode character. */
2080 if (!unicode)
2081 break;
2082
2083 ucwidth = unicode_width(unicode);
2084 width += ucwidth;
2085 if (width > max_width) {
2086 *trimmed = 1;
2087 break;
2088 }
2089
2090 /* The column offset collects the differences between the
2091 * number of bytes encoding a character and the number of
2092 * columns will be used for rendering said character.
2093 *
2094 * So if some character A is encoded in 2 bytes, but will be
2095 * represented on the screen using only 1 byte this will and up
2096 * adding 1 to the multi-byte column offset.
2097 *
2098 * Assumes that no double-width character can be encoding in
2099 * less than two bytes. */
2100 if (bytes > ucwidth)
2101 mbwidth += bytes - ucwidth;
2102
2103 string += bytes;
2104 }
2105
2106 *coloffset += mbwidth;
2107
2108 return string - start;
2109}
2110
2111
2112/*
6b161b31
JF
2113 * Status management
2114 */
2e8488b4 2115
8855ada4 2116/* Whether or not the curses interface has been initialized. */
68b6e0eb 2117static bool cursed = FALSE;
8855ada4 2118
6b161b31
JF
2119/* The status window is used for polling keystrokes. */
2120static WINDOW *status_win;
4a2909a7 2121
2e8488b4 2122/* Update status and title window. */
4a2909a7
JF
2123static void
2124report(const char *msg, ...)
2125{
6706b2ba
JF
2126 static bool empty = TRUE;
2127 struct view *view = display[current_view];
b76c2afc 2128
6706b2ba
JF
2129 if (!empty || *msg) {
2130 va_list args;
4a2909a7 2131
6706b2ba 2132 va_start(args, msg);
4b76734f 2133
6706b2ba
JF
2134 werase(status_win);
2135 wmove(status_win, 0, 0);
2136 if (*msg) {
2137 vwprintw(status_win, msg, args);
2138 empty = FALSE;
2139 } else {
2140 empty = TRUE;
2141 }
2142 wrefresh(status_win);
b801d8b2 2143
6706b2ba
JF
2144 va_end(args);
2145 }
2146
2147 update_view_title(view);
2148
2149 /* Move the cursor to the right-most column of the cursor line.
2150 *
2151 * XXX: This could turn out to be a bit expensive, but it ensures that
2152 * the cursor does not jump around. */
2153 if (view->lines) {
2154 wmove(view->win, view->lineno - view->offset, view->width - 1);
2155 wrefresh(view->win);
2156 }
b801d8b2
JF
2157}
2158
6b161b31
JF
2159/* Controls when nodelay should be in effect when polling user input. */
2160static void
1ba2ae4b 2161set_nonblocking_input(bool loading)
b801d8b2 2162{
6706b2ba 2163 static unsigned int loading_views;
b801d8b2 2164
6706b2ba
JF
2165 if ((loading == FALSE && loading_views-- == 1) ||
2166 (loading == TRUE && loading_views++ == 0))
1ba2ae4b 2167 nodelay(status_win, loading);
6b161b31
JF
2168}
2169
2170static void
2171init_display(void)
2172{
2173 int x, y;
b76c2afc 2174
6908bdbd
JF
2175 /* Initialize the curses library */
2176 if (isatty(STDIN_FILENO)) {
8855ada4 2177 cursed = !!initscr();
6908bdbd
JF
2178 } else {
2179 /* Leave stdin and stdout alone when acting as a pager. */
2180 FILE *io = fopen("/dev/tty", "r+");
2181
8855ada4 2182 cursed = !!newterm(NULL, io, io);
6908bdbd
JF
2183 }
2184
8855ada4
JF
2185 if (!cursed)
2186 die("Failed to initialize curses");
2187
2e8488b4
JF
2188 nonl(); /* Tell curses not to do NL->CR/NL on output */
2189 cbreak(); /* Take input chars one at a time, no wait for \n */
2190 noecho(); /* Don't echo input */
b801d8b2 2191 leaveok(stdscr, TRUE);
b801d8b2
JF
2192
2193 if (has_colors())
2194 init_colors();
2195
2196 getmaxyx(stdscr, y, x);
2197 status_win = newwin(1, 0, y - 1, 0);
2198 if (!status_win)
2199 die("Failed to create status window");
2200
2201 /* Enable keyboard mapping */
2202 keypad(status_win, TRUE);
78c70acd 2203 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6b161b31
JF
2204}
2205
c34d9c9f
JF
2206
2207/*
2208 * Repository references
2209 */
2210
2211static struct ref *refs;
3a91b75e 2212static size_t refs_size;
c34d9c9f
JF
2213
2214static struct ref **
2215get_refs(char *id)
2216{
2217 struct ref **id_refs = NULL;
2218 size_t id_refs_size = 0;
2219 size_t i;
2220
2221 for (i = 0; i < refs_size; i++) {
2222 struct ref **tmp;
2223
2224 if (strcmp(id, refs[i].id))
2225 continue;
2226
2227 tmp = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2228 if (!tmp) {
2229 if (id_refs)
2230 free(id_refs);
2231 return NULL;
2232 }
2233
2234 id_refs = tmp;
3af8774e 2235 if (id_refs_size > 0)
c34d9c9f 2236 id_refs[id_refs_size - 1]->next = 1;
3af8774e
JF
2237 id_refs[id_refs_size] = &refs[i];
2238
2239 /* XXX: The properties of the commit chains ensures that we can
2240 * safely modify the shared ref. The repo references will
2241 * always be similar for the same id. */
2242 id_refs[id_refs_size]->next = 0;
2243 id_refs_size++;
c34d9c9f
JF
2244 }
2245
2246 return id_refs;
2247}
2248
2249static int
2250load_refs(void)
2251{
4685845e
TH
2252 const char *cmd_env = getenv("TIG_LS_REMOTE");
2253 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
c34d9c9f
JF
2254 FILE *pipe = popen(cmd, "r");
2255 char buffer[BUFSIZ];
2256 char *line;
2257
2258 if (!pipe)
2259 return ERR;
2260
2261 while ((line = fgets(buffer, sizeof(buffer), pipe))) {
2262 char *name = strchr(line, '\t');
2263 struct ref *ref;
2264 int namelen;
2265 bool tag = FALSE;
6734f6b9 2266 bool tag_commit = FALSE;
c34d9c9f
JF
2267
2268 if (!name)
2269 continue;
2270
2271 *name++ = 0;
2272 namelen = strlen(name) - 1;
6706b2ba
JF
2273
2274 /* Commits referenced by tags has "^{}" appended. */
c34d9c9f
JF
2275 if (name[namelen - 1] == '}') {
2276 while (namelen > 0 && name[namelen] != '^')
2277 namelen--;
6734f6b9
JF
2278 if (namelen > 0)
2279 tag_commit = TRUE;
c34d9c9f
JF
2280 }
2281 name[namelen] = 0;
2282
2283 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
6734f6b9
JF
2284 if (!tag_commit)
2285 continue;
c34d9c9f
JF
2286 name += STRING_SIZE("refs/tags/");
2287 tag = TRUE;
3af8774e
JF
2288
2289 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2290 name += STRING_SIZE("refs/heads/");
2291
2292 } else if (!strcmp(name, "HEAD")) {
2293 continue;
c34d9c9f
JF
2294 }
2295
2296 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2297 if (!refs)
2298 return ERR;
2299
2300 ref = &refs[refs_size++];
2301 ref->tag = tag;
2302 ref->name = strdup(name);
2303 if (!ref->name)
2304 return ERR;
2305
2306 string_copy(ref->id, line);
2307 }
2308
2309 if (ferror(pipe))
2310 return ERR;
2311
2312 pclose(pipe);
2313
1b4b0bd9
JF
2314 if (refs_size == 0)
2315 die("Not a git repository");
2316
c34d9c9f
JF
2317 return OK;
2318}
2319
6b161b31
JF
2320/*
2321 * Main
2322 */
2323
b5c9e67f
TH
2324#if __GNUC__ >= 3
2325#define __NORETURN __attribute__((__noreturn__))
2326#else
2327#define __NORETURN
2328#endif
2329
2330static void __NORETURN
6b161b31
JF
2331quit(int sig)
2332{
8855ada4
JF
2333 /* XXX: Restore tty modes and let the OS cleanup the rest! */
2334 if (cursed)
2335 endwin();
6b161b31
JF
2336 exit(0);
2337}
2338
c6704a4e
JF
2339static void __NORETURN
2340die(const char *err, ...)
6b161b31
JF
2341{
2342 va_list args;
2343
2344 endwin();
2345
2346 va_start(args, err);
2347 fputs("tig: ", stderr);
2348 vfprintf(stderr, err, args);
2349 fputs("\n", stderr);
2350 va_end(args);
2351
2352 exit(1);
2353}
2354
2355int
2356main(int argc, char *argv[])
2357{
1ba2ae4b 2358 struct view *view;
6b161b31 2359 enum request request;
1ba2ae4b 2360 size_t i;
6b161b31
JF
2361
2362 signal(SIGINT, quit);
2363
8855ada4 2364 if (!parse_options(argc, argv))
6b161b31
JF
2365 return 0;
2366
c34d9c9f
JF
2367 if (load_refs() == ERR)
2368 die("Failed to load refs.");
2369
1ba2ae4b
JF
2370 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2371 view->cmd_env = getenv(view->cmd_env);
2372
6b161b31
JF
2373 request = opt_request;
2374
2375 init_display();
b801d8b2
JF
2376
2377 while (view_driver(display[current_view], request)) {
6b161b31 2378 int key;
b801d8b2
JF
2379 int i;
2380
6b161b31
JF
2381 foreach_view (view, i)
2382 update_view(view);
b801d8b2
JF
2383
2384 /* Refresh, accept single keystroke of input */
6b161b31
JF
2385 key = wgetch(status_win);
2386 request = get_request(key);
03a93dbb 2387
6706b2ba 2388 /* Some low-level request handling. This keeps access to
fac7db6c
JF
2389 * status_win restricted. */
2390 switch (request) {
2391 case REQ_PROMPT:
6908bdbd
JF
2392 report(":");
2393 /* Temporarily switch to line-oriented and echoed
2394 * input. */
03a93dbb
JF
2395 nocbreak();
2396 echo();
49f2b43f
JF
2397
2398 if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2399 memcpy(opt_cmd, "git ", 4);
2400 opt_request = REQ_VIEW_PAGER;
2401 } else {
2402 request = ERR;
2403 }
2404
6908bdbd
JF
2405 noecho();
2406 cbreak();
fac7db6c
JF
2407 break;
2408
2409 case REQ_SCREEN_RESIZE:
2410 {
2411 int height, width;
2412
2413 getmaxyx(stdscr, height, width);
2414
2415 /* Resize the status view and let the view driver take
2416 * care of resizing the displayed views. */
2417 wresize(status_win, 1, width);
2418 mvwin(status_win, height - 1, 0);
2419 wrefresh(status_win);
2420 break;
2421 }
2422 default:
2423 break;
03a93dbb 2424 }
b801d8b2
JF
2425 }
2426
2427 quit(0);
2428
2429 return 0;
2430}
2431
2432/**
0721c53a 2433 * [[refspec]]
3a11b38f
JF
2434 * Revision specification
2435 * ----------------------
0721c53a 2436 * This section describes various ways to specify what revisions to display
3a11b38f
JF
2437 * or otherwise limit the view to. tig(1) does not itself parse the described
2438 * revision options so refer to the relevant git man pages for futher
2439 * information. Relevant man pages besides git-log(1) are git-diff(1) and
2440 * git-rev-list(1).
468876c9 2441 *
3a11b38f
JF
2442 * You can tune the interaction with git by making use of the options
2443 * explained in this section. For example, by configuring the environment
2444 * variables described in the <<view-commands, "View commands">> section.
2445 *
2446 * Limit by path name
2447 * ~~~~~~~~~~~~~~~~~~
ab46037d
JF
2448 * If you are interested only in those revisions that made changes to a
2449 * specific file (or even several files) list the files like this:
2450 *
c760e6ba 2451 * $ tig log Makefile README
ab46037d
JF
2452 *
2453 * To avoid ambiguity with repository references such as tag name, be sure
2454 * to separate file names from other git options using "\--". So if you
2455 * have a file named 'master' it will clash with the reference named
2456 * 'master', and thus you will have to use:
2457 *
1b4b0bd9 2458 * $ tig log -- master
ab46037d
JF
2459 *
2460 * NOTE: For the main view, avoiding ambiguity will in some cases require
2461 * you to specify two "\--" options. The first will make tig(1) stop
2462 * option processing and the latter will be passed to git log.
2463 *
468876c9
JF
2464 * Limit by date or number
2465 * ~~~~~~~~~~~~~~~~~~~~~~~
2466 * To speed up interaction with git, you can limit the amount of commits
2467 * to show both for the log and main view. Either limit by date using
2468 * e.g. `--since=1.month` or limit by the number of commits using `-n400`.
2469 *
3a11b38f
JF
2470 * If you are only interested in changed that happened between two dates
2471 * you can use:
2472 *
c760e6ba 2473 * $ tig -- --after="May 5th" --before="2006-05-16 15:44"
468876c9 2474 *
c760e6ba
JF
2475 * NOTE: If you want to avoid having to quote dates containing spaces you
2476 * can use "." instead, e.g. `--after=May.5th`.
3a11b38f
JF
2477 *
2478 * Limiting by commit ranges
2479 * ~~~~~~~~~~~~~~~~~~~~~~~~~
468876c9
JF
2480 * Alternatively, commits can be limited to a specific range, such as
2481 * "all commits between 'tag-1.0' and 'tag-2.0'". For example:
2482 *
2483 * $ tig log tag-1.0..tag-2.0
2484 *
2485 * This way of commit limiting makes it trivial to only browse the commits
2486 * which haven't been pushed to a remote branch. Assuming 'origin' is your
2487 * upstream remote branch, using:
2488 *
2489 * $ tig log origin..HEAD
2490 *
2491 * will list what will be pushed to the remote branch. Optionally, the ending
2492 * 'HEAD' can be left out since it is implied.
2493 *
2494 * Limiting by reachability
2495 * ~~~~~~~~~~~~~~~~~~~~~~~~
2496 * Git interprets the range specifier "tag-1.0..tag-2.0" as
2497 * "all commits reachable from 'tag-2.0' but not from 'tag-1.0'".
3a11b38f
JF
2498 * Where reachability refers to what commits are ancestors (or part of the
2499 * history) of the branch or tagged revision in question.
2500 *
468876c9
JF
2501 * If you prefer to specify which commit to preview in this way use the
2502 * following:
2503 *
2504 * $ tig log tag-2.0 ^tag-1.0
2505 *
57bdf034
JF
2506 * You can think of '^' as a negation operator. Using this alternate syntax,
2507 * it is possible to further prune commits by specifying multiple branch
2508 * cut offs.
468876c9 2509 *
3a11b38f
JF
2510 * Combining revisions specification
2511 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2512 * Revisions options can to some degree be combined, which makes it possible
2513 * to say "show at most 20 commits from within the last month that changed
2514 * files under the Documentation/ directory."
2515 *
2516 * $ tig -- --since=1.month -n20 -- Documentation/
2517 *
2518 * Examining all repository references
2519 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2520 * In some cases, it can be useful to query changes across all references
2521 * in a repository. An example is to ask "did any line of development in
2522 * this repository change a particular file within the last week". This
2523 * can be accomplished using:
2524 *
2525 * $ tig -- --all --since=1.week -- Makefile
2526 *
6706b2ba
JF
2527 * BUGS
2528 * ----
2529 * Known bugs and problems:
2530 *
10e290ee
JF
2531 * - In it's current state tig is pretty much UTF-8 only.
2532 *
6706b2ba 2533 * - If the screen width is very small the main view can draw
468876c9
JF
2534 * outside the current view causing bad wrapping. Same goes
2535 * for title and status windows.
6706b2ba 2536 *
4c6fabc2
JF
2537 * TODO
2538 * ----
2539 * Features that should be explored.
2540 *
fac7db6c 2541 * - Searching.
4c6fabc2
JF
2542 *
2543 * - Locale support.
2544 *
b801d8b2
JF
2545 * COPYRIGHT
2546 * ---------
4a2909a7 2547 * Copyright (c) Jonas Fonseca <fonseca@diku.dk>, 2006
b801d8b2
JF
2548 *
2549 * This program is free software; you can redistribute it and/or modify
2550 * it under the terms of the GNU General Public License as published by
2551 * the Free Software Foundation; either version 2 of the License, or
2552 * (at your option) any later version.
2553 *
2554 * SEE ALSO
2555 * --------
4c6fabc2 2556 * [verse]
b801d8b2
JF
2557 * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
2558 * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
4c6fabc2 2559 * gitk(1): git repository browser written using tcl/tk,
c6704a4e 2560 * qgit(1): git repository browser written using c++/Qt,
4c6fabc2 2561 * gitview(1): git repository browser written using python/gtk.
b801d8b2 2562 **/