Add -O2 to CFLAGS to get more warnings
[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);
4f774851 1263 int nviews = display[1] ? 2 : 1;
b801d8b2 1264
49f2b43f 1265 if (view == prev && nviews == 1 && !reload) {
6b161b31
JF
1266 report("Already in %s view", view->name);
1267 return;
1268 }
b801d8b2 1269
8855ada4 1270 if ((reload || strcmp(view->vid, view->id)) &&
03a93dbb 1271 !begin_update(view)) {
6b161b31
JF
1272 report("Failed to load %s view", view->name);
1273 return;
1274 }
a28bcc22 1275
6b161b31
JF
1276 if (split) {
1277 display[current_view + 1] = view;
1278 if (!backgrounded)
a28bcc22 1279 current_view++;
6b161b31
JF
1280 } else {
1281 /* Maximize the current view. */
1282 memset(display, 0, sizeof(display));
1283 current_view = 0;
1284 display[current_view] = view;
a28bcc22 1285 }
b801d8b2 1286
6b161b31 1287 resize_display();
b801d8b2 1288
a8891802 1289 if (split && prev->lineno - prev->offset >= prev->height) {
03a93dbb 1290 /* Take the title line into account. */
eb98559e 1291 int lines = prev->lineno - prev->offset - prev->height + 1;
03a93dbb
JF
1292
1293 /* Scroll the view that was split if the current line is
1294 * outside the new limited view. */
1295 do_scroll_view(prev, lines);
1296 }
1297
6b161b31
JF
1298 if (prev && view != prev) {
1299 /* "Blur" the previous view. */
6706b2ba
JF
1300 if (!backgrounded)
1301 update_view_title(prev);
6b161b31
JF
1302
1303 /* Continue loading split views in the background. */
03a93dbb 1304 if (!split)
6b161b31 1305 end_update(prev);
f6da0b66 1306 view->parent = prev;
b801d8b2
JF
1307 }
1308
03a93dbb
JF
1309 if (view->pipe) {
1310 /* Clear the old view and let the incremental updating refill
1311 * the screen. */
1312 wclear(view->win);
1313 report("Loading...");
1314 } else {
1315 redraw_view(view);
468876c9
JF
1316 if (view == VIEW(REQ_VIEW_HELP))
1317 report("%s", TIG_HELP);
1318 else
1319 report("");
03a93dbb 1320 }
6706b2ba
JF
1321
1322 /* If the view is backgrounded the above calls to report()
1323 * won't redraw the view title. */
1324 if (backgrounded)
1325 update_view_title(view);
b801d8b2
JF
1326}
1327
1328
6b161b31
JF
1329/*
1330 * User request switch noodle
1331 */
1332
b801d8b2 1333static int
6b161b31 1334view_driver(struct view *view, enum request request)
b801d8b2 1335{
b801d8b2
JF
1336 int i;
1337
1338 switch (request) {
4a2909a7
JF
1339 case REQ_MOVE_UP:
1340 case REQ_MOVE_DOWN:
1341 case REQ_MOVE_PAGE_UP:
1342 case REQ_MOVE_PAGE_DOWN:
1343 case REQ_MOVE_FIRST_LINE:
1344 case REQ_MOVE_LAST_LINE:
a28bcc22 1345 move_view(view, request);
fd85fef1
JF
1346 break;
1347
4a2909a7
JF
1348 case REQ_SCROLL_LINE_DOWN:
1349 case REQ_SCROLL_LINE_UP:
1350 case REQ_SCROLL_PAGE_DOWN:
1351 case REQ_SCROLL_PAGE_UP:
a28bcc22 1352 scroll_view(view, request);
b801d8b2
JF
1353 break;
1354
4a2909a7 1355 case REQ_VIEW_MAIN:
4a2909a7 1356 case REQ_VIEW_DIFF:
2e8488b4
JF
1357 case REQ_VIEW_LOG:
1358 case REQ_VIEW_HELP:
6908bdbd 1359 case REQ_VIEW_PAGER:
49f2b43f 1360 open_view(view, request, OPEN_DEFAULT);
b801d8b2
JF
1361 break;
1362
6706b2ba
JF
1363 case REQ_MOVE_UP_ENTER:
1364 case REQ_MOVE_DOWN_ENTER:
1365 move_view(view, request);
1366 /* Fall-through */
1367
6b161b31 1368 case REQ_ENTER:
6908bdbd
JF
1369 if (!view->lines) {
1370 report("Nothing to enter");
1371 break;
1372 }
6b161b31
JF
1373 return view->ops->enter(view);
1374
03a93dbb
JF
1375 case REQ_VIEW_NEXT:
1376 {
1377 int nviews = display[1] ? 2 : 1;
1378 int next_view = (current_view + 1) % nviews;
1379
1380 if (next_view == current_view) {
1381 report("Only one view is displayed");
1382 break;
1383 }
1384
1385 current_view = next_view;
1386 /* Blur out the title of the previous view. */
1387 update_view_title(view);
6734f6b9 1388 report("");
03a93dbb
JF
1389 break;
1390 }
4a2909a7 1391 case REQ_TOGGLE_LINE_NUMBERS:
b76c2afc 1392 opt_line_number = !opt_line_number;
20bb5e18 1393 redraw_display();
b801d8b2
JF
1394 break;
1395
03a93dbb 1396 case REQ_PROMPT:
8855ada4 1397 /* Always reload^Wrerun commands from the prompt. */
49f2b43f 1398 open_view(view, opt_request, OPEN_RELOAD);
03a93dbb
JF
1399 break;
1400
4a2909a7 1401 case REQ_STOP_LOADING:
03a93dbb 1402 foreach_view (view, i) {
2e8488b4 1403 if (view->pipe)
6706b2ba 1404 report("Stopped loaded the %s view", view->name),
03a93dbb
JF
1405 end_update(view);
1406 }
b801d8b2
JF
1407 break;
1408
4a2909a7 1409 case REQ_SHOW_VERSION:
6cb291b7 1410 report("%s (built %s)", VERSION, __DATE__);
b801d8b2
JF
1411 return TRUE;
1412
fac7db6c
JF
1413 case REQ_SCREEN_RESIZE:
1414 resize_display();
1415 /* Fall-through */
4a2909a7 1416 case REQ_SCREEN_REDRAW:
20bb5e18 1417 redraw_display();
4a2909a7
JF
1418 break;
1419
1420 case REQ_SCREEN_UPDATE:
b801d8b2
JF
1421 doupdate();
1422 return TRUE;
1423
4f9b667a 1424 case REQ_VIEW_CLOSE:
f6da0b66 1425 if (view->parent) {
4f9b667a
JF
1426 memset(display, 0, sizeof(display));
1427 current_view = 0;
f6da0b66
JF
1428 display[current_view] = view->parent;
1429 view->parent = NULL;
4f9b667a
JF
1430 resize_display();
1431 redraw_display();
1432 break;
1433 }
1434 /* Fall-through */
b801d8b2
JF
1435 case REQ_QUIT:
1436 return FALSE;
1437
1438 default:
2e8488b4 1439 /* An unknown key will show most commonly used commands. */
468876c9 1440 report("Unknown key, press 'h' for help");
b801d8b2
JF
1441 return TRUE;
1442 }
1443
1444 return TRUE;
1445}
1446
1447
1448/*
6b161b31 1449 * View backend handlers
b801d8b2
JF
1450 */
1451
6b161b31 1452static bool
22f66b0a 1453pager_draw(struct view *view, unsigned int lineno)
b801d8b2 1454{
78c70acd 1455 enum line_type type;
b801d8b2 1456 char *line;
4c6fabc2 1457 int linelen;
78c70acd 1458 int attr;
b801d8b2 1459
fd85fef1
JF
1460 if (view->offset + lineno >= view->lines)
1461 return FALSE;
1462
b801d8b2 1463 line = view->line[view->offset + lineno];
78c70acd 1464 type = get_line_type(line);
b801d8b2 1465
6706b2ba
JF
1466 wmove(view->win, lineno, 0);
1467
fd85fef1 1468 if (view->offset + lineno == view->lineno) {
8855ada4 1469 if (type == LINE_COMMIT) {
03a93dbb
JF
1470 string_copy(view->ref, line + 7);
1471 string_copy(ref_commit, view->ref);
1472 }
8855ada4 1473
78c70acd 1474 type = LINE_CURSOR;
6706b2ba 1475 wchgat(view->win, -1, 0, type, NULL);
fd85fef1
JF
1476 }
1477
78c70acd 1478 attr = get_line_attr(type);
b801d8b2 1479 wattrset(view->win, attr);
b76c2afc 1480
4c6fabc2 1481 linelen = strlen(line);
4c6fabc2 1482
6706b2ba
JF
1483 if (opt_line_number || opt_tab_size < TABSIZE) {
1484 static char spaces[] = " ";
1485 int col_offset = 0, col = 0;
1486
1487 if (opt_line_number) {
1488 unsigned long real_lineno = view->offset + lineno + 1;
82e78006 1489
6706b2ba
JF
1490 if (real_lineno == 1 ||
1491 (real_lineno % opt_num_interval) == 0) {
1492 wprintw(view->win, "%.*d", view->digits, real_lineno);
8855ada4 1493
6706b2ba
JF
1494 } else {
1495 waddnstr(view->win, spaces,
1496 MIN(view->digits, STRING_SIZE(spaces)));
1497 }
1498 waddstr(view->win, ": ");
1499 col_offset = view->digits + 2;
1500 }
8855ada4 1501
6706b2ba
JF
1502 while (line && col_offset + col < view->width) {
1503 int cols_max = view->width - col_offset - col;
1504 char *text = line;
1505 int cols;
4c6fabc2 1506
b76c2afc 1507 if (*line == '\t') {
6706b2ba 1508 assert(sizeof(spaces) > TABSIZE);
b76c2afc 1509 line++;
6706b2ba
JF
1510 text = spaces;
1511 cols = opt_tab_size - (col % opt_tab_size);
82e78006 1512
b76c2afc 1513 } else {
6706b2ba
JF
1514 line = strchr(line, '\t');
1515 cols = line ? line - text : strlen(text);
b76c2afc 1516 }
6706b2ba
JF
1517
1518 waddnstr(view->win, text, MIN(cols, cols_max));
1519 col += cols;
b76c2afc 1520 }
b76c2afc
JF
1521
1522 } else {
6706b2ba 1523 int col = 0, pos = 0;
b801d8b2 1524
6706b2ba
JF
1525 for (; pos < linelen && col < view->width; pos++, col++)
1526 if (line[pos] == '\t')
1527 col += TABSIZE - (col % TABSIZE) - 1;
1528
1529 waddnstr(view->win, line, pos);
1530 }
2e8488b4 1531
b801d8b2
JF
1532 return TRUE;
1533}
1534
6b161b31 1535static bool
22f66b0a
JF
1536pager_read(struct view *view, char *line)
1537{
6706b2ba
JF
1538 /* Compress empty lines in the help view. */
1539 if (view == VIEW(REQ_VIEW_HELP) &&
1540 !*line &&
1541 view->lines &&
1542 !*((char *) view->line[view->lines - 1]))
1543 return TRUE;
1544
22f66b0a
JF
1545 view->line[view->lines] = strdup(line);
1546 if (!view->line[view->lines])
1547 return FALSE;
1548
1549 view->lines++;
1550 return TRUE;
1551}
1552
6b161b31
JF
1553static bool
1554pager_enter(struct view *view)
1555{
1556 char *line = view->line[view->lineno];
91e8e277 1557 int split = 0;
6b161b31 1558
91e8e277 1559 if ((view == VIEW(REQ_VIEW_LOG) ||
415de53c 1560 view == VIEW(REQ_VIEW_PAGER)) &&
91e8e277
JF
1561 get_line_type(line) == LINE_COMMIT) {
1562 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1563 split = 1;
67e48ac5
JF
1564 }
1565
91e8e277
JF
1566 /* Always scroll the view even if it was split. That way
1567 * you can use Enter to scroll through the log view and
1568 * split open each commit diff. */
1569 scroll_view(view, REQ_SCROLL_LINE_DOWN);
1570
1571 /* FIXME: A minor workaround. Scrolling the view will call report("")
1572 * but if we are scolling a non-current view this won't properly update
1573 * the view title. */
1574 if (split)
1575 update_view_title(view);
6b161b31
JF
1576
1577 return TRUE;
1578}
1579
6b161b31 1580static struct view_ops pager_ops = {
6734f6b9 1581 "line",
6b161b31
JF
1582 pager_draw,
1583 pager_read,
1584 pager_enter,
1585};
1586
80ce96ea 1587
c34d9c9f
JF
1588static struct ref **get_refs(char *id);
1589
6b161b31 1590static bool
22f66b0a
JF
1591main_draw(struct view *view, unsigned int lineno)
1592{
2e8488b4 1593 char buf[DATE_COLS + 1];
22f66b0a 1594 struct commit *commit;
78c70acd 1595 enum line_type type;
6706b2ba 1596 int col = 0;
b76c2afc 1597 size_t timelen;
10e290ee
JF
1598 size_t authorlen;
1599 int trimmed;
22f66b0a
JF
1600
1601 if (view->offset + lineno >= view->lines)
1602 return FALSE;
1603
1604 commit = view->line[view->offset + lineno];
4c6fabc2
JF
1605 if (!*commit->author)
1606 return FALSE;
22f66b0a 1607
6706b2ba
JF
1608 wmove(view->win, lineno, col);
1609
22f66b0a 1610 if (view->offset + lineno == view->lineno) {
03a93dbb 1611 string_copy(view->ref, commit->id);
49f2b43f 1612 string_copy(ref_commit, view->ref);
78c70acd 1613 type = LINE_CURSOR;
6706b2ba
JF
1614 wattrset(view->win, get_line_attr(type));
1615 wchgat(view->win, -1, 0, type, NULL);
1616
78c70acd 1617 } else {
b76c2afc 1618 type = LINE_MAIN_COMMIT;
6706b2ba 1619 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
b76c2afc
JF
1620 }
1621
4c6fabc2 1622 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
b76c2afc 1623 waddnstr(view->win, buf, timelen);
4c6fabc2 1624 waddstr(view->win, " ");
b76c2afc 1625
6706b2ba
JF
1626 col += DATE_COLS;
1627 wmove(view->win, lineno, col);
1628 if (type != LINE_CURSOR)
1629 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
b76c2afc 1630
10e290ee
JF
1631 /* FIXME: Make this optional, and add i18n.commitEncoding support. */
1632 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
1633
1634 if (trimmed) {
1635 waddnstr(view->win, commit->author, authorlen);
6706b2ba
JF
1636 if (type != LINE_CURSOR)
1637 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
b76c2afc
JF
1638 waddch(view->win, '~');
1639 } else {
1640 waddstr(view->win, commit->author);
22f66b0a
JF
1641 }
1642
10e290ee 1643 col += AUTHOR_COLS;
6706b2ba
JF
1644 if (type != LINE_CURSOR)
1645 wattrset(view->win, A_NORMAL);
1646
1647 mvwaddch(view->win, lineno, col, ACS_LTEE);
1648 wmove(view->win, lineno, col + 2);
1649 col += 2;
c34d9c9f
JF
1650
1651 if (commit->refs) {
1652 size_t i = 0;
1653
1654 do {
6706b2ba
JF
1655 if (type == LINE_CURSOR)
1656 ;
1657 else if (commit->refs[i]->tag)
c34d9c9f
JF
1658 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
1659 else
1660 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1661 waddstr(view->win, "[");
1662 waddstr(view->win, commit->refs[i]->name);
1663 waddstr(view->win, "]");
6706b2ba
JF
1664 if (type != LINE_CURSOR)
1665 wattrset(view->win, A_NORMAL);
c34d9c9f 1666 waddstr(view->win, " ");
6706b2ba 1667 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
c34d9c9f
JF
1668 } while (commit->refs[i++]->next);
1669 }
1670
6706b2ba
JF
1671 if (type != LINE_CURSOR)
1672 wattrset(view->win, get_line_attr(type));
1673
1674 {
1675 int titlelen = strlen(commit->title);
1676
1677 if (col + titlelen > view->width)
1678 titlelen = view->width - col;
1679
1680 waddnstr(view->win, commit->title, titlelen);
1681 }
22f66b0a
JF
1682
1683 return TRUE;
1684}
1685
4c6fabc2 1686/* Reads git log --pretty=raw output and parses it into the commit struct. */
6b161b31 1687static bool
22f66b0a
JF
1688main_read(struct view *view, char *line)
1689{
78c70acd
JF
1690 enum line_type type = get_line_type(line);
1691 struct commit *commit;
22f66b0a 1692
78c70acd
JF
1693 switch (type) {
1694 case LINE_COMMIT:
22f66b0a
JF
1695 commit = calloc(1, sizeof(struct commit));
1696 if (!commit)
1697 return FALSE;
1698
4c6fabc2 1699 line += STRING_SIZE("commit ");
b76c2afc 1700
22f66b0a 1701 view->line[view->lines++] = commit;
82e78006 1702 string_copy(commit->id, line);
c34d9c9f 1703 commit->refs = get_refs(commit->id);
78c70acd 1704 break;
22f66b0a 1705
8855ada4 1706 case LINE_AUTHOR:
b76c2afc 1707 {
4c6fabc2 1708 char *ident = line + STRING_SIZE("author ");
b76c2afc
JF
1709 char *end = strchr(ident, '<');
1710
1711 if (end) {
1712 for (; end > ident && isspace(end[-1]); end--) ;
1713 *end = 0;
1714 }
1715
1716 commit = view->line[view->lines - 1];
82e78006 1717 string_copy(commit->author, ident);
b76c2afc 1718
4c6fabc2 1719 /* Parse epoch and timezone */
b76c2afc
JF
1720 if (end) {
1721 char *secs = strchr(end + 1, '>');
1722 char *zone;
1723 time_t time;
1724
1725 if (!secs || secs[1] != ' ')
1726 break;
1727
1728 secs += 2;
1729 time = (time_t) atol(secs);
1730 zone = strchr(secs, ' ');
4c6fabc2 1731 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
b76c2afc
JF
1732 long tz;
1733
1734 zone++;
1735 tz = ('0' - zone[1]) * 60 * 60 * 10;
1736 tz += ('0' - zone[2]) * 60 * 60;
1737 tz += ('0' - zone[3]) * 60;
1738 tz += ('0' - zone[4]) * 60;
1739
1740 if (zone[0] == '-')
1741 tz = -tz;
1742
1743 time -= tz;
1744 }
1745 gmtime_r(&time, &commit->time);
1746 }
1747 break;
1748 }
78c70acd 1749 default:
2e8488b4
JF
1750 /* We should only ever end up here if there has already been a
1751 * commit line, however, be safe. */
1752 if (view->lines == 0)
1753 break;
1754
1755 /* Fill in the commit title if it has not already been set. */
78c70acd 1756 commit = view->line[view->lines - 1];
2e8488b4
JF
1757 if (commit->title[0])
1758 break;
1759
1760 /* Require titles to start with a non-space character at the
1761 * offset used by git log. */
eb98559e
JF
1762 /* FIXME: More gracefull handling of titles; append "..." to
1763 * shortened titles, etc. */
2e8488b4 1764 if (strncmp(line, " ", 4) ||
eb98559e 1765 isspace(line[4]))
82e78006
JF
1766 break;
1767
1768 string_copy(commit->title, line + 4);
22f66b0a
JF
1769 }
1770
1771 return TRUE;
1772}
1773
6b161b31
JF
1774static bool
1775main_enter(struct view *view)
b801d8b2 1776{
89e4aafd 1777 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
6b161b31 1778 return TRUE;
b801d8b2
JF
1779}
1780
6b161b31 1781static struct view_ops main_ops = {
6734f6b9 1782 "commit",
6b161b31
JF
1783 main_draw,
1784 main_read,
1785 main_enter,
1786};
2e8488b4 1787
c34d9c9f 1788
468876c9
JF
1789/**
1790 * KEYS
1791 * ----
1792 * Below the default key bindings are shown.
1793 **/
1794
1795struct keymap {
1796 int alias;
1797 int request;
1798};
1799
3a91b75e 1800static struct keymap keymap[] = {
468876c9
JF
1801 /**
1802 * View switching
1803 * ~~~~~~~~~~~~~~
1804 * m::
1805 * Switch to main view.
1806 * d::
1807 * Switch to diff view.
1808 * l::
1809 * Switch to log view.
1810 * p::
1811 * Switch to pager view.
1812 * h::
1813 * Show man page.
4f9b667a 1814 * q::
f6da0b66
JF
1815 * Close view, if multiple views are open it will jump back to the
1816 * previous view in the view stack. If it is the last open view it
1817 * will quit. Use 'Q' to quit all views at once.
67e48ac5
JF
1818 * Enter::
1819 * This key is "context sensitive" depending on what view you are
1820 * currently in. When in log view on a commit line or in the main
1821 * view, split the view and show the commit diff. In the diff view
1822 * pressing Enter will simply scroll the view one line down.
468876c9
JF
1823 * Tab::
1824 * Switch to next view.
1825 **/
1826 { 'm', REQ_VIEW_MAIN },
1827 { 'd', REQ_VIEW_DIFF },
1828 { 'l', REQ_VIEW_LOG },
1829 { 'p', REQ_VIEW_PAGER },
1830 { 'h', REQ_VIEW_HELP },
1831
4f9b667a 1832 { 'q', REQ_VIEW_CLOSE },
468876c9
JF
1833 { KEY_TAB, REQ_VIEW_NEXT },
1834 { KEY_RETURN, REQ_ENTER },
1835
1836 /**
1837 * Cursor navigation
1838 * ~~~~~~~~~~~~~~~~~
1839 * Up::
57bdf034 1840 * Move cursor one line up.
468876c9
JF
1841 * Down::
1842 * Move cursor one line down.
1843 * k::
57bdf034 1844 * Move cursor one line up and enter. When used in the main view
468876c9
JF
1845 * this will always show the diff of the current commit in the
1846 * split diff view.
1847 * j::
1848 * Move cursor one line down and enter.
1849 * PgUp::
c622eefa 1850 * b::
0df811cb 1851 * -::
57bdf034 1852 * Move cursor one page up.
468876c9 1853 * PgDown::
c622eefa 1854 * Space::
468876c9
JF
1855 * Move cursor one page down.
1856 * Home::
1857 * Jump to first line.
1858 * End::
1859 * Jump to last line.
1860 **/
1861 { KEY_UP, REQ_MOVE_UP },
1862 { KEY_DOWN, REQ_MOVE_DOWN },
1863 { 'k', REQ_MOVE_UP_ENTER },
1864 { 'j', REQ_MOVE_DOWN_ENTER },
1865 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1866 { KEY_END, REQ_MOVE_LAST_LINE },
1867 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
c622eefa 1868 { ' ', REQ_MOVE_PAGE_DOWN },
468876c9 1869 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
c622eefa 1870 { 'b', REQ_MOVE_PAGE_UP },
0df811cb 1871 { '-', REQ_MOVE_PAGE_UP },
468876c9
JF
1872
1873 /**
1874 * Scrolling
1875 * ~~~~~~~~~
1876 * Insert::
1877 * Scroll view one line up.
1878 * Delete::
1879 * Scroll view one line down.
1880 * w::
1881 * Scroll view one page up.
1882 * s::
1883 * Scroll view one page down.
1884 **/
1885 { KEY_IC, REQ_SCROLL_LINE_UP },
1886 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1887 { 'w', REQ_SCROLL_PAGE_UP },
1888 { 's', REQ_SCROLL_PAGE_DOWN },
1889
1890 /**
1891 * Misc
1892 * ~~~~
d9d1c722
JF
1893 * Q::
1894 * Quit.
468876c9
JF
1895 * r::
1896 * Redraw screen.
1897 * z::
1898 * Stop all background loading. This can be useful if you use
1899 * tig(1) in a repository with a long history without limiting
0721c53a 1900 * the revision log.
468876c9
JF
1901 * v::
1902 * Show version.
1903 * n::
1904 * Toggle line numbers on/off.
1905 * ':'::
1906 * Open prompt. This allows you to specify what git command
1907 * to run. Example:
1908 *
1909 * :log -p
1910 **/
d9d1c722 1911 { 'Q', REQ_QUIT },
468876c9
JF
1912 { 'z', REQ_STOP_LOADING },
1913 { 'v', REQ_SHOW_VERSION },
1914 { 'r', REQ_SCREEN_REDRAW },
1915 { 'n', REQ_TOGGLE_LINE_NUMBERS },
1916 { ':', REQ_PROMPT },
1917
1918 /* wgetch() with nodelay() enabled returns ERR when there's no input. */
1919 { ERR, REQ_SCREEN_UPDATE },
1920
1921 /* Use the ncurses SIGWINCH handler. */
1922 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1923};
1924
1925static enum request
1926get_request(int key)
1927{
1928 int i;
1929
1930 for (i = 0; i < ARRAY_SIZE(keymap); i++)
1931 if (keymap[i].alias == key)
1932 return keymap[i].request;
1933
1934 return (enum request) key;
1935}
1936
1937
6b161b31 1938/*
10e290ee
JF
1939 * Unicode / UTF-8 handling
1940 *
1941 * NOTE: Much of the following code for dealing with unicode is derived from
1942 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
1943 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
1944 */
1945
1946/* I've (over)annotated a lot of code snippets because I am not entirely
1947 * confident that the approach taken by this small UTF-8 interface is correct.
1948 * --jonas */
1949
1950static inline int
1951unicode_width(unsigned long c)
1952{
1953 if (c >= 0x1100 &&
1954 (c <= 0x115f /* Hangul Jamo */
1955 || c == 0x2329
1956 || c == 0x232a
1957 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
1958 /* CJK ... Yi */
1959 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
1960 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
1961 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
1962 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
1963 || (c >= 0xffe0 && c <= 0xffe6)
1964 || (c >= 0x20000 && c <= 0x2fffd)
1965 || (c >= 0x30000 && c <= 0x3fffd)))
1966 return 2;
1967
1968 return 1;
1969}
1970
1971/* Number of bytes used for encoding a UTF-8 character indexed by first byte.
1972 * Illegal bytes are set one. */
1973static const unsigned char utf8_bytes[256] = {
1974 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,
1975 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,
1976 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,
1977 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,
1978 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,
1979 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,
1980 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,
1981 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,
1982};
1983
1984/* Decode UTF-8 multi-byte representation into a unicode character. */
1985static inline unsigned long
1986utf8_to_unicode(const char *string, size_t length)
1987{
1988 unsigned long unicode;
1989
1990 switch (length) {
1991 case 1:
1992 unicode = string[0];
1993 break;
1994 case 2:
1995 unicode = (string[0] & 0x1f) << 6;
1996 unicode += (string[1] & 0x3f);
1997 break;
1998 case 3:
1999 unicode = (string[0] & 0x0f) << 12;
2000 unicode += ((string[1] & 0x3f) << 6);
2001 unicode += (string[2] & 0x3f);
2002 break;
2003 case 4:
2004 unicode = (string[0] & 0x0f) << 18;
2005 unicode += ((string[1] & 0x3f) << 12);
2006 unicode += ((string[2] & 0x3f) << 6);
2007 unicode += (string[3] & 0x3f);
2008 break;
2009 case 5:
2010 unicode = (string[0] & 0x0f) << 24;
2011 unicode += ((string[1] & 0x3f) << 18);
2012 unicode += ((string[2] & 0x3f) << 12);
2013 unicode += ((string[3] & 0x3f) << 6);
2014 unicode += (string[4] & 0x3f);
2015 break;
68b6e0eb 2016 case 6:
10e290ee
JF
2017 unicode = (string[0] & 0x01) << 30;
2018 unicode += ((string[1] & 0x3f) << 24);
2019 unicode += ((string[2] & 0x3f) << 18);
2020 unicode += ((string[3] & 0x3f) << 12);
2021 unicode += ((string[4] & 0x3f) << 6);
2022 unicode += (string[5] & 0x3f);
2023 break;
2024 default:
2025 die("Invalid unicode length");
2026 }
2027
2028 /* Invalid characters could return the special 0xfffd value but NUL
2029 * should be just as good. */
2030 return unicode > 0xffff ? 0 : unicode;
2031}
2032
2033/* Calculates how much of string can be shown within the given maximum width
2034 * and sets trimmed parameter to non-zero value if all of string could not be
2035 * shown.
2036 *
2037 * Additionally, adds to coloffset how many many columns to move to align with
2038 * the expected position. Takes into account how multi-byte and double-width
2039 * characters will effect the cursor position.
2040 *
2041 * Returns the number of bytes to output from string to satisfy max_width. */
2042static size_t
2043utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2044{
2045 const char *start = string;
2046 const char *end = strchr(string, '\0');
2047 size_t mbwidth = 0;
2048 size_t width = 0;
2049
2050 *trimmed = 0;
2051
2052 while (string < end) {
2053 int c = *(unsigned char *) string;
2054 unsigned char bytes = utf8_bytes[c];
2055 size_t ucwidth;
2056 unsigned long unicode;
2057
2058 if (string + bytes > end)
2059 break;
2060
2061 /* Change representation to figure out whether
2062 * it is a single- or double-width character. */
2063
2064 unicode = utf8_to_unicode(string, bytes);
2065 /* FIXME: Graceful handling of invalid unicode character. */
2066 if (!unicode)
2067 break;
2068
2069 ucwidth = unicode_width(unicode);
2070 width += ucwidth;
2071 if (width > max_width) {
2072 *trimmed = 1;
2073 break;
2074 }
2075
2076 /* The column offset collects the differences between the
2077 * number of bytes encoding a character and the number of
2078 * columns will be used for rendering said character.
2079 *
2080 * So if some character A is encoded in 2 bytes, but will be
2081 * represented on the screen using only 1 byte this will and up
2082 * adding 1 to the multi-byte column offset.
2083 *
2084 * Assumes that no double-width character can be encoding in
2085 * less than two bytes. */
2086 if (bytes > ucwidth)
2087 mbwidth += bytes - ucwidth;
2088
2089 string += bytes;
2090 }
2091
2092 *coloffset += mbwidth;
2093
2094 return string - start;
2095}
2096
2097
2098/*
6b161b31
JF
2099 * Status management
2100 */
2e8488b4 2101
8855ada4 2102/* Whether or not the curses interface has been initialized. */
68b6e0eb 2103static bool cursed = FALSE;
8855ada4 2104
6b161b31
JF
2105/* The status window is used for polling keystrokes. */
2106static WINDOW *status_win;
4a2909a7 2107
2e8488b4 2108/* Update status and title window. */
4a2909a7
JF
2109static void
2110report(const char *msg, ...)
2111{
6706b2ba
JF
2112 static bool empty = TRUE;
2113 struct view *view = display[current_view];
b76c2afc 2114
6706b2ba
JF
2115 if (!empty || *msg) {
2116 va_list args;
4a2909a7 2117
6706b2ba 2118 va_start(args, msg);
4b76734f 2119
6706b2ba
JF
2120 werase(status_win);
2121 wmove(status_win, 0, 0);
2122 if (*msg) {
2123 vwprintw(status_win, msg, args);
2124 empty = FALSE;
2125 } else {
2126 empty = TRUE;
2127 }
2128 wrefresh(status_win);
b801d8b2 2129
6706b2ba
JF
2130 va_end(args);
2131 }
2132
2133 update_view_title(view);
2134
2135 /* Move the cursor to the right-most column of the cursor line.
2136 *
2137 * XXX: This could turn out to be a bit expensive, but it ensures that
2138 * the cursor does not jump around. */
2139 if (view->lines) {
2140 wmove(view->win, view->lineno - view->offset, view->width - 1);
2141 wrefresh(view->win);
2142 }
b801d8b2
JF
2143}
2144
6b161b31
JF
2145/* Controls when nodelay should be in effect when polling user input. */
2146static void
1ba2ae4b 2147set_nonblocking_input(bool loading)
b801d8b2 2148{
6706b2ba 2149 static unsigned int loading_views;
b801d8b2 2150
6706b2ba
JF
2151 if ((loading == FALSE && loading_views-- == 1) ||
2152 (loading == TRUE && loading_views++ == 0))
1ba2ae4b 2153 nodelay(status_win, loading);
6b161b31
JF
2154}
2155
2156static void
2157init_display(void)
2158{
2159 int x, y;
b76c2afc 2160
6908bdbd
JF
2161 /* Initialize the curses library */
2162 if (isatty(STDIN_FILENO)) {
8855ada4 2163 cursed = !!initscr();
6908bdbd
JF
2164 } else {
2165 /* Leave stdin and stdout alone when acting as a pager. */
2166 FILE *io = fopen("/dev/tty", "r+");
2167
8855ada4 2168 cursed = !!newterm(NULL, io, io);
6908bdbd
JF
2169 }
2170
8855ada4
JF
2171 if (!cursed)
2172 die("Failed to initialize curses");
2173
2e8488b4
JF
2174 nonl(); /* Tell curses not to do NL->CR/NL on output */
2175 cbreak(); /* Take input chars one at a time, no wait for \n */
2176 noecho(); /* Don't echo input */
b801d8b2 2177 leaveok(stdscr, TRUE);
b801d8b2
JF
2178
2179 if (has_colors())
2180 init_colors();
2181
2182 getmaxyx(stdscr, y, x);
2183 status_win = newwin(1, 0, y - 1, 0);
2184 if (!status_win)
2185 die("Failed to create status window");
2186
2187 /* Enable keyboard mapping */
2188 keypad(status_win, TRUE);
78c70acd 2189 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6b161b31
JF
2190}
2191
c34d9c9f
JF
2192
2193/*
2194 * Repository references
2195 */
2196
2197static struct ref *refs;
3a91b75e 2198static size_t refs_size;
c34d9c9f 2199
1307df1a
JF
2200/* Id <-> ref store */
2201static struct ref ***id_refs;
2202static size_t id_refs_size;
2203
c34d9c9f
JF
2204static struct ref **
2205get_refs(char *id)
2206{
1307df1a
JF
2207 struct ref ***tmp_id_refs;
2208 struct ref **ref_list = NULL;
2209 size_t ref_list_size = 0;
c34d9c9f
JF
2210 size_t i;
2211
1307df1a
JF
2212 for (i = 0; i < id_refs_size; i++)
2213 if (!strcmp(id, id_refs[i][0]->id))
2214 return id_refs[i];
2215
2216 tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2217 if (!tmp_id_refs)
2218 return NULL;
2219
2220 id_refs = tmp_id_refs;
2221
c34d9c9f
JF
2222 for (i = 0; i < refs_size; i++) {
2223 struct ref **tmp;
2224
2225 if (strcmp(id, refs[i].id))
2226 continue;
2227
1307df1a 2228 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
c34d9c9f 2229 if (!tmp) {
1307df1a
JF
2230 if (ref_list)
2231 free(ref_list);
c34d9c9f
JF
2232 return NULL;
2233 }
2234
1307df1a
JF
2235 ref_list = tmp;
2236 if (ref_list_size > 0)
2237 ref_list[ref_list_size - 1]->next = 1;
2238 ref_list[ref_list_size] = &refs[i];
3af8774e
JF
2239
2240 /* XXX: The properties of the commit chains ensures that we can
2241 * safely modify the shared ref. The repo references will
2242 * always be similar for the same id. */
1307df1a
JF
2243 ref_list[ref_list_size]->next = 0;
2244 ref_list_size++;
c34d9c9f
JF
2245 }
2246
1307df1a
JF
2247 if (ref_list)
2248 id_refs[id_refs_size++] = ref_list;
2249
2250 return ref_list;
c34d9c9f
JF
2251}
2252
2253static int
2254load_refs(void)
2255{
4685845e
TH
2256 const char *cmd_env = getenv("TIG_LS_REMOTE");
2257 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
c34d9c9f
JF
2258 FILE *pipe = popen(cmd, "r");
2259 char buffer[BUFSIZ];
2260 char *line;
2261
2262 if (!pipe)
2263 return ERR;
2264
2265 while ((line = fgets(buffer, sizeof(buffer), pipe))) {
2266 char *name = strchr(line, '\t');
2267 struct ref *ref;
2268 int namelen;
2269 bool tag = FALSE;
6734f6b9 2270 bool tag_commit = FALSE;
c34d9c9f
JF
2271
2272 if (!name)
2273 continue;
2274
2275 *name++ = 0;
2276 namelen = strlen(name) - 1;
6706b2ba
JF
2277
2278 /* Commits referenced by tags has "^{}" appended. */
c34d9c9f
JF
2279 if (name[namelen - 1] == '}') {
2280 while (namelen > 0 && name[namelen] != '^')
2281 namelen--;
6734f6b9
JF
2282 if (namelen > 0)
2283 tag_commit = TRUE;
c34d9c9f
JF
2284 }
2285 name[namelen] = 0;
2286
2287 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
6734f6b9
JF
2288 if (!tag_commit)
2289 continue;
c34d9c9f
JF
2290 name += STRING_SIZE("refs/tags/");
2291 tag = TRUE;
3af8774e
JF
2292
2293 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2294 name += STRING_SIZE("refs/heads/");
2295
2296 } else if (!strcmp(name, "HEAD")) {
2297 continue;
c34d9c9f
JF
2298 }
2299
2300 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2301 if (!refs)
2302 return ERR;
2303
2304 ref = &refs[refs_size++];
2305 ref->tag = tag;
2306 ref->name = strdup(name);
2307 if (!ref->name)
2308 return ERR;
2309
2310 string_copy(ref->id, line);
2311 }
2312
2313 if (ferror(pipe))
2314 return ERR;
2315
2316 pclose(pipe);
2317
2318 return OK;
2319}
2320
6b161b31
JF
2321/*
2322 * Main
2323 */
2324
b5c9e67f
TH
2325#if __GNUC__ >= 3
2326#define __NORETURN __attribute__((__noreturn__))
2327#else
2328#define __NORETURN
2329#endif
2330
2331static void __NORETURN
6b161b31
JF
2332quit(int sig)
2333{
8855ada4
JF
2334 /* XXX: Restore tty modes and let the OS cleanup the rest! */
2335 if (cursed)
2336 endwin();
6b161b31
JF
2337 exit(0);
2338}
2339
c6704a4e
JF
2340static void __NORETURN
2341die(const char *err, ...)
6b161b31
JF
2342{
2343 va_list args;
2344
2345 endwin();
2346
2347 va_start(args, err);
2348 fputs("tig: ", stderr);
2349 vfprintf(stderr, err, args);
2350 fputs("\n", stderr);
2351 va_end(args);
2352
2353 exit(1);
2354}
2355
2356int
2357main(int argc, char *argv[])
2358{
1ba2ae4b 2359 struct view *view;
6b161b31 2360 enum request request;
1ba2ae4b 2361 size_t i;
6b161b31
JF
2362
2363 signal(SIGINT, quit);
2364
8855ada4 2365 if (!parse_options(argc, argv))
6b161b31
JF
2366 return 0;
2367
c34d9c9f
JF
2368 if (load_refs() == ERR)
2369 die("Failed to load refs.");
2370
7bb55251
JF
2371 /* Require a git repository unless when running in pager mode. */
2372 if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
2373 die("Not a git repository");
2374
1ba2ae4b
JF
2375 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2376 view->cmd_env = getenv(view->cmd_env);
2377
6b161b31
JF
2378 request = opt_request;
2379
2380 init_display();
b801d8b2
JF
2381
2382 while (view_driver(display[current_view], request)) {
6b161b31 2383 int key;
b801d8b2
JF
2384 int i;
2385
6b161b31
JF
2386 foreach_view (view, i)
2387 update_view(view);
b801d8b2
JF
2388
2389 /* Refresh, accept single keystroke of input */
6b161b31
JF
2390 key = wgetch(status_win);
2391 request = get_request(key);
03a93dbb 2392
6706b2ba 2393 /* Some low-level request handling. This keeps access to
fac7db6c
JF
2394 * status_win restricted. */
2395 switch (request) {
2396 case REQ_PROMPT:
6908bdbd
JF
2397 report(":");
2398 /* Temporarily switch to line-oriented and echoed
2399 * input. */
03a93dbb
JF
2400 nocbreak();
2401 echo();
49f2b43f
JF
2402
2403 if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2404 memcpy(opt_cmd, "git ", 4);
2405 opt_request = REQ_VIEW_PAGER;
2406 } else {
2407 request = ERR;
2408 }
2409
6908bdbd
JF
2410 noecho();
2411 cbreak();
fac7db6c
JF
2412 break;
2413
2414 case REQ_SCREEN_RESIZE:
2415 {
2416 int height, width;
2417
2418 getmaxyx(stdscr, height, width);
2419
2420 /* Resize the status view and let the view driver take
2421 * care of resizing the displayed views. */
2422 wresize(status_win, 1, width);
2423 mvwin(status_win, height - 1, 0);
2424 wrefresh(status_win);
2425 break;
2426 }
2427 default:
2428 break;
03a93dbb 2429 }
b801d8b2
JF
2430 }
2431
2432 quit(0);
2433
2434 return 0;
2435}
2436
2437/**
0721c53a 2438 * [[refspec]]
3a11b38f
JF
2439 * Revision specification
2440 * ----------------------
0721c53a 2441 * This section describes various ways to specify what revisions to display
3a11b38f
JF
2442 * or otherwise limit the view to. tig(1) does not itself parse the described
2443 * revision options so refer to the relevant git man pages for futher
2444 * information. Relevant man pages besides git-log(1) are git-diff(1) and
2445 * git-rev-list(1).
468876c9 2446 *
3a11b38f
JF
2447 * You can tune the interaction with git by making use of the options
2448 * explained in this section. For example, by configuring the environment
2449 * variables described in the <<view-commands, "View commands">> section.
2450 *
2451 * Limit by path name
2452 * ~~~~~~~~~~~~~~~~~~
ab46037d
JF
2453 * If you are interested only in those revisions that made changes to a
2454 * specific file (or even several files) list the files like this:
2455 *
c760e6ba 2456 * $ tig log Makefile README
ab46037d
JF
2457 *
2458 * To avoid ambiguity with repository references such as tag name, be sure
2459 * to separate file names from other git options using "\--". So if you
2460 * have a file named 'master' it will clash with the reference named
2461 * 'master', and thus you will have to use:
2462 *
1b4b0bd9 2463 * $ tig log -- master
ab46037d
JF
2464 *
2465 * NOTE: For the main view, avoiding ambiguity will in some cases require
2466 * you to specify two "\--" options. The first will make tig(1) stop
2467 * option processing and the latter will be passed to git log.
2468 *
468876c9
JF
2469 * Limit by date or number
2470 * ~~~~~~~~~~~~~~~~~~~~~~~
2471 * To speed up interaction with git, you can limit the amount of commits
2472 * to show both for the log and main view. Either limit by date using
2473 * e.g. `--since=1.month` or limit by the number of commits using `-n400`.
2474 *
3a11b38f
JF
2475 * If you are only interested in changed that happened between two dates
2476 * you can use:
2477 *
c760e6ba 2478 * $ tig -- --after="May 5th" --before="2006-05-16 15:44"
468876c9 2479 *
c760e6ba
JF
2480 * NOTE: If you want to avoid having to quote dates containing spaces you
2481 * can use "." instead, e.g. `--after=May.5th`.
3a11b38f
JF
2482 *
2483 * Limiting by commit ranges
2484 * ~~~~~~~~~~~~~~~~~~~~~~~~~
468876c9
JF
2485 * Alternatively, commits can be limited to a specific range, such as
2486 * "all commits between 'tag-1.0' and 'tag-2.0'". For example:
2487 *
2488 * $ tig log tag-1.0..tag-2.0
2489 *
2490 * This way of commit limiting makes it trivial to only browse the commits
2491 * which haven't been pushed to a remote branch. Assuming 'origin' is your
2492 * upstream remote branch, using:
2493 *
2494 * $ tig log origin..HEAD
2495 *
2496 * will list what will be pushed to the remote branch. Optionally, the ending
2497 * 'HEAD' can be left out since it is implied.
2498 *
2499 * Limiting by reachability
2500 * ~~~~~~~~~~~~~~~~~~~~~~~~
2501 * Git interprets the range specifier "tag-1.0..tag-2.0" as
2502 * "all commits reachable from 'tag-2.0' but not from 'tag-1.0'".
3a11b38f
JF
2503 * Where reachability refers to what commits are ancestors (or part of the
2504 * history) of the branch or tagged revision in question.
2505 *
468876c9
JF
2506 * If you prefer to specify which commit to preview in this way use the
2507 * following:
2508 *
2509 * $ tig log tag-2.0 ^tag-1.0
2510 *
57bdf034
JF
2511 * You can think of '^' as a negation operator. Using this alternate syntax,
2512 * it is possible to further prune commits by specifying multiple branch
2513 * cut offs.
468876c9 2514 *
3a11b38f
JF
2515 * Combining revisions specification
2516 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2517 * Revisions options can to some degree be combined, which makes it possible
2518 * to say "show at most 20 commits from within the last month that changed
2519 * files under the Documentation/ directory."
2520 *
2521 * $ tig -- --since=1.month -n20 -- Documentation/
2522 *
2523 * Examining all repository references
2524 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2525 * In some cases, it can be useful to query changes across all references
2526 * in a repository. An example is to ask "did any line of development in
2527 * this repository change a particular file within the last week". This
2528 * can be accomplished using:
2529 *
2530 * $ tig -- --all --since=1.week -- Makefile
2531 *
6706b2ba
JF
2532 * BUGS
2533 * ----
2534 * Known bugs and problems:
2535 *
10e290ee
JF
2536 * - In it's current state tig is pretty much UTF-8 only.
2537 *
6706b2ba 2538 * - If the screen width is very small the main view can draw
468876c9
JF
2539 * outside the current view causing bad wrapping. Same goes
2540 * for title and status windows.
6706b2ba 2541 *
4c6fabc2
JF
2542 * TODO
2543 * ----
2544 * Features that should be explored.
2545 *
fac7db6c 2546 * - Searching.
4c6fabc2
JF
2547 *
2548 * - Locale support.
2549 *
b801d8b2
JF
2550 * COPYRIGHT
2551 * ---------
4a2909a7 2552 * Copyright (c) Jonas Fonseca <fonseca@diku.dk>, 2006
b801d8b2
JF
2553 *
2554 * This program is free software; you can redistribute it and/or modify
2555 * it under the terms of the GNU General Public License as published by
2556 * the Free Software Foundation; either version 2 of the License, or
2557 * (at your option) any later version.
2558 *
2559 * SEE ALSO
2560 * --------
4c6fabc2 2561 * [verse]
b801d8b2
JF
2562 * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
2563 * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
4c6fabc2 2564 * gitk(1): git repository browser written using tcl/tk,
c6704a4e 2565 * qgit(1): git repository browser written using c++/Qt,
4c6fabc2 2566 * gitview(1): git repository browser written using python/gtk.
b801d8b2 2567 **/