Remove objsize member from struct commit
[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
702 /* Buffering */
703 unsigned long lines; /* Total number of lines */
8855ada4
JF
704 void **line; /* Line index; each line contains user data */
705 unsigned int digits; /* Number of digits in the lines member. */
b801d8b2
JF
706
707 /* Loading */
708 FILE *pipe;
2e8488b4 709 time_t start_time;
b801d8b2
JF
710};
711
6b161b31
JF
712static struct view_ops pager_ops;
713static struct view_ops main_ops;
a28bcc22 714
95d7ddcd
JF
715#define VIEW_STR(name, cmd, env, ref, ops) \
716 { name, cmd, #env, ref, ops }
1ba2ae4b 717
95d7ddcd
JF
718#define VIEW_(id, name, ops, ref) \
719 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops)
1ba2ae4b 720
c2124ccd
JF
721/**
722 * Views
723 * ~~~~~
724 * tig(1) presents various 'views' of a repository. Each view is based on output
725 * from an external command, most often 'git log', 'git diff', or 'git show'.
726 *
727 * The main view::
728 * Is the default view, and it shows a one line summary of each commit
729 * in the chosen list of revisions. The summary includes commit date,
730 * author, and the first line of the log message. Additionally, any
731 * repository references, such as tags, will be shown.
732 *
733 * The log view::
734 * Presents a more rich view of the revision log showing the whole log
735 * message and the diffstat.
736 *
737 * The diff view::
738 * Shows either the diff of the current working tree, that is, what
739 * has changed since the last commit, or the commit diff complete
740 * with log message, diffstat and diff.
741 *
742 * The pager view::
743 * Is used for displaying both input from stdin and output from git
744 * commands entered in the internal prompt.
745 *
746 * The help view::
747 * Displays the information from the tig(1) man page. For the help view
748 * to work you need to have the tig(1) man page installed.
749 **/
750
b801d8b2 751static struct view views[] = {
95d7ddcd
JF
752 VIEW_(MAIN, "main", &main_ops, ref_head),
753 VIEW_(DIFF, "diff", &pager_ops, ref_commit),
754 VIEW_(LOG, "log", &pager_ops, ref_head),
755 VIEW_(HELP, "help", &pager_ops, "static"),
756 VIEW_(PAGER, "pager", &pager_ops, "static"),
b801d8b2
JF
757};
758
a28bcc22
JF
759#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
760
4c6fabc2 761
b801d8b2 762static void
82e78006 763redraw_view_from(struct view *view, int lineno)
b801d8b2 764{
82e78006 765 assert(0 <= lineno && lineno < view->height);
b801d8b2 766
82e78006 767 for (; lineno < view->height; lineno++) {
6b161b31 768 if (!view->ops->draw(view, lineno))
fd85fef1 769 break;
b801d8b2
JF
770 }
771
772 redrawwin(view->win);
773 wrefresh(view->win);
774}
775
b76c2afc 776static void
82e78006
JF
777redraw_view(struct view *view)
778{
779 wclear(view->win);
780 redraw_view_from(view, 0);
781}
782
c2124ccd
JF
783
784/**
785 * Title windows
786 * ~~~~~~~~~~~~~
787 * Each view has a title window which shows the name of the view, current
788 * commit ID if available, and where the view is positioned:
789 *
790 * [main] c622eefaa485995320bc743431bae0d497b1d875 - commit 1 of 61 (1%)
791 *
792 * By default, the title of the current view is highlighted using bold font.
793 **/
794
6b161b31 795static void
81030ec8
JF
796update_view_title(struct view *view)
797{
798 if (view == display[current_view])
799 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
800 else
801 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
802
803 werase(view->title);
804 wmove(view->title, 0, 0);
805
81030ec8
JF
806 if (*view->ref)
807 wprintw(view->title, "[%s] %s", view->name, view->ref);
808 else
809 wprintw(view->title, "[%s]", view->name);
810
811 if (view->lines) {
812 wprintw(view->title, " - %s %d of %d (%d%%)",
813 view->ops->type,
814 view->lineno + 1,
815 view->lines,
816 (view->lineno + 1) * 100 / view->lines);
817 }
818
819 wrefresh(view->title);
820}
821
822static void
6b161b31 823resize_display(void)
b76c2afc 824{
03a93dbb 825 int offset, i;
6b161b31
JF
826 struct view *base = display[0];
827 struct view *view = display[1] ? display[1] : display[0];
b76c2afc 828
6b161b31 829 /* Setup window dimensions */
b76c2afc 830
03a93dbb 831 getmaxyx(stdscr, base->height, base->width);
b76c2afc 832
6b161b31 833 /* Make room for the status window. */
03a93dbb 834 base->height -= 1;
6b161b31
JF
835
836 if (view != base) {
03a93dbb
JF
837 /* Horizontal split. */
838 view->width = base->width;
6b161b31
JF
839 view->height = SCALE_SPLIT_VIEW(base->height);
840 base->height -= view->height;
841
842 /* Make room for the title bar. */
843 view->height -= 1;
844 }
845
846 /* Make room for the title bar. */
847 base->height -= 1;
848
849 offset = 0;
850
851 foreach_view (view, i) {
4d55caff
JF
852 /* Keep the height of all view->win windows one larger than is
853 * required so that the cursor can wrap-around on the last line
854 * without scrolling the window. */
b76c2afc 855 if (!view->win) {
6706b2ba 856 view->win = newwin(view->height + 1, 0, offset, 0);
6b161b31
JF
857 if (!view->win)
858 die("Failed to create %s view", view->name);
859
860 scrollok(view->win, TRUE);
861
862 view->title = newwin(1, 0, offset + view->height, 0);
863 if (!view->title)
864 die("Failed to create title window");
865
866 } else {
6706b2ba 867 wresize(view->win, view->height + 1, view->width);
6b161b31
JF
868 mvwin(view->win, offset, 0);
869 mvwin(view->title, offset + view->height, 0);
870 wrefresh(view->win);
a28bcc22 871 }
a28bcc22 872
6b161b31 873 offset += view->height + 1;
b76c2afc 874 }
6b161b31 875}
b76c2afc 876
6b161b31 877static void
20bb5e18
JF
878redraw_display(void)
879{
880 struct view *view;
881 int i;
882
883 foreach_view (view, i) {
884 redraw_view(view);
885 update_view_title(view);
886 }
887}
888
889
2e8488b4
JF
890/*
891 * Navigation
892 */
893
4a2909a7 894/* Scrolling backend */
b801d8b2 895static void
4a2909a7 896do_scroll_view(struct view *view, int lines)
b801d8b2 897{
fd85fef1
JF
898 /* The rendering expects the new offset. */
899 view->offset += lines;
900
901 assert(0 <= view->offset && view->offset < view->lines);
902 assert(lines);
b801d8b2 903
82e78006 904 /* Redraw the whole screen if scrolling is pointless. */
4c6fabc2 905 if (view->height < ABS(lines)) {
b76c2afc
JF
906 redraw_view(view);
907
908 } else {
22f66b0a 909 int line = lines > 0 ? view->height - lines : 0;
82e78006 910 int end = line + ABS(lines);
fd85fef1
JF
911
912 wscrl(view->win, lines);
913
22f66b0a 914 for (; line < end; line++) {
6b161b31 915 if (!view->ops->draw(view, line))
fd85fef1
JF
916 break;
917 }
918 }
919
920 /* Move current line into the view. */
921 if (view->lineno < view->offset) {
922 view->lineno = view->offset;
6b161b31 923 view->ops->draw(view, 0);
fd85fef1
JF
924
925 } else if (view->lineno >= view->offset + view->height) {
6706b2ba
JF
926 if (view->lineno == view->offset + view->height) {
927 /* Clear the hidden line so it doesn't show if the view
928 * is scrolled up. */
929 wmove(view->win, view->height, 0);
930 wclrtoeol(view->win);
931 }
fd85fef1 932 view->lineno = view->offset + view->height - 1;
6b161b31 933 view->ops->draw(view, view->lineno - view->offset);
fd85fef1
JF
934 }
935
4c6fabc2 936 assert(view->offset <= view->lineno && view->lineno < view->lines);
fd85fef1
JF
937
938 redrawwin(view->win);
939 wrefresh(view->win);
9d3f5834 940 report("");
fd85fef1 941}
78c70acd 942
4a2909a7 943/* Scroll frontend */
fd85fef1 944static void
6b161b31 945scroll_view(struct view *view, enum request request)
fd85fef1
JF
946{
947 int lines = 1;
b801d8b2
JF
948
949 switch (request) {
4a2909a7 950 case REQ_SCROLL_PAGE_DOWN:
fd85fef1 951 lines = view->height;
4a2909a7 952 case REQ_SCROLL_LINE_DOWN:
b801d8b2 953 if (view->offset + lines > view->lines)
bde3653a 954 lines = view->lines - view->offset;
b801d8b2 955
fd85fef1 956 if (lines == 0 || view->offset + view->height >= view->lines) {
eb98559e 957 report("Cannot scroll beyond the last line");
b801d8b2
JF
958 return;
959 }
960 break;
961
4a2909a7 962 case REQ_SCROLL_PAGE_UP:
fd85fef1 963 lines = view->height;
4a2909a7 964 case REQ_SCROLL_LINE_UP:
b801d8b2
JF
965 if (lines > view->offset)
966 lines = view->offset;
967
968 if (lines == 0) {
eb98559e 969 report("Cannot scroll beyond the first line");
b801d8b2
JF
970 return;
971 }
972
fd85fef1 973 lines = -lines;
b801d8b2 974 break;
03a93dbb 975
6b161b31
JF
976 default:
977 die("request %d not handled in switch", request);
b801d8b2
JF
978 }
979
4a2909a7 980 do_scroll_view(view, lines);
fd85fef1 981}
b801d8b2 982
4a2909a7 983/* Cursor moving */
fd85fef1 984static void
6b161b31 985move_view(struct view *view, enum request request)
fd85fef1
JF
986{
987 int steps;
b801d8b2 988
fd85fef1 989 switch (request) {
4a2909a7 990 case REQ_MOVE_FIRST_LINE:
78c70acd
JF
991 steps = -view->lineno;
992 break;
993
4a2909a7 994 case REQ_MOVE_LAST_LINE:
78c70acd
JF
995 steps = view->lines - view->lineno - 1;
996 break;
997
4a2909a7 998 case REQ_MOVE_PAGE_UP:
78c70acd
JF
999 steps = view->height > view->lineno
1000 ? -view->lineno : -view->height;
1001 break;
1002
4a2909a7 1003 case REQ_MOVE_PAGE_DOWN:
78c70acd
JF
1004 steps = view->lineno + view->height >= view->lines
1005 ? view->lines - view->lineno - 1 : view->height;
1006 break;
1007
4a2909a7 1008 case REQ_MOVE_UP:
6706b2ba 1009 case REQ_MOVE_UP_ENTER:
fd85fef1
JF
1010 steps = -1;
1011 break;
b801d8b2 1012
4a2909a7 1013 case REQ_MOVE_DOWN:
6706b2ba 1014 case REQ_MOVE_DOWN_ENTER:
fd85fef1
JF
1015 steps = 1;
1016 break;
6b161b31
JF
1017
1018 default:
1019 die("request %d not handled in switch", request);
78c70acd 1020 }
b801d8b2 1021
4c6fabc2 1022 if (steps <= 0 && view->lineno == 0) {
eb98559e 1023 report("Cannot move beyond the first line");
78c70acd 1024 return;
b801d8b2 1025
6908bdbd 1026 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
eb98559e 1027 report("Cannot move beyond the last line");
78c70acd 1028 return;
fd85fef1
JF
1029 }
1030
4c6fabc2 1031 /* Move the current line */
fd85fef1 1032 view->lineno += steps;
4c6fabc2
JF
1033 assert(0 <= view->lineno && view->lineno < view->lines);
1034
1035 /* Repaint the old "current" line if we be scrolling */
2e8488b4
JF
1036 if (ABS(steps) < view->height) {
1037 int prev_lineno = view->lineno - steps - view->offset;
1038
1039 wmove(view->win, prev_lineno, 0);
1040 wclrtoeol(view->win);
03a93dbb 1041 view->ops->draw(view, prev_lineno);
2e8488b4 1042 }
fd85fef1 1043
4c6fabc2 1044 /* Check whether the view needs to be scrolled */
fd85fef1
JF
1045 if (view->lineno < view->offset ||
1046 view->lineno >= view->offset + view->height) {
1047 if (steps < 0 && -steps > view->offset) {
1048 steps = -view->offset;
b76c2afc
JF
1049
1050 } else if (steps > 0) {
1051 if (view->lineno == view->lines - 1 &&
1052 view->lines > view->height) {
1053 steps = view->lines - view->offset - 1;
1054 if (steps >= view->height)
1055 steps -= view->height - 1;
1056 }
b801d8b2 1057 }
78c70acd 1058
4a2909a7 1059 do_scroll_view(view, steps);
fd85fef1 1060 return;
b801d8b2
JF
1061 }
1062
4c6fabc2 1063 /* Draw the current line */
6b161b31 1064 view->ops->draw(view, view->lineno - view->offset);
fd85fef1 1065
b801d8b2
JF
1066 redrawwin(view->win);
1067 wrefresh(view->win);
9d3f5834 1068 report("");
b801d8b2
JF
1069}
1070
b801d8b2 1071
2e8488b4
JF
1072/*
1073 * Incremental updating
1074 */
b801d8b2 1075
03a93dbb 1076static bool
b801d8b2
JF
1077begin_update(struct view *view)
1078{
4685845e 1079 const char *id = view->id;
fd85fef1 1080
03a93dbb
JF
1081 if (opt_cmd[0]) {
1082 string_copy(view->cmd, opt_cmd);
1083 opt_cmd[0] = 0;
8855ada4
JF
1084 /* When running random commands, the view ref could have become
1085 * invalid so clear it. */
1086 view->ref[0] = 0;
03a93dbb 1087 } else {
4685845e 1088 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1ba2ae4b
JF
1089
1090 if (snprintf(view->cmd, sizeof(view->cmd), format,
1091 id, id, id, id, id) >= sizeof(view->cmd))
03a93dbb
JF
1092 return FALSE;
1093 }
b801d8b2 1094
6908bdbd
JF
1095 /* Special case for the pager view. */
1096 if (opt_pipe) {
1097 view->pipe = opt_pipe;
1098 opt_pipe = NULL;
1099 } else {
1100 view->pipe = popen(view->cmd, "r");
1101 }
1102
2e8488b4
JF
1103 if (!view->pipe)
1104 return FALSE;
b801d8b2 1105
6b161b31 1106 set_nonblocking_input(TRUE);
b801d8b2
JF
1107
1108 view->offset = 0;
1109 view->lines = 0;
1110 view->lineno = 0;
49f2b43f 1111 string_copy(view->vid, id);
b801d8b2 1112
2e8488b4
JF
1113 if (view->line) {
1114 int i;
1115
1116 for (i = 0; i < view->lines; i++)
1117 if (view->line[i])
1118 free(view->line[i]);
1119
1120 free(view->line);
1121 view->line = NULL;
1122 }
1123
1124 view->start_time = time(NULL);
1125
b801d8b2
JF
1126 return TRUE;
1127}
1128
1129static void
1130end_update(struct view *view)
1131{
03a93dbb
JF
1132 if (!view->pipe)
1133 return;
6b161b31 1134 set_nonblocking_input(FALSE);
80ce96ea
JF
1135 if (view->pipe == stdin)
1136 fclose(view->pipe);
1137 else
1138 pclose(view->pipe);
2e8488b4 1139 view->pipe = NULL;
b801d8b2
JF
1140}
1141
03a93dbb 1142static bool
b801d8b2
JF
1143update_view(struct view *view)
1144{
1145 char buffer[BUFSIZ];
1146 char *line;
22f66b0a 1147 void **tmp;
82e78006
JF
1148 /* The number of lines to read. If too low it will cause too much
1149 * redrawing (and possible flickering), if too high responsiveness
1150 * will suffer. */
8855ada4 1151 unsigned long lines = view->height;
82e78006 1152 int redraw_from = -1;
b801d8b2
JF
1153
1154 if (!view->pipe)
1155 return TRUE;
1156
82e78006
JF
1157 /* Only redraw if lines are visible. */
1158 if (view->offset + view->height >= view->lines)
1159 redraw_from = view->lines - view->offset;
b801d8b2
JF
1160
1161 tmp = realloc(view->line, sizeof(*view->line) * (view->lines + lines));
1162 if (!tmp)
1163 goto alloc_error;
1164
1165 view->line = tmp;
1166
1167 while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
c34d9c9f 1168 int linelen = strlen(line);
b801d8b2 1169
b801d8b2
JF
1170 if (linelen)
1171 line[linelen - 1] = 0;
1172
6b161b31 1173 if (!view->ops->read(view, line))
b801d8b2 1174 goto alloc_error;
fd85fef1
JF
1175
1176 if (lines-- == 1)
1177 break;
b801d8b2
JF
1178 }
1179
8855ada4
JF
1180 {
1181 int digits;
1182
1183 lines = view->lines;
1184 for (digits = 0; lines; digits++)
1185 lines /= 10;
1186
1187 /* Keep the displayed view in sync with line number scaling. */
1188 if (digits != view->digits) {
1189 view->digits = digits;
1190 redraw_from = 0;
1191 }
1192 }
1193
82e78006
JF
1194 if (redraw_from >= 0) {
1195 /* If this is an incremental update, redraw the previous line
a28bcc22
JF
1196 * since for commits some members could have changed when
1197 * loading the main view. */
82e78006
JF
1198 if (redraw_from > 0)
1199 redraw_from--;
1200
1201 /* Incrementally draw avoids flickering. */
1202 redraw_view_from(view, redraw_from);
4c6fabc2 1203 }
b801d8b2 1204
eb98559e
JF
1205 /* Update the title _after_ the redraw so that if the redraw picks up a
1206 * commit reference in view->ref it'll be available here. */
1207 update_view_title(view);
1208
b801d8b2 1209 if (ferror(view->pipe)) {
03a93dbb 1210 report("Failed to read: %s", strerror(errno));
b801d8b2
JF
1211 goto end;
1212
1213 } else if (feof(view->pipe)) {
2e8488b4
JF
1214 time_t secs = time(NULL) - view->start_time;
1215
a28bcc22 1216 if (view == VIEW(REQ_VIEW_HELP)) {
4685845e 1217 const char *msg = TIG_HELP;
468876c9
JF
1218
1219 if (view->lines == 0) {
1220 /* Slightly ugly, but abusing view->ref keeps
1221 * the error message. */
1222 string_copy(view->ref, "No help available");
1223 msg = "The tig(1) manpage is not installed";
1224 }
1225
1226 report("%s", msg);
2e8488b4
JF
1227 goto end;
1228 }
1229
1230 report("Loaded %d lines in %ld second%s", view->lines, secs,
1231 secs == 1 ? "" : "s");
b801d8b2
JF
1232 goto end;
1233 }
1234
1235 return TRUE;
1236
1237alloc_error:
2e8488b4 1238 report("Allocation failure");
b801d8b2
JF
1239
1240end:
1241 end_update(view);
1242 return FALSE;
1243}
1244
49f2b43f
JF
1245enum open_flags {
1246 OPEN_DEFAULT = 0, /* Use default view switching. */
1247 OPEN_SPLIT = 1, /* Split current view. */
1248 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
1249 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
1250};
1251
6b161b31 1252static void
49f2b43f 1253open_view(struct view *prev, enum request request, enum open_flags flags)
b801d8b2 1254{
49f2b43f
JF
1255 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
1256 bool split = !!(flags & OPEN_SPLIT);
1257 bool reload = !!(flags & OPEN_RELOAD);
a28bcc22 1258 struct view *view = VIEW(request);
b801d8b2 1259 struct view *displayed;
6b161b31
JF
1260 int nviews;
1261
03a93dbb 1262 /* Cycle between displayed views and count the views. */
6b161b31 1263 foreach_view (displayed, nviews) {
03a93dbb
JF
1264 if (prev != view &&
1265 view == displayed &&
8855ada4 1266 !strcmp(view->vid, prev->vid)) {
6b161b31
JF
1267 current_view = nviews;
1268 /* Blur out the title of the previous view. */
1269 update_view_title(prev);
6734f6b9 1270 report("");
6b161b31 1271 return;
a28bcc22 1272 }
6b161b31 1273 }
b801d8b2 1274
49f2b43f 1275 if (view == prev && nviews == 1 && !reload) {
6b161b31
JF
1276 report("Already in %s view", view->name);
1277 return;
1278 }
b801d8b2 1279
8855ada4 1280 if ((reload || strcmp(view->vid, view->id)) &&
03a93dbb 1281 !begin_update(view)) {
6b161b31
JF
1282 report("Failed to load %s view", view->name);
1283 return;
1284 }
a28bcc22 1285
6b161b31
JF
1286 if (split) {
1287 display[current_view + 1] = view;
1288 if (!backgrounded)
a28bcc22 1289 current_view++;
6b161b31
JF
1290 } else {
1291 /* Maximize the current view. */
1292 memset(display, 0, sizeof(display));
1293 current_view = 0;
1294 display[current_view] = view;
a28bcc22 1295 }
b801d8b2 1296
6b161b31 1297 resize_display();
b801d8b2 1298
a8891802 1299 if (split && prev->lineno - prev->offset >= prev->height) {
03a93dbb 1300 /* Take the title line into account. */
eb98559e 1301 int lines = prev->lineno - prev->offset - prev->height + 1;
03a93dbb
JF
1302
1303 /* Scroll the view that was split if the current line is
1304 * outside the new limited view. */
1305 do_scroll_view(prev, lines);
1306 }
1307
6b161b31
JF
1308 if (prev && view != prev) {
1309 /* "Blur" the previous view. */
6706b2ba
JF
1310 if (!backgrounded)
1311 update_view_title(prev);
6b161b31
JF
1312
1313 /* Continue loading split views in the background. */
03a93dbb 1314 if (!split)
6b161b31 1315 end_update(prev);
b801d8b2
JF
1316 }
1317
03a93dbb
JF
1318 if (view->pipe) {
1319 /* Clear the old view and let the incremental updating refill
1320 * the screen. */
1321 wclear(view->win);
1322 report("Loading...");
1323 } else {
1324 redraw_view(view);
468876c9
JF
1325 if (view == VIEW(REQ_VIEW_HELP))
1326 report("%s", TIG_HELP);
1327 else
1328 report("");
03a93dbb 1329 }
6706b2ba
JF
1330
1331 /* If the view is backgrounded the above calls to report()
1332 * won't redraw the view title. */
1333 if (backgrounded)
1334 update_view_title(view);
b801d8b2
JF
1335}
1336
1337
6b161b31
JF
1338/*
1339 * User request switch noodle
1340 */
1341
b801d8b2 1342static int
6b161b31 1343view_driver(struct view *view, enum request request)
b801d8b2 1344{
b801d8b2
JF
1345 int i;
1346
1347 switch (request) {
4a2909a7
JF
1348 case REQ_MOVE_UP:
1349 case REQ_MOVE_DOWN:
1350 case REQ_MOVE_PAGE_UP:
1351 case REQ_MOVE_PAGE_DOWN:
1352 case REQ_MOVE_FIRST_LINE:
1353 case REQ_MOVE_LAST_LINE:
a28bcc22 1354 move_view(view, request);
fd85fef1
JF
1355 break;
1356
4a2909a7
JF
1357 case REQ_SCROLL_LINE_DOWN:
1358 case REQ_SCROLL_LINE_UP:
1359 case REQ_SCROLL_PAGE_DOWN:
1360 case REQ_SCROLL_PAGE_UP:
a28bcc22 1361 scroll_view(view, request);
b801d8b2
JF
1362 break;
1363
4a2909a7 1364 case REQ_VIEW_MAIN:
4a2909a7 1365 case REQ_VIEW_DIFF:
2e8488b4
JF
1366 case REQ_VIEW_LOG:
1367 case REQ_VIEW_HELP:
6908bdbd 1368 case REQ_VIEW_PAGER:
49f2b43f 1369 open_view(view, request, OPEN_DEFAULT);
b801d8b2
JF
1370 break;
1371
6706b2ba
JF
1372 case REQ_MOVE_UP_ENTER:
1373 case REQ_MOVE_DOWN_ENTER:
1374 move_view(view, request);
1375 /* Fall-through */
1376
6b161b31 1377 case REQ_ENTER:
6908bdbd
JF
1378 if (!view->lines) {
1379 report("Nothing to enter");
1380 break;
1381 }
6b161b31
JF
1382 return view->ops->enter(view);
1383
03a93dbb
JF
1384 case REQ_VIEW_NEXT:
1385 {
1386 int nviews = display[1] ? 2 : 1;
1387 int next_view = (current_view + 1) % nviews;
1388
1389 if (next_view == current_view) {
1390 report("Only one view is displayed");
1391 break;
1392 }
1393
1394 current_view = next_view;
1395 /* Blur out the title of the previous view. */
1396 update_view_title(view);
6734f6b9 1397 report("");
03a93dbb
JF
1398 break;
1399 }
4a2909a7 1400 case REQ_TOGGLE_LINE_NUMBERS:
b76c2afc 1401 opt_line_number = !opt_line_number;
20bb5e18 1402 redraw_display();
b801d8b2
JF
1403 break;
1404
03a93dbb 1405 case REQ_PROMPT:
8855ada4 1406 /* Always reload^Wrerun commands from the prompt. */
49f2b43f 1407 open_view(view, opt_request, OPEN_RELOAD);
03a93dbb
JF
1408 break;
1409
4a2909a7 1410 case REQ_STOP_LOADING:
03a93dbb 1411 foreach_view (view, i) {
2e8488b4 1412 if (view->pipe)
6706b2ba 1413 report("Stopped loaded the %s view", view->name),
03a93dbb
JF
1414 end_update(view);
1415 }
b801d8b2
JF
1416 break;
1417
4a2909a7 1418 case REQ_SHOW_VERSION:
6cb291b7 1419 report("%s (built %s)", VERSION, __DATE__);
b801d8b2
JF
1420 return TRUE;
1421
fac7db6c
JF
1422 case REQ_SCREEN_RESIZE:
1423 resize_display();
1424 /* Fall-through */
4a2909a7 1425 case REQ_SCREEN_REDRAW:
20bb5e18 1426 redraw_display();
4a2909a7
JF
1427 break;
1428
1429 case REQ_SCREEN_UPDATE:
b801d8b2
JF
1430 doupdate();
1431 return TRUE;
1432
4f9b667a
JF
1433 case REQ_VIEW_CLOSE:
1434 if (display[1]) {
1435 view = display[(current_view + 1) % ARRAY_SIZE(display)];
1436 memset(display, 0, sizeof(display));
1437 current_view = 0;
1438 display[current_view] = view;
1439 resize_display();
1440 redraw_display();
1441 break;
1442 }
1443 /* Fall-through */
b801d8b2
JF
1444 case REQ_QUIT:
1445 return FALSE;
1446
1447 default:
2e8488b4 1448 /* An unknown key will show most commonly used commands. */
468876c9 1449 report("Unknown key, press 'h' for help");
b801d8b2
JF
1450 return TRUE;
1451 }
1452
1453 return TRUE;
1454}
1455
1456
1457/*
6b161b31 1458 * View backend handlers
b801d8b2
JF
1459 */
1460
6b161b31 1461static bool
22f66b0a 1462pager_draw(struct view *view, unsigned int lineno)
b801d8b2 1463{
78c70acd 1464 enum line_type type;
b801d8b2 1465 char *line;
4c6fabc2 1466 int linelen;
78c70acd 1467 int attr;
b801d8b2 1468
fd85fef1
JF
1469 if (view->offset + lineno >= view->lines)
1470 return FALSE;
1471
b801d8b2 1472 line = view->line[view->offset + lineno];
78c70acd 1473 type = get_line_type(line);
b801d8b2 1474
6706b2ba
JF
1475 wmove(view->win, lineno, 0);
1476
fd85fef1 1477 if (view->offset + lineno == view->lineno) {
8855ada4 1478 if (type == LINE_COMMIT) {
03a93dbb
JF
1479 string_copy(view->ref, line + 7);
1480 string_copy(ref_commit, view->ref);
1481 }
8855ada4 1482
78c70acd 1483 type = LINE_CURSOR;
6706b2ba 1484 wchgat(view->win, -1, 0, type, NULL);
fd85fef1
JF
1485 }
1486
78c70acd 1487 attr = get_line_attr(type);
b801d8b2 1488 wattrset(view->win, attr);
b76c2afc 1489
4c6fabc2 1490 linelen = strlen(line);
4c6fabc2 1491
6706b2ba
JF
1492 if (opt_line_number || opt_tab_size < TABSIZE) {
1493 static char spaces[] = " ";
1494 int col_offset = 0, col = 0;
1495
1496 if (opt_line_number) {
1497 unsigned long real_lineno = view->offset + lineno + 1;
82e78006 1498
6706b2ba
JF
1499 if (real_lineno == 1 ||
1500 (real_lineno % opt_num_interval) == 0) {
1501 wprintw(view->win, "%.*d", view->digits, real_lineno);
8855ada4 1502
6706b2ba
JF
1503 } else {
1504 waddnstr(view->win, spaces,
1505 MIN(view->digits, STRING_SIZE(spaces)));
1506 }
1507 waddstr(view->win, ": ");
1508 col_offset = view->digits + 2;
1509 }
8855ada4 1510
6706b2ba
JF
1511 while (line && col_offset + col < view->width) {
1512 int cols_max = view->width - col_offset - col;
1513 char *text = line;
1514 int cols;
4c6fabc2 1515
b76c2afc 1516 if (*line == '\t') {
6706b2ba 1517 assert(sizeof(spaces) > TABSIZE);
b76c2afc 1518 line++;
6706b2ba
JF
1519 text = spaces;
1520 cols = opt_tab_size - (col % opt_tab_size);
82e78006 1521
b76c2afc 1522 } else {
6706b2ba
JF
1523 line = strchr(line, '\t');
1524 cols = line ? line - text : strlen(text);
b76c2afc 1525 }
6706b2ba
JF
1526
1527 waddnstr(view->win, text, MIN(cols, cols_max));
1528 col += cols;
b76c2afc 1529 }
b76c2afc
JF
1530
1531 } else {
6706b2ba 1532 int col = 0, pos = 0;
b801d8b2 1533
6706b2ba
JF
1534 for (; pos < linelen && col < view->width; pos++, col++)
1535 if (line[pos] == '\t')
1536 col += TABSIZE - (col % TABSIZE) - 1;
1537
1538 waddnstr(view->win, line, pos);
1539 }
2e8488b4 1540
b801d8b2
JF
1541 return TRUE;
1542}
1543
6b161b31 1544static bool
22f66b0a
JF
1545pager_read(struct view *view, char *line)
1546{
6706b2ba
JF
1547 /* Compress empty lines in the help view. */
1548 if (view == VIEW(REQ_VIEW_HELP) &&
1549 !*line &&
1550 view->lines &&
1551 !*((char *) view->line[view->lines - 1]))
1552 return TRUE;
1553
22f66b0a
JF
1554 view->line[view->lines] = strdup(line);
1555 if (!view->line[view->lines])
1556 return FALSE;
1557
1558 view->lines++;
1559 return TRUE;
1560}
1561
6b161b31
JF
1562static bool
1563pager_enter(struct view *view)
1564{
1565 char *line = view->line[view->lineno];
91e8e277 1566 int split = 0;
6b161b31 1567
91e8e277
JF
1568 if ((view == VIEW(REQ_VIEW_LOG) ||
1569 view == VIEW(REQ_VIEW_LOG)) &&
1570 get_line_type(line) == LINE_COMMIT) {
1571 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
1572 split = 1;
67e48ac5
JF
1573 }
1574
91e8e277
JF
1575 /* Always scroll the view even if it was split. That way
1576 * you can use Enter to scroll through the log view and
1577 * split open each commit diff. */
1578 scroll_view(view, REQ_SCROLL_LINE_DOWN);
1579
1580 /* FIXME: A minor workaround. Scrolling the view will call report("")
1581 * but if we are scolling a non-current view this won't properly update
1582 * the view title. */
1583 if (split)
1584 update_view_title(view);
6b161b31
JF
1585
1586 return TRUE;
1587}
1588
6b161b31 1589static struct view_ops pager_ops = {
6734f6b9 1590 "line",
6b161b31
JF
1591 pager_draw,
1592 pager_read,
1593 pager_enter,
1594};
1595
80ce96ea 1596
c34d9c9f
JF
1597static struct ref **get_refs(char *id);
1598
6b161b31 1599static bool
22f66b0a
JF
1600main_draw(struct view *view, unsigned int lineno)
1601{
2e8488b4 1602 char buf[DATE_COLS + 1];
22f66b0a 1603 struct commit *commit;
78c70acd 1604 enum line_type type;
6706b2ba 1605 int col = 0;
b76c2afc 1606 size_t timelen;
10e290ee
JF
1607 size_t authorlen;
1608 int trimmed;
22f66b0a
JF
1609
1610 if (view->offset + lineno >= view->lines)
1611 return FALSE;
1612
1613 commit = view->line[view->offset + lineno];
4c6fabc2
JF
1614 if (!*commit->author)
1615 return FALSE;
22f66b0a 1616
6706b2ba
JF
1617 wmove(view->win, lineno, col);
1618
22f66b0a 1619 if (view->offset + lineno == view->lineno) {
03a93dbb 1620 string_copy(view->ref, commit->id);
49f2b43f 1621 string_copy(ref_commit, view->ref);
78c70acd 1622 type = LINE_CURSOR;
6706b2ba
JF
1623 wattrset(view->win, get_line_attr(type));
1624 wchgat(view->win, -1, 0, type, NULL);
1625
78c70acd 1626 } else {
b76c2afc 1627 type = LINE_MAIN_COMMIT;
6706b2ba 1628 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
b76c2afc
JF
1629 }
1630
4c6fabc2 1631 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
b76c2afc 1632 waddnstr(view->win, buf, timelen);
4c6fabc2 1633 waddstr(view->win, " ");
b76c2afc 1634
6706b2ba
JF
1635 col += DATE_COLS;
1636 wmove(view->win, lineno, col);
1637 if (type != LINE_CURSOR)
1638 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
b76c2afc 1639
10e290ee
JF
1640 /* FIXME: Make this optional, and add i18n.commitEncoding support. */
1641 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
1642
1643 if (trimmed) {
1644 waddnstr(view->win, commit->author, authorlen);
6706b2ba
JF
1645 if (type != LINE_CURSOR)
1646 wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
b76c2afc
JF
1647 waddch(view->win, '~');
1648 } else {
1649 waddstr(view->win, commit->author);
22f66b0a
JF
1650 }
1651
10e290ee 1652 col += AUTHOR_COLS;
6706b2ba
JF
1653 if (type != LINE_CURSOR)
1654 wattrset(view->win, A_NORMAL);
1655
1656 mvwaddch(view->win, lineno, col, ACS_LTEE);
1657 wmove(view->win, lineno, col + 2);
1658 col += 2;
c34d9c9f
JF
1659
1660 if (commit->refs) {
1661 size_t i = 0;
1662
1663 do {
6706b2ba
JF
1664 if (type == LINE_CURSOR)
1665 ;
1666 else if (commit->refs[i]->tag)
c34d9c9f
JF
1667 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
1668 else
1669 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
1670 waddstr(view->win, "[");
1671 waddstr(view->win, commit->refs[i]->name);
1672 waddstr(view->win, "]");
6706b2ba
JF
1673 if (type != LINE_CURSOR)
1674 wattrset(view->win, A_NORMAL);
c34d9c9f 1675 waddstr(view->win, " ");
6706b2ba 1676 col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
c34d9c9f
JF
1677 } while (commit->refs[i++]->next);
1678 }
1679
6706b2ba
JF
1680 if (type != LINE_CURSOR)
1681 wattrset(view->win, get_line_attr(type));
1682
1683 {
1684 int titlelen = strlen(commit->title);
1685
1686 if (col + titlelen > view->width)
1687 titlelen = view->width - col;
1688
1689 waddnstr(view->win, commit->title, titlelen);
1690 }
22f66b0a
JF
1691
1692 return TRUE;
1693}
1694
4c6fabc2 1695/* Reads git log --pretty=raw output and parses it into the commit struct. */
6b161b31 1696static bool
22f66b0a
JF
1697main_read(struct view *view, char *line)
1698{
78c70acd
JF
1699 enum line_type type = get_line_type(line);
1700 struct commit *commit;
22f66b0a 1701
78c70acd
JF
1702 switch (type) {
1703 case LINE_COMMIT:
22f66b0a
JF
1704 commit = calloc(1, sizeof(struct commit));
1705 if (!commit)
1706 return FALSE;
1707
4c6fabc2 1708 line += STRING_SIZE("commit ");
b76c2afc 1709
22f66b0a 1710 view->line[view->lines++] = commit;
82e78006 1711 string_copy(commit->id, line);
c34d9c9f 1712 commit->refs = get_refs(commit->id);
78c70acd 1713 break;
22f66b0a 1714
8855ada4 1715 case LINE_AUTHOR:
b76c2afc 1716 {
4c6fabc2 1717 char *ident = line + STRING_SIZE("author ");
b76c2afc
JF
1718 char *end = strchr(ident, '<');
1719
1720 if (end) {
1721 for (; end > ident && isspace(end[-1]); end--) ;
1722 *end = 0;
1723 }
1724
1725 commit = view->line[view->lines - 1];
82e78006 1726 string_copy(commit->author, ident);
b76c2afc 1727
4c6fabc2 1728 /* Parse epoch and timezone */
b76c2afc
JF
1729 if (end) {
1730 char *secs = strchr(end + 1, '>');
1731 char *zone;
1732 time_t time;
1733
1734 if (!secs || secs[1] != ' ')
1735 break;
1736
1737 secs += 2;
1738 time = (time_t) atol(secs);
1739 zone = strchr(secs, ' ');
4c6fabc2 1740 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
b76c2afc
JF
1741 long tz;
1742
1743 zone++;
1744 tz = ('0' - zone[1]) * 60 * 60 * 10;
1745 tz += ('0' - zone[2]) * 60 * 60;
1746 tz += ('0' - zone[3]) * 60;
1747 tz += ('0' - zone[4]) * 60;
1748
1749 if (zone[0] == '-')
1750 tz = -tz;
1751
1752 time -= tz;
1753 }
1754 gmtime_r(&time, &commit->time);
1755 }
1756 break;
1757 }
78c70acd 1758 default:
2e8488b4
JF
1759 /* We should only ever end up here if there has already been a
1760 * commit line, however, be safe. */
1761 if (view->lines == 0)
1762 break;
1763
1764 /* Fill in the commit title if it has not already been set. */
78c70acd 1765 commit = view->line[view->lines - 1];
2e8488b4
JF
1766 if (commit->title[0])
1767 break;
1768
1769 /* Require titles to start with a non-space character at the
1770 * offset used by git log. */
eb98559e
JF
1771 /* FIXME: More gracefull handling of titles; append "..." to
1772 * shortened titles, etc. */
2e8488b4 1773 if (strncmp(line, " ", 4) ||
eb98559e 1774 isspace(line[4]))
82e78006
JF
1775 break;
1776
1777 string_copy(commit->title, line + 4);
22f66b0a
JF
1778 }
1779
1780 return TRUE;
1781}
1782
6b161b31
JF
1783static bool
1784main_enter(struct view *view)
b801d8b2 1785{
89e4aafd 1786 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
6b161b31 1787 return TRUE;
b801d8b2
JF
1788}
1789
6b161b31 1790static struct view_ops main_ops = {
6734f6b9 1791 "commit",
6b161b31
JF
1792 main_draw,
1793 main_read,
1794 main_enter,
1795};
2e8488b4 1796
c34d9c9f 1797
468876c9
JF
1798/**
1799 * KEYS
1800 * ----
1801 * Below the default key bindings are shown.
1802 **/
1803
1804struct keymap {
1805 int alias;
1806 int request;
1807};
1808
3a91b75e 1809static struct keymap keymap[] = {
468876c9
JF
1810 /**
1811 * View switching
1812 * ~~~~~~~~~~~~~~
1813 * m::
1814 * Switch to main view.
1815 * d::
1816 * Switch to diff view.
1817 * l::
1818 * Switch to log view.
1819 * p::
1820 * Switch to pager view.
1821 * h::
1822 * Show man page.
4f9b667a
JF
1823 * q::
1824 * Close view if multiple views are open, else quit.
67e48ac5
JF
1825 * Enter::
1826 * This key is "context sensitive" depending on what view you are
1827 * currently in. When in log view on a commit line or in the main
1828 * view, split the view and show the commit diff. In the diff view
1829 * pressing Enter will simply scroll the view one line down.
468876c9
JF
1830 * Tab::
1831 * Switch to next view.
1832 **/
1833 { 'm', REQ_VIEW_MAIN },
1834 { 'd', REQ_VIEW_DIFF },
1835 { 'l', REQ_VIEW_LOG },
1836 { 'p', REQ_VIEW_PAGER },
1837 { 'h', REQ_VIEW_HELP },
1838
4f9b667a 1839 { 'q', REQ_VIEW_CLOSE },
468876c9
JF
1840 { KEY_TAB, REQ_VIEW_NEXT },
1841 { KEY_RETURN, REQ_ENTER },
1842
1843 /**
1844 * Cursor navigation
1845 * ~~~~~~~~~~~~~~~~~
1846 * Up::
57bdf034 1847 * Move cursor one line up.
468876c9
JF
1848 * Down::
1849 * Move cursor one line down.
1850 * k::
57bdf034 1851 * Move cursor one line up and enter. When used in the main view
468876c9
JF
1852 * this will always show the diff of the current commit in the
1853 * split diff view.
1854 * j::
1855 * Move cursor one line down and enter.
1856 * PgUp::
c622eefa 1857 * b::
0df811cb 1858 * -::
57bdf034 1859 * Move cursor one page up.
468876c9 1860 * PgDown::
c622eefa 1861 * Space::
468876c9
JF
1862 * Move cursor one page down.
1863 * Home::
1864 * Jump to first line.
1865 * End::
1866 * Jump to last line.
1867 **/
1868 { KEY_UP, REQ_MOVE_UP },
1869 { KEY_DOWN, REQ_MOVE_DOWN },
1870 { 'k', REQ_MOVE_UP_ENTER },
1871 { 'j', REQ_MOVE_DOWN_ENTER },
1872 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1873 { KEY_END, REQ_MOVE_LAST_LINE },
1874 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
c622eefa 1875 { ' ', REQ_MOVE_PAGE_DOWN },
468876c9 1876 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
c622eefa 1877 { 'b', REQ_MOVE_PAGE_UP },
0df811cb 1878 { '-', REQ_MOVE_PAGE_UP },
468876c9
JF
1879
1880 /**
1881 * Scrolling
1882 * ~~~~~~~~~
1883 * Insert::
1884 * Scroll view one line up.
1885 * Delete::
1886 * Scroll view one line down.
1887 * w::
1888 * Scroll view one page up.
1889 * s::
1890 * Scroll view one page down.
1891 **/
1892 { KEY_IC, REQ_SCROLL_LINE_UP },
1893 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1894 { 'w', REQ_SCROLL_PAGE_UP },
1895 { 's', REQ_SCROLL_PAGE_DOWN },
1896
1897 /**
1898 * Misc
1899 * ~~~~
d9d1c722
JF
1900 * Q::
1901 * Quit.
468876c9
JF
1902 * r::
1903 * Redraw screen.
1904 * z::
1905 * Stop all background loading. This can be useful if you use
1906 * tig(1) in a repository with a long history without limiting
0721c53a 1907 * the revision log.
468876c9
JF
1908 * v::
1909 * Show version.
1910 * n::
1911 * Toggle line numbers on/off.
1912 * ':'::
1913 * Open prompt. This allows you to specify what git command
1914 * to run. Example:
1915 *
1916 * :log -p
1917 **/
d9d1c722 1918 { 'Q', REQ_QUIT },
468876c9
JF
1919 { 'z', REQ_STOP_LOADING },
1920 { 'v', REQ_SHOW_VERSION },
1921 { 'r', REQ_SCREEN_REDRAW },
1922 { 'n', REQ_TOGGLE_LINE_NUMBERS },
1923 { ':', REQ_PROMPT },
1924
1925 /* wgetch() with nodelay() enabled returns ERR when there's no input. */
1926 { ERR, REQ_SCREEN_UPDATE },
1927
1928 /* Use the ncurses SIGWINCH handler. */
1929 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1930};
1931
1932static enum request
1933get_request(int key)
1934{
1935 int i;
1936
1937 for (i = 0; i < ARRAY_SIZE(keymap); i++)
1938 if (keymap[i].alias == key)
1939 return keymap[i].request;
1940
1941 return (enum request) key;
1942}
1943
1944
6b161b31 1945/*
10e290ee
JF
1946 * Unicode / UTF-8 handling
1947 *
1948 * NOTE: Much of the following code for dealing with unicode is derived from
1949 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
1950 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
1951 */
1952
1953/* I've (over)annotated a lot of code snippets because I am not entirely
1954 * confident that the approach taken by this small UTF-8 interface is correct.
1955 * --jonas */
1956
1957static inline int
1958unicode_width(unsigned long c)
1959{
1960 if (c >= 0x1100 &&
1961 (c <= 0x115f /* Hangul Jamo */
1962 || c == 0x2329
1963 || c == 0x232a
1964 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
1965 /* CJK ... Yi */
1966 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
1967 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
1968 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
1969 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
1970 || (c >= 0xffe0 && c <= 0xffe6)
1971 || (c >= 0x20000 && c <= 0x2fffd)
1972 || (c >= 0x30000 && c <= 0x3fffd)))
1973 return 2;
1974
1975 return 1;
1976}
1977
1978/* Number of bytes used for encoding a UTF-8 character indexed by first byte.
1979 * Illegal bytes are set one. */
1980static const unsigned char utf8_bytes[256] = {
1981 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,
1982 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,
1983 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,
1984 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,
1985 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,
1986 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,
1987 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,
1988 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,
1989};
1990
1991/* Decode UTF-8 multi-byte representation into a unicode character. */
1992static inline unsigned long
1993utf8_to_unicode(const char *string, size_t length)
1994{
1995 unsigned long unicode;
1996
1997 switch (length) {
1998 case 1:
1999 unicode = string[0];
2000 break;
2001 case 2:
2002 unicode = (string[0] & 0x1f) << 6;
2003 unicode += (string[1] & 0x3f);
2004 break;
2005 case 3:
2006 unicode = (string[0] & 0x0f) << 12;
2007 unicode += ((string[1] & 0x3f) << 6);
2008 unicode += (string[2] & 0x3f);
2009 break;
2010 case 4:
2011 unicode = (string[0] & 0x0f) << 18;
2012 unicode += ((string[1] & 0x3f) << 12);
2013 unicode += ((string[2] & 0x3f) << 6);
2014 unicode += (string[3] & 0x3f);
2015 break;
2016 case 5:
2017 unicode = (string[0] & 0x0f) << 24;
2018 unicode += ((string[1] & 0x3f) << 18);
2019 unicode += ((string[2] & 0x3f) << 12);
2020 unicode += ((string[3] & 0x3f) << 6);
2021 unicode += (string[4] & 0x3f);
2022 break;
68b6e0eb 2023 case 6:
10e290ee
JF
2024 unicode = (string[0] & 0x01) << 30;
2025 unicode += ((string[1] & 0x3f) << 24);
2026 unicode += ((string[2] & 0x3f) << 18);
2027 unicode += ((string[3] & 0x3f) << 12);
2028 unicode += ((string[4] & 0x3f) << 6);
2029 unicode += (string[5] & 0x3f);
2030 break;
2031 default:
2032 die("Invalid unicode length");
2033 }
2034
2035 /* Invalid characters could return the special 0xfffd value but NUL
2036 * should be just as good. */
2037 return unicode > 0xffff ? 0 : unicode;
2038}
2039
2040/* Calculates how much of string can be shown within the given maximum width
2041 * and sets trimmed parameter to non-zero value if all of string could not be
2042 * shown.
2043 *
2044 * Additionally, adds to coloffset how many many columns to move to align with
2045 * the expected position. Takes into account how multi-byte and double-width
2046 * characters will effect the cursor position.
2047 *
2048 * Returns the number of bytes to output from string to satisfy max_width. */
2049static size_t
2050utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
2051{
2052 const char *start = string;
2053 const char *end = strchr(string, '\0');
2054 size_t mbwidth = 0;
2055 size_t width = 0;
2056
2057 *trimmed = 0;
2058
2059 while (string < end) {
2060 int c = *(unsigned char *) string;
2061 unsigned char bytes = utf8_bytes[c];
2062 size_t ucwidth;
2063 unsigned long unicode;
2064
2065 if (string + bytes > end)
2066 break;
2067
2068 /* Change representation to figure out whether
2069 * it is a single- or double-width character. */
2070
2071 unicode = utf8_to_unicode(string, bytes);
2072 /* FIXME: Graceful handling of invalid unicode character. */
2073 if (!unicode)
2074 break;
2075
2076 ucwidth = unicode_width(unicode);
2077 width += ucwidth;
2078 if (width > max_width) {
2079 *trimmed = 1;
2080 break;
2081 }
2082
2083 /* The column offset collects the differences between the
2084 * number of bytes encoding a character and the number of
2085 * columns will be used for rendering said character.
2086 *
2087 * So if some character A is encoded in 2 bytes, but will be
2088 * represented on the screen using only 1 byte this will and up
2089 * adding 1 to the multi-byte column offset.
2090 *
2091 * Assumes that no double-width character can be encoding in
2092 * less than two bytes. */
2093 if (bytes > ucwidth)
2094 mbwidth += bytes - ucwidth;
2095
2096 string += bytes;
2097 }
2098
2099 *coloffset += mbwidth;
2100
2101 return string - start;
2102}
2103
2104
2105/*
6b161b31
JF
2106 * Status management
2107 */
2e8488b4 2108
8855ada4 2109/* Whether or not the curses interface has been initialized. */
68b6e0eb 2110static bool cursed = FALSE;
8855ada4 2111
6b161b31
JF
2112/* The status window is used for polling keystrokes. */
2113static WINDOW *status_win;
4a2909a7 2114
2e8488b4 2115/* Update status and title window. */
4a2909a7
JF
2116static void
2117report(const char *msg, ...)
2118{
6706b2ba
JF
2119 static bool empty = TRUE;
2120 struct view *view = display[current_view];
b76c2afc 2121
6706b2ba
JF
2122 if (!empty || *msg) {
2123 va_list args;
4a2909a7 2124
6706b2ba 2125 va_start(args, msg);
4b76734f 2126
6706b2ba
JF
2127 werase(status_win);
2128 wmove(status_win, 0, 0);
2129 if (*msg) {
2130 vwprintw(status_win, msg, args);
2131 empty = FALSE;
2132 } else {
2133 empty = TRUE;
2134 }
2135 wrefresh(status_win);
b801d8b2 2136
6706b2ba
JF
2137 va_end(args);
2138 }
2139
2140 update_view_title(view);
2141
2142 /* Move the cursor to the right-most column of the cursor line.
2143 *
2144 * XXX: This could turn out to be a bit expensive, but it ensures that
2145 * the cursor does not jump around. */
2146 if (view->lines) {
2147 wmove(view->win, view->lineno - view->offset, view->width - 1);
2148 wrefresh(view->win);
2149 }
b801d8b2
JF
2150}
2151
6b161b31
JF
2152/* Controls when nodelay should be in effect when polling user input. */
2153static void
1ba2ae4b 2154set_nonblocking_input(bool loading)
b801d8b2 2155{
6706b2ba 2156 static unsigned int loading_views;
b801d8b2 2157
6706b2ba
JF
2158 if ((loading == FALSE && loading_views-- == 1) ||
2159 (loading == TRUE && loading_views++ == 0))
1ba2ae4b 2160 nodelay(status_win, loading);
6b161b31
JF
2161}
2162
2163static void
2164init_display(void)
2165{
2166 int x, y;
b76c2afc 2167
6908bdbd
JF
2168 /* Initialize the curses library */
2169 if (isatty(STDIN_FILENO)) {
8855ada4 2170 cursed = !!initscr();
6908bdbd
JF
2171 } else {
2172 /* Leave stdin and stdout alone when acting as a pager. */
2173 FILE *io = fopen("/dev/tty", "r+");
2174
8855ada4 2175 cursed = !!newterm(NULL, io, io);
6908bdbd
JF
2176 }
2177
8855ada4
JF
2178 if (!cursed)
2179 die("Failed to initialize curses");
2180
2e8488b4
JF
2181 nonl(); /* Tell curses not to do NL->CR/NL on output */
2182 cbreak(); /* Take input chars one at a time, no wait for \n */
2183 noecho(); /* Don't echo input */
b801d8b2 2184 leaveok(stdscr, TRUE);
b801d8b2
JF
2185
2186 if (has_colors())
2187 init_colors();
2188
2189 getmaxyx(stdscr, y, x);
2190 status_win = newwin(1, 0, y - 1, 0);
2191 if (!status_win)
2192 die("Failed to create status window");
2193
2194 /* Enable keyboard mapping */
2195 keypad(status_win, TRUE);
78c70acd 2196 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6b161b31
JF
2197}
2198
c34d9c9f
JF
2199
2200/*
2201 * Repository references
2202 */
2203
2204static struct ref *refs;
3a91b75e 2205static size_t refs_size;
c34d9c9f
JF
2206
2207static struct ref **
2208get_refs(char *id)
2209{
2210 struct ref **id_refs = NULL;
2211 size_t id_refs_size = 0;
2212 size_t i;
2213
2214 for (i = 0; i < refs_size; i++) {
2215 struct ref **tmp;
2216
2217 if (strcmp(id, refs[i].id))
2218 continue;
2219
2220 tmp = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
2221 if (!tmp) {
2222 if (id_refs)
2223 free(id_refs);
2224 return NULL;
2225 }
2226
2227 id_refs = tmp;
3af8774e 2228 if (id_refs_size > 0)
c34d9c9f 2229 id_refs[id_refs_size - 1]->next = 1;
3af8774e
JF
2230 id_refs[id_refs_size] = &refs[i];
2231
2232 /* XXX: The properties of the commit chains ensures that we can
2233 * safely modify the shared ref. The repo references will
2234 * always be similar for the same id. */
2235 id_refs[id_refs_size]->next = 0;
2236 id_refs_size++;
c34d9c9f
JF
2237 }
2238
2239 return id_refs;
2240}
2241
2242static int
2243load_refs(void)
2244{
4685845e
TH
2245 const char *cmd_env = getenv("TIG_LS_REMOTE");
2246 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
c34d9c9f
JF
2247 FILE *pipe = popen(cmd, "r");
2248 char buffer[BUFSIZ];
2249 char *line;
2250
2251 if (!pipe)
2252 return ERR;
2253
2254 while ((line = fgets(buffer, sizeof(buffer), pipe))) {
2255 char *name = strchr(line, '\t');
2256 struct ref *ref;
2257 int namelen;
2258 bool tag = FALSE;
6734f6b9 2259 bool tag_commit = FALSE;
c34d9c9f
JF
2260
2261 if (!name)
2262 continue;
2263
2264 *name++ = 0;
2265 namelen = strlen(name) - 1;
6706b2ba
JF
2266
2267 /* Commits referenced by tags has "^{}" appended. */
c34d9c9f
JF
2268 if (name[namelen - 1] == '}') {
2269 while (namelen > 0 && name[namelen] != '^')
2270 namelen--;
6734f6b9
JF
2271 if (namelen > 0)
2272 tag_commit = TRUE;
c34d9c9f
JF
2273 }
2274 name[namelen] = 0;
2275
2276 if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
6734f6b9
JF
2277 if (!tag_commit)
2278 continue;
c34d9c9f
JF
2279 name += STRING_SIZE("refs/tags/");
2280 tag = TRUE;
3af8774e
JF
2281
2282 } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
2283 name += STRING_SIZE("refs/heads/");
2284
2285 } else if (!strcmp(name, "HEAD")) {
2286 continue;
c34d9c9f
JF
2287 }
2288
2289 refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
2290 if (!refs)
2291 return ERR;
2292
2293 ref = &refs[refs_size++];
2294 ref->tag = tag;
2295 ref->name = strdup(name);
2296 if (!ref->name)
2297 return ERR;
2298
2299 string_copy(ref->id, line);
2300 }
2301
2302 if (ferror(pipe))
2303 return ERR;
2304
2305 pclose(pipe);
2306
1b4b0bd9
JF
2307 if (refs_size == 0)
2308 die("Not a git repository");
2309
c34d9c9f
JF
2310 return OK;
2311}
2312
6b161b31
JF
2313/*
2314 * Main
2315 */
2316
b5c9e67f
TH
2317#if __GNUC__ >= 3
2318#define __NORETURN __attribute__((__noreturn__))
2319#else
2320#define __NORETURN
2321#endif
2322
2323static void __NORETURN
6b161b31
JF
2324quit(int sig)
2325{
8855ada4
JF
2326 /* XXX: Restore tty modes and let the OS cleanup the rest! */
2327 if (cursed)
2328 endwin();
6b161b31
JF
2329 exit(0);
2330}
2331
c6704a4e
JF
2332static void __NORETURN
2333die(const char *err, ...)
6b161b31
JF
2334{
2335 va_list args;
2336
2337 endwin();
2338
2339 va_start(args, err);
2340 fputs("tig: ", stderr);
2341 vfprintf(stderr, err, args);
2342 fputs("\n", stderr);
2343 va_end(args);
2344
2345 exit(1);
2346}
2347
2348int
2349main(int argc, char *argv[])
2350{
1ba2ae4b 2351 struct view *view;
6b161b31 2352 enum request request;
1ba2ae4b 2353 size_t i;
6b161b31
JF
2354
2355 signal(SIGINT, quit);
2356
8855ada4 2357 if (!parse_options(argc, argv))
6b161b31
JF
2358 return 0;
2359
c34d9c9f
JF
2360 if (load_refs() == ERR)
2361 die("Failed to load refs.");
2362
1ba2ae4b
JF
2363 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2364 view->cmd_env = getenv(view->cmd_env);
2365
6b161b31
JF
2366 request = opt_request;
2367
2368 init_display();
b801d8b2
JF
2369
2370 while (view_driver(display[current_view], request)) {
6b161b31 2371 int key;
b801d8b2
JF
2372 int i;
2373
6b161b31
JF
2374 foreach_view (view, i)
2375 update_view(view);
b801d8b2
JF
2376
2377 /* Refresh, accept single keystroke of input */
6b161b31
JF
2378 key = wgetch(status_win);
2379 request = get_request(key);
03a93dbb 2380
6706b2ba 2381 /* Some low-level request handling. This keeps access to
fac7db6c
JF
2382 * status_win restricted. */
2383 switch (request) {
2384 case REQ_PROMPT:
6908bdbd
JF
2385 report(":");
2386 /* Temporarily switch to line-oriented and echoed
2387 * input. */
03a93dbb
JF
2388 nocbreak();
2389 echo();
49f2b43f
JF
2390
2391 if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
2392 memcpy(opt_cmd, "git ", 4);
2393 opt_request = REQ_VIEW_PAGER;
2394 } else {
2395 request = ERR;
2396 }
2397
6908bdbd
JF
2398 noecho();
2399 cbreak();
fac7db6c
JF
2400 break;
2401
2402 case REQ_SCREEN_RESIZE:
2403 {
2404 int height, width;
2405
2406 getmaxyx(stdscr, height, width);
2407
2408 /* Resize the status view and let the view driver take
2409 * care of resizing the displayed views. */
2410 wresize(status_win, 1, width);
2411 mvwin(status_win, height - 1, 0);
2412 wrefresh(status_win);
2413 break;
2414 }
2415 default:
2416 break;
03a93dbb 2417 }
b801d8b2
JF
2418 }
2419
2420 quit(0);
2421
2422 return 0;
2423}
2424
2425/**
0721c53a 2426 * [[refspec]]
3a11b38f
JF
2427 * Revision specification
2428 * ----------------------
0721c53a 2429 * This section describes various ways to specify what revisions to display
3a11b38f
JF
2430 * or otherwise limit the view to. tig(1) does not itself parse the described
2431 * revision options so refer to the relevant git man pages for futher
2432 * information. Relevant man pages besides git-log(1) are git-diff(1) and
2433 * git-rev-list(1).
468876c9 2434 *
3a11b38f
JF
2435 * You can tune the interaction with git by making use of the options
2436 * explained in this section. For example, by configuring the environment
2437 * variables described in the <<view-commands, "View commands">> section.
2438 *
2439 * Limit by path name
2440 * ~~~~~~~~~~~~~~~~~~
ab46037d
JF
2441 * If you are interested only in those revisions that made changes to a
2442 * specific file (or even several files) list the files like this:
2443 *
c760e6ba 2444 * $ tig log Makefile README
ab46037d
JF
2445 *
2446 * To avoid ambiguity with repository references such as tag name, be sure
2447 * to separate file names from other git options using "\--". So if you
2448 * have a file named 'master' it will clash with the reference named
2449 * 'master', and thus you will have to use:
2450 *
1b4b0bd9 2451 * $ tig log -- master
ab46037d
JF
2452 *
2453 * NOTE: For the main view, avoiding ambiguity will in some cases require
2454 * you to specify two "\--" options. The first will make tig(1) stop
2455 * option processing and the latter will be passed to git log.
2456 *
468876c9
JF
2457 * Limit by date or number
2458 * ~~~~~~~~~~~~~~~~~~~~~~~
2459 * To speed up interaction with git, you can limit the amount of commits
2460 * to show both for the log and main view. Either limit by date using
2461 * e.g. `--since=1.month` or limit by the number of commits using `-n400`.
2462 *
3a11b38f
JF
2463 * If you are only interested in changed that happened between two dates
2464 * you can use:
2465 *
c760e6ba 2466 * $ tig -- --after="May 5th" --before="2006-05-16 15:44"
468876c9 2467 *
c760e6ba
JF
2468 * NOTE: If you want to avoid having to quote dates containing spaces you
2469 * can use "." instead, e.g. `--after=May.5th`.
3a11b38f
JF
2470 *
2471 * Limiting by commit ranges
2472 * ~~~~~~~~~~~~~~~~~~~~~~~~~
468876c9
JF
2473 * Alternatively, commits can be limited to a specific range, such as
2474 * "all commits between 'tag-1.0' and 'tag-2.0'". For example:
2475 *
2476 * $ tig log tag-1.0..tag-2.0
2477 *
2478 * This way of commit limiting makes it trivial to only browse the commits
2479 * which haven't been pushed to a remote branch. Assuming 'origin' is your
2480 * upstream remote branch, using:
2481 *
2482 * $ tig log origin..HEAD
2483 *
2484 * will list what will be pushed to the remote branch. Optionally, the ending
2485 * 'HEAD' can be left out since it is implied.
2486 *
2487 * Limiting by reachability
2488 * ~~~~~~~~~~~~~~~~~~~~~~~~
2489 * Git interprets the range specifier "tag-1.0..tag-2.0" as
2490 * "all commits reachable from 'tag-2.0' but not from 'tag-1.0'".
3a11b38f
JF
2491 * Where reachability refers to what commits are ancestors (or part of the
2492 * history) of the branch or tagged revision in question.
2493 *
468876c9
JF
2494 * If you prefer to specify which commit to preview in this way use the
2495 * following:
2496 *
2497 * $ tig log tag-2.0 ^tag-1.0
2498 *
57bdf034
JF
2499 * You can think of '^' as a negation operator. Using this alternate syntax,
2500 * it is possible to further prune commits by specifying multiple branch
2501 * cut offs.
468876c9 2502 *
3a11b38f
JF
2503 * Combining revisions specification
2504 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2505 * Revisions options can to some degree be combined, which makes it possible
2506 * to say "show at most 20 commits from within the last month that changed
2507 * files under the Documentation/ directory."
2508 *
2509 * $ tig -- --since=1.month -n20 -- Documentation/
2510 *
2511 * Examining all repository references
2512 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2513 * In some cases, it can be useful to query changes across all references
2514 * in a repository. An example is to ask "did any line of development in
2515 * this repository change a particular file within the last week". This
2516 * can be accomplished using:
2517 *
2518 * $ tig -- --all --since=1.week -- Makefile
2519 *
6706b2ba
JF
2520 * BUGS
2521 * ----
2522 * Known bugs and problems:
2523 *
10e290ee
JF
2524 * - In it's current state tig is pretty much UTF-8 only.
2525 *
6706b2ba 2526 * - If the screen width is very small the main view can draw
468876c9
JF
2527 * outside the current view causing bad wrapping. Same goes
2528 * for title and status windows.
6706b2ba 2529 *
4c6fabc2
JF
2530 * TODO
2531 * ----
2532 * Features that should be explored.
2533 *
fac7db6c 2534 * - Searching.
4c6fabc2
JF
2535 *
2536 * - Locale support.
2537 *
b801d8b2
JF
2538 * COPYRIGHT
2539 * ---------
4a2909a7 2540 * Copyright (c) Jonas Fonseca <fonseca@diku.dk>, 2006
b801d8b2
JF
2541 *
2542 * This program is free software; you can redistribute it and/or modify
2543 * it under the terms of the GNU General Public License as published by
2544 * the Free Software Foundation; either version 2 of the License, or
2545 * (at your option) any later version.
2546 *
2547 * SEE ALSO
2548 * --------
4c6fabc2 2549 * [verse]
b801d8b2
JF
2550 * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
2551 * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
4c6fabc2 2552 * gitk(1): git repository browser written using tcl/tk,
c6704a4e 2553 * qgit(1): git repository browser written using c++/Qt,
4c6fabc2 2554 * gitview(1): git repository browser written using python/gtk.
b801d8b2 2555 **/