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