{"thread":{"id":"4183","subject":"[ANNOUNCE] tig - text-mode interface for git","startedAt":"2006-05-17T12:07:33Z","lastAt":"2006-05-17T15:53:10Z","messageCount":5,"participants":["Jonas Fonseca","Jakub Narebski","Timo Hirvonen"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"20136","messageId":"20060517120733.GA14041@diku.dk","threadId":"4183","inReplyTo":null,"subject":"[ANNOUNCE] tig - text-mode interface for git","fromName":"Jonas Fonseca","fromEmail":"fonseca@diku.dk","sentAt":"2006-05-17T12:07:33Z","receivedAt":"2006-05-17T12:07:33Z","isPatch":false,"sender":{"key":"fonseca@diku.dk","avatar":"https://gravatar.com/avatar/f82f3ad698717c51873b020c750a92438c820a24056dc39fe4d07baa10a92264?d=mp&s=160"},"body":"Hello,\n\nI am pleased to announce tig, a simple git repository browser written\nusing ncurses. Basically, it just acts as a front-end for git-log and\ngit-show/git-diff. Additionally, you can also use it as a pager for git\ncommands.\n\nCurrently, it just provides a minimum support for looking through\nchanges. I hope to slowly extend it to also be usable as a front-end for\ngit-blame and for tree browsing. Also, it doesn't do any fancy revision\ngraph rendering, but I want to at least give it a shot at some point. :)\n\nFollowing is the man page. I've attached the tig.c file if you want to\ntry it out. Compile it with:\n\n\t$ cc -o tig tig.c -lncurses\n\nMore project files (including a Makefile) can be found at:\n\n\thttp://jonas.nitro.dk/tig/\n\nObligatory screenshots at:\n\n\thttp://jonas.nitro.dk/tig/screenshots/\n\n---\n\nNAME\n\n   tig - text-mode interface for git\n\nSYNOPSIS\n\n   tig [options]\n   tig [options] [--] [git log options]\n   tig [options] log  [git log options]\n   tig [options] diff [git diff options]\n   tig [options] show [git show options]\n   tig [options] <    [git command output]\n\nDESCRIPTION\n\n   Browse changes in a git repository. Additionally, tig(1) can also act\n   as a pager for output of various git commands.\n\n   When browsing repositories, tig(1) uses the underlying git commands\n   to present the user with various views, such as summarized commit log\n   and showing the commit with the log message, diffstat, and the diff.\n\n   Using tig(1) as a pager, it will display input from stdin and try to\n   colorize it.\n\nOPTIONS\n\n   -l\n           Start up in log view using the internal log command.\n\n   -d\n           Start up in diff view using the internal diff command.\n\n   -n[INTERVAL], --line-number[=INTERVAL]\n           Prefix line numbers in log and diff view. Optionally, with\n           interval different than each line.\n\n   -t[NSPACES], --tab-size[=NSPACES]\n           Set the number of spaces tabs should be expanded to.\n\n   -v, --version\n           Show version and exit.\n\n   --\n           End of tig(1) options. Useful when specifying command options\n           for the main view. Example:\n\n\t\t$ tig -- --since=1.month\n\n   log [git log options]\n           Open log view using the given git log options.\n\n   diff [git diff options]\n           Open diff view using the given git diff options.\n\n   show [git show options]\n           Open diff view using the given git show options.\n\n   [git log options]\n           tig(1) will stop the option parsing when the first command\n           line parameter not starting with \"-\" is encountered. All\n           options including this one will be passed to git log when\n           loading the main view. This makes it possible to say:\n\n\t\t$ tig tag-1.0..HEAD\n\n  Pager mode\n\n   If stdin is a pipe, any log or diff options will be ignored and the\n   pager view will be opened loading data from stdin. The pager mode can\n   be used for colorizing output from various git commands.\n\n   Example on how to colorize the output of git-show(1):\n\n\t$ git show | tig\n\n  Git command options\n\n   All git command options specified on the command line will be passed\n   to the given command and all will be shell quoted before they are\n   passed to the shell.\n\n        If you specify options for the main view, you should not use the\n   Note --pretty option as this option will be set automatically to the\n        format expected by the main view.\n\n   Example on how to open the log view and show both author and\n   committer information:\n\n\t$ tig log --pretty=fuller\n\n   See the \"Specifying revisions\" section below for an introduction to\n   revision options supported by the git commands. For details on\n   specific git command options, refer to the man page of the command in\n   question.\n\nENVIRONMENT VARIABLES\n\n   Several options related to the interface with git can be configured\n   via environment options.\n\n  Repository references\n\n   Commits that are referenced by tags and branch heads will be marked\n   by the reference name surrounded by [ and ]:\n\n\t2006-03-26 19:42 Petr Baudis       | [cogito-0.17.1] Cogito 0.17.1\n\n   If you want to filter out certain directories under .git/refs/, say\n   tmp you can do it by setting the following variable:\n\n\t$ TIG_LS_REMOTE=\"git ls-remote . | sed /\\/tmp\\//d\" tig\n\n   Or set the variable permanently in your environment.\n\n   TIG_LS_REMOTE\n\n           Set command for retrieving all repository references. The\n           command should output data in the same format as\n           git-ls-remote(1).\n\n  View commands\n\n   It is possible to alter which commands are used for the different\n   views. If for example you prefer commits in the main view to be\n   sorted by date and only show 500 commits, use:\n\n\t$ TIG_MAIN_CMD=\"git log --date-order -n500 --pretty=raw %s\" tig\n\n   Or set the variable permanently in your environment.\n\n   Notice, how %s is used to specify the commit reference. There can be\n   a maximum of 5 %s ref specifications.\n\n   TIG_DIFF_CMD\n\n           The command used for the diff view. By default, git show is\n           used as a backend.\n\n   TIG_LOG_CMD\n\n           The command used for the log view. If you prefer to have both\n           author and committer shown in the log view be sure to pass\n           --pretty=fuller to git log.\n\n   TIG_MAIN_CMD\n\n           The command used for the main view. Note, you must always\n           specify the option: --pretty=raw since the main view parser\n           expects to read that format.\n\nThe viewer\n\n   tig(1) presents various views of a repository. Each view is based on\n   output from an external command, most often git log, git diff, or git\n   show.\n\n   The main view\n           Is the default view, and it shows a one line summary of each\n           commit in the chosen list of revisions. The summary includes\n           commit date, author, and the first line of the log message.\n           Additionally, any repository references, such as tags, will\n           be shown.\n\n   The log view\n           Presents a more rich view of the revision log showing the\n           whole log message and the diffstat.\n\n   The diff view\n           Shows either the diff of the current working tree, that is,\n           what has changed since the last commit, or the commit diff\n           complete with log message, diffstat and diff.\n\n   The pager view\n           Is used for displaying both input from stdin and output from\n           git commands entered in the internal prompt.\n\n   The help view\n           Displays the information from the tig(1) man page. For the\n           help view to work you need to have the tig(1) man page\n           installed.\n\nKEYS\n\n   Below the default key bindings are shown.\n\n  View switching\n\n   m       Switch to main view.\n   d       Switch to diff view.\n   l       Switch to log view.\n   p       Switch to pager view.\n   h       Show man page.\n   Return  If on a commit line show the commit diff. Additionally, if in\n           main or log view this will split the view. To open the commit\n           diff in full size view either use d or press Return twice.\n   Tab     Switch to next view.\n\n  Cursor navigation\n\n   Up      Move cursor one line up.\n   Down    Move cursor one line down.\n   k       Move cursor one line up and enter. When used in the main view\n           this will always show the diff of the current commit in the\n           split diff view.\n   j       Move cursor one line down and enter.\n   PgUp    Move cursor one page up.\n   PgDown  Move cursor one page down.\n   Home    Jump to first line.\n   End     Jump to last line.\n\n  Scrolling\n\n   Insert  Scroll view one line up.\n   Delete  Scroll view one line down.\n   w       Scroll view one page up.\n   s       Scroll view one page down.\n\n  Misc\n\n   q       Quit\n   r       Redraw screen.\n   z       Stop all background loading. This can be useful if you use\n           tig(1) in a repository with a long history without limiting\n           the revision log.\n   v       Show version.\n   n       Toggle line numbers on/off.\n   :       Open prompt. This allows you to specify what git command to\n           run. Example:\n\n\t\t:log -p\n\nRevision specification\n\n   This section describes various ways to specify what revisions to\n   display or otherwise limit the view to. tig(1) does not itself parse\n   the described revision options so refer to the relevant git man pages\n   for futher information. Relevant man pages besides git-log(1) are\n   git-diff(1) and git-rev-list(1).\n\n   You can tune the interaction with git by making use of the options\n   explained in this section. For example, by configuring the\n   environment variables described in the \"View commands\" section.\n\n  Limit by path name\n\n   If you are interested only in those revisions that made changes to a\n   specific file (or even several files) list the files like this:\n\n\t$ tig log Makefile\n\n   To avoid ambiguity with repository references such as tag name, be\n   sure to separate file names from other git options using \"--\". So if\n   you have a file named master it will clash with the reference named\n   master, and thus you will have to use:\n\n\t$ tig log -- master\n\n        For the main view, avoiding ambiguity will in some cases require\n   Note you to specify two \"--\" options. The first will make tig(1) stop\n        option processing and the latter will be passed to git log.\n\n  Limit by date or number\n\n   To speed up interaction with git, you can limit the amount of commits\n   to show both for the log and main view. Either limit by date using\n   e.g. --since=1.month or limit by the number of commits using -n400.\n\n   If you are only interested in changed that happened between two dates\n   you can use:\n\n\t$ tig -- --after=May.5th --before=2006-05-16.15:44\n\n        The dot (\".\") is used as a separator instead of a space to avoid\n   Note having to quote the option value. If you prefer use --after=\"May\n        5th\" instead of --after=\"May 5th\".\n\n  Limiting by commit ranges\n\n   Alternatively, commits can be limited to a specific range, such as\n   \"all commits between tag-1.0 and tag-2.0\". For example:\n\n\t$ tig log tag-1.0..tag-2.0\n\n   This way of commit limiting makes it trivial to only browse the\n   commits which haven't been pushed to a remote branch. Assuming origin\n   is your upstream remote branch, using:\n\n\t$ tig log origin..HEAD\n\n   will list what will be pushed to the remote branch. Optionally, the\n   ending HEAD can be left out since it is implied.\n\n  Limiting by reachability\n\n   Git interprets the range specifier \"tag-1.0..tag-2.0\" as \"all commits\n   reachable from tag-2.0 but not from tag-1.0\". Where reachability\n   refers to what commits are ancestors (or part of the history) of the\n   branch or tagged revision in question.\n\n   If you prefer to specify which commit to preview in this way use the\n   following:\n\n\t$ tig log tag-2.0 ^tag-1.0\n\n   You can think of ^ as a negation operator. Using this alternate\n   syntax, it is possible to further prune commits by specifying\n   multiple branch cut offs.\n\n  Combining revisions specification\n\n   Revisions options can to some degree be combined, which makes it\n   possible to say \"show at most 20 commits from within the last month\n   that changed files under the Documentation/ directory.\"\n\n\t$ tig -- --since=1.month -n20 -- Documentation/\n\n  Examining all repository references\n\n   In some cases, it can be useful to query changes across all\n   references in a repository. An example is to ask \"did any line of\n   development in this repository change a particular file within the\n   last week\". This can be accomplished using:\n\n\t$ tig -- --all --since=1.week -- Makefile\n\nBUGS\n\n   Known bugs and problems:\n\n     * If the screen width is very small the main view can draw outside\n       the current view causing bad wrapping. Same goes for title and\n       status windows.\n\nTODO\n\n   Features that should be explored.\n\n     * Searching.\n\n     * Locale support.\n\nCOPYRIGHT\n\n   Copyright (c) Jonas Fonseca <fonseca@diku.dk>, 2006\n\n   This program is free software; you can redistribute it and/or modify\n   it under the terms of the GNU General Public License as published by\n   the Free Software Foundation; either version 2 of the License, or (at\n   your option) any later version.\n\nSEE ALSO\n\n   git(7),\n   cogito(7)\n   gitk(1): git repository browser written using tcl/tk,\n   gitview(1): git repository browser written using python/gtk.\n\n-- \nJonas Fonseca\n"},{"id":"20137","messageId":"20060517120923.GB14041@diku.dk","threadId":"4183","inReplyTo":"20060517120733.GA14041@diku.dk","subject":"tig.c","fromName":"Jonas Fonseca","fromEmail":"fonseca@diku.dk","sentAt":"2006-05-17T12:09:23Z","receivedAt":"2006-05-17T12:09:23Z","isPatch":false,"sender":{"key":"fonseca@diku.dk","avatar":"https://gravatar.com/avatar/f82f3ad698717c51873b020c750a92438c820a24056dc39fe4d07baa10a92264?d=mp&s=160"},"body":"/* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>\n * See license info at the bottom. */\n/**\n * TIG(1)\n * ======\n *\n * NAME\n * ----\n * tig - text-mode interface for git\n *\n * SYNOPSIS\n * --------\n * [verse]\n * tig [options]\n * tig [options] [--] [git log options]\n * tig [options] log  [git log options]\n * tig [options] diff [git diff options]\n * tig [options] show [git show options]\n * tig [options] <    [git command output]\n *\n * DESCRIPTION\n * -----------\n * Browse changes in a git repository. Additionally, tig(1) can also act\n * as a pager for output of various git commands.\n *\n * When browsing repositories, tig(1) uses the underlying git commands\n * to present the user with various views, such as summarized commit log\n * and showing the commit with the log message, diffstat, and the diff.\n *\n * Using tig(1) as a pager, it will display input from stdin and try\n * to colorize it.\n **/\n\n#ifndef\tVERSION\n#define VERSION\t\"tig-0.3\"\n#endif\n\n#ifndef DEBUG\n#define NDEBUG\n#endif\n\n#include <assert.h>\n#include <errno.h>\n#include <ctype.h>\n#include <signal.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <time.h>\n\n#include <curses.h>\n\nstatic void die(const char *err, ...);\nstatic void report(const char *msg, ...);\nstatic void set_nonblocking_input(bool loading);\n\n#define ABS(x)\t\t((x) >= 0  ? (x) : -(x))\n#define MIN(x, y)\t((x) < (y) ? (x) :  (y))\n\n#define ARRAY_SIZE(x)\t(sizeof(x) / sizeof(x[0]))\n#define STRING_SIZE(x)\t(sizeof(x) - 1)\n\n#define SIZEOF_REF\t256\t/* Size of symbolic or SHA1 ID. */\n#define SIZEOF_CMD\t1024\t/* Size of command buffer. */\n\n/* This color name can be used to refer to the default term colors. */\n#define COLOR_DEFAULT\t(-1)\n\n#define TIG_HELP\t\"(d)iff, (l)og, (m)ain, (q)uit, (h)elp, (Enter) show diff\"\n\n/* The format and size of the date column in the main view. */\n#define DATE_FORMAT\t\"%Y-%m-%d %H:%M\"\n#define DATE_COLS\tSTRING_SIZE(\"2006-04-29 14:21 \")\n\n/* The default interval between line numbers. */\n#define NUMBER_INTERVAL\t1\n\n#define TABSIZE\t\t8\n\n#define\tSCALE_SPLIT_VIEW(height)\t((height) * 2 / 3)\n\n/* Some ascii-shorthands fitted into the ncurses namespace. */\n#define KEY_TAB\t\t'\\t'\n#define KEY_RETURN\t'\\r'\n#define KEY_ESC\t\t27\n\n\n/* User action requests. */\nenum request {\n\t/* Offset all requests to avoid conflicts with ncurses getch values. */\n\tREQ_OFFSET = KEY_MAX + 1,\n\n\t/* XXX: Keep the view request first and in sync with views[]. */\n\tREQ_VIEW_MAIN,\n\tREQ_VIEW_DIFF,\n\tREQ_VIEW_LOG,\n\tREQ_VIEW_HELP,\n\tREQ_VIEW_PAGER,\n\n\tREQ_ENTER,\n\tREQ_QUIT,\n\tREQ_PROMPT,\n\tREQ_SCREEN_REDRAW,\n\tREQ_SCREEN_RESIZE,\n\tREQ_SCREEN_UPDATE,\n\tREQ_SHOW_VERSION,\n\tREQ_STOP_LOADING,\n\tREQ_TOGGLE_LINE_NUMBERS,\n\tREQ_VIEW_NEXT,\n\n\tREQ_MOVE_UP,\n\tREQ_MOVE_UP_ENTER,\n\tREQ_MOVE_DOWN,\n\tREQ_MOVE_DOWN_ENTER,\n\tREQ_MOVE_PAGE_UP,\n\tREQ_MOVE_PAGE_DOWN,\n\tREQ_MOVE_FIRST_LINE,\n\tREQ_MOVE_LAST_LINE,\n\n\tREQ_SCROLL_LINE_UP,\n\tREQ_SCROLL_LINE_DOWN,\n\tREQ_SCROLL_PAGE_UP,\n\tREQ_SCROLL_PAGE_DOWN,\n};\n\nstruct ref {\n\tchar *name;\t\t/* Ref name; tag or head names are shortened. */\n\tchar id[41];\t\t/* Commit SHA1 ID */\n\tunsigned int tag:1;\t/* Is it a tag? */\n\tunsigned int next:1;\t/* For ref lists: are there more refs? */\n};\n\nstruct commit {\n\tchar id[41];\t\t/* SHA1 ID. */\n\tchar title[75];\t\t/* The first line of the commit message. */\n\tchar author[75];\t/* The author of the commit. */\n\tstruct tm time;\t\t/* Date from the author ident. */\n\tstruct ref **refs;\t/* Repository references; tags & branch heads. */\n};\n\n\n/*\n * String helpers\n */\n\nstatic inline void\nstring_ncopy(char *dst, char *src, int dstlen)\n{\n\tstrncpy(dst, src, dstlen - 1);\n\tdst[dstlen - 1] = 0;\n\n}\n\n/* Shorthand for safely copying into a fixed buffer. */\n#define string_copy(dst, src) \\\n\tstring_ncopy(dst, src, sizeof(dst))\n\n\n/* Shell quoting\n *\n * NOTE: The following is a slightly modified copy of the git project's shell\n * quoting routines found in the quote.c file.\n *\n * Help to copy the thing properly quoted for the shell safety.  any single\n * quote is replaced with '\\'', any exclamation point is replaced with '\\!',\n * and the whole thing is enclosed in a\n *\n * E.g.\n *  original     sq_quote     result\n *  name     ==> name      ==> 'name'\n *  a b      ==> a b       ==> 'a b'\n *  a'b      ==> a'\\''b    ==> 'a'\\''b'\n *  a!b      ==> a'\\!'b    ==> 'a'\\!'b'\n */\n\nstatic size_t\nsq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src)\n{\n\tchar c;\n\n#define BUFPUT(x) ( (bufsize < SIZEOF_CMD) && (buf[bufsize++] = (x)) )\n\n\tBUFPUT('\\'');\n\twhile ((c = *src++)) {\n\t\tif (c == '\\'' || c == '!') {\n\t\t\tBUFPUT('\\'');\n\t\t\tBUFPUT('\\\\');\n\t\t\tBUFPUT(c);\n\t\t\tBUFPUT('\\'');\n\t\t} else {\n\t\t\tBUFPUT(c);\n\t\t}\n\t}\n\tBUFPUT('\\'');\n\n\treturn bufsize;\n}\n\n\n/**\n * OPTIONS\n * -------\n **/\n\n/* Option and state variables. */\nstatic bool opt_line_number\t= FALSE;\nstatic int opt_num_interval\t= NUMBER_INTERVAL;\nstatic int opt_tab_size\t\t= TABSIZE;\nstatic enum request opt_request = REQ_VIEW_MAIN;\nstatic char opt_cmd[SIZEOF_CMD]\t= \"\";\nstatic FILE *opt_pipe\t\t= NULL;\n\n/* Returns the index of log or diff command or -1 to exit. */\nstatic bool\nparse_options(int argc, char *argv[])\n{\n\tint i;\n\n\tfor (i = 1; i < argc; i++) {\n\t\tchar *opt = argv[i];\n\n\t\t/**\n\t\t * -l::\n\t\t *\tStart up in log view using the internal log command.\n\t\t **/\n\t\tif (!strcmp(opt, \"-l\")) {\n\t\t\topt_request = REQ_VIEW_LOG;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/**\n\t\t * -d::\n\t\t *\tStart up in diff view using the internal diff command.\n\t\t **/\n\t\tif (!strcmp(opt, \"-d\")) {\n\t\t\topt_request = REQ_VIEW_DIFF;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/**\n\t\t * -n[INTERVAL], --line-number[=INTERVAL]::\n\t\t *\tPrefix line numbers in log and diff view.\n\t\t *\tOptionally, with interval different than each line.\n\t\t **/\n\t\tif (!strncmp(opt, \"-n\", 2) ||\n\t\t    !strncmp(opt, \"--line-number\", 13)) {\n\t\t\tchar *num = opt;\n\n\t\t\tif (opt[1] == 'n') {\n\t\t\t\tnum = opt + 2;\n\n\t\t\t} else if (opt[STRING_SIZE(\"--line-number\")] == '=') {\n\t\t\t\tnum = opt + STRING_SIZE(\"--line-number=\");\n\t\t\t}\n\n\t\t\tif (isdigit(*num))\n\t\t\t\topt_num_interval = atoi(num);\n\n\t\t\topt_line_number = TRUE;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/**\n\t\t * -t[NSPACES], --tab-size[=NSPACES]::\n\t\t *\tSet the number of spaces tabs should be expanded to.\n\t\t **/\n\t\tif (!strncmp(opt, \"-t\", 2) ||\n\t\t    !strncmp(opt, \"--tab-size\", 10)) {\n\t\t\tchar *num = opt;\n\n\t\t\tif (opt[1] == 't') {\n\t\t\t\tnum = opt + 2;\n\n\t\t\t} else if (opt[STRING_SIZE(\"--tab-size\")] == '=') {\n\t\t\t\tnum = opt + STRING_SIZE(\"--tab-size=\");\n\t\t\t}\n\n\t\t\tif (isdigit(*num))\n\t\t\t\topt_tab_size = MIN(atoi(num), TABSIZE);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/**\n\t\t * -v, --version::\n\t\t *\tShow version and exit.\n\t\t **/\n\t\tif (!strcmp(opt, \"-v\") ||\n\t\t    !strcmp(opt, \"--version\")) {\n\t\t\tprintf(\"tig version %s\\n\", VERSION);\n\t\t\treturn FALSE;\n\t\t}\n\n\t\t/**\n\t\t * \\--::\n\t\t *\tEnd of tig(1) options. Useful when specifying command\n\t\t *\toptions for the main view. Example:\n\t\t *\n\t\t *\t\t$ tig -- --since=1.month\n\t\t **/\n\t\tif (!strcmp(opt, \"--\")) {\n\t\t\ti++;\n\t\t\tbreak;\n\t\t}\n\n\t\t/**\n\t\t * log [git log options]::\n\t\t *\tOpen log view using the given git log options.\n\t\t *\n\t\t * diff [git diff options]::\n\t\t *\tOpen diff view using the given git diff options.\n\t\t *\n\t\t * show [git show options]::\n\t\t *\tOpen diff view using the given git show options.\n\t\t **/\n\t\tif (!strcmp(opt, \"log\") ||\n\t\t    !strcmp(opt, \"diff\") ||\n\t\t    !strcmp(opt, \"show\")) {\n\t\t\topt_request = opt[0] == 'l'\n\t\t\t\t    ? REQ_VIEW_LOG : REQ_VIEW_DIFF;\n\t\t\tbreak;\n\t\t}\n\n\t\t/**\n\t\t * [git log options]::\n\t\t *\ttig(1) will stop the option parsing when the first\n\t\t *\tcommand line parameter not starting with \"-\" is\n\t\t *\tencountered. All options including this one will be\n\t\t *\tpassed to git log when loading the main view.\n\t\t *\tThis makes it possible to say:\n\t\t *\n\t\t *\t$ tig tag-1.0..HEAD\n\t\t **/\n\t\tif (opt[0] && opt[0] != '-')\n\t\t\tbreak;\n\n\t\tdie(\"unknown command '%s'\", opt);\n\t}\n\n\tif (!isatty(STDIN_FILENO)) {\n\t\t/**\n\t\t * Pager mode\n\t\t * ~~~~~~~~~~\n\t\t * If stdin is a pipe, any log or diff options will be ignored and the\n\t\t * pager view will be opened loading data from stdin. The pager mode\n\t\t * can be used for colorizing output from various git commands.\n\t\t *\n\t\t * Example on how to colorize the output of git-show(1):\n\t\t *\n\t\t *\t$ git show | tig\n\t\t **/\n\t\topt_request = REQ_VIEW_PAGER;\n\t\topt_pipe = stdin;\n\n\t} else if (i < argc) {\n\t\tsize_t buf_size;\n\n\t\t/**\n\t\t * Git command options\n\t\t * ~~~~~~~~~~~~~~~~~~~\n\t\t * All git command options specified on the command line will\n\t\t * be passed to the given command and all will be shell quoted\n\t\t * before they are passed to the shell.\n\t\t *\n\t\t * NOTE: If you specify options for the main view, you should\n\t\t * not use the `--pretty` option as this option will be set\n\t\t * automatically to the format expected by the main view.\n\t\t *\n\t\t * Example on how to open the log view and show both author and\n\t\t * committer information:\n\t\t *\n\t\t *\t$ tig log --pretty=fuller\n\t\t *\n\t\t * See the <<refspec, \"Specifying revisions\">> section below\n\t\t * for an introduction to revision options supported by the git\n\t\t * commands. For details on specific git command options, refer\n\t\t * to the man page of the command in question.\n\t\t **/\n\n\t\tif (opt_request == REQ_VIEW_MAIN)\n\t\t\t/* XXX: This is vulnerable to the user overriding\n\t\t\t * options required for the main view parser. */\n\t\t\tstring_copy(opt_cmd, \"git log --stat --pretty=raw\");\n\t\telse\n\t\t\tstring_copy(opt_cmd, \"git\");\n\t\tbuf_size = strlen(opt_cmd);\n\n\t\twhile (buf_size < sizeof(opt_cmd) && i < argc) {\n\t\t\topt_cmd[buf_size++] = ' ';\n\t\t\tbuf_size = sq_quote(opt_cmd, buf_size, argv[i++]);\n\t\t}\n\n\t\tif (buf_size >= sizeof(opt_cmd))\n\t\t\tdie(\"command too long\");\n\n\t\topt_cmd[buf_size] = 0;\n\n\t}\n\n\treturn TRUE;\n}\n\n\n/*\n * Line-oriented content detection.\n */\n\n#define LINE_INFO \\\n/*   Line type\t   String to match\tForeground\tBackground\tAttributes\n *   ---------     ---------------      ----------      ----------      ---------- */ \\\n/* Diff markup */ \\\nLINE(DIFF,\t   \"diff --git \",\tCOLOR_YELLOW,\tCOLOR_DEFAULT,\t0), \\\nLINE(DIFF_INDEX,   \"index \",\t\tCOLOR_BLUE,\tCOLOR_DEFAULT,\t0), \\\nLINE(DIFF_CHUNK,   \"@@\",\t\tCOLOR_MAGENTA,\tCOLOR_DEFAULT,\t0), \\\nLINE(DIFF_ADD,\t   \"+\",\t\t\tCOLOR_GREEN,\tCOLOR_DEFAULT,\t0), \\\nLINE(DIFF_DEL,\t   \"-\",\t\t\tCOLOR_RED,\tCOLOR_DEFAULT,\t0), \\\nLINE(DIFF_OLDMODE, \"old file mode \",\tCOLOR_YELLOW,\tCOLOR_DEFAULT,\t0), \\\nLINE(DIFF_NEWMODE, \"new file mode \",\tCOLOR_YELLOW,\tCOLOR_DEFAULT,\t0), \\\nLINE(DIFF_COPY,\t   \"copy \",\t\tCOLOR_YELLOW,\tCOLOR_DEFAULT,\t0), \\\nLINE(DIFF_RENAME,  \"rename \",\t\tCOLOR_YELLOW,\tCOLOR_DEFAULT,\t0), \\\nLINE(DIFF_SIM,\t   \"similarity \",\tCOLOR_YELLOW,\tCOLOR_DEFAULT,\t0), \\\nLINE(DIFF_DISSIM,  \"dissimilarity \",\tCOLOR_YELLOW,\tCOLOR_DEFAULT,\t0), \\\n/* Pretty print commit header */ \\\nLINE(PP_AUTHOR,\t   \"Author: \",\t\tCOLOR_CYAN,\tCOLOR_DEFAULT,\t0), \\\nLINE(PP_COMMIT,\t   \"Commit: \",\t\tCOLOR_MAGENTA,\tCOLOR_DEFAULT,\t0), \\\nLINE(PP_MERGE,\t   \"Merge: \",\t\tCOLOR_BLUE,\tCOLOR_DEFAULT,\t0), \\\nLINE(PP_DATE,\t   \"Date:   \",\t\tCOLOR_YELLOW,\tCOLOR_DEFAULT,\t0), \\\nLINE(PP_ADATE,\t   \"AuthorDate: \",\tCOLOR_YELLOW,\tCOLOR_DEFAULT,\t0), \\\nLINE(PP_CDATE,\t   \"CommitDate: \",\tCOLOR_YELLOW,\tCOLOR_DEFAULT,\t0), \\\n/* Raw commit header */ \\\nLINE(COMMIT,\t   \"commit \",\t\tCOLOR_GREEN,\tCOLOR_DEFAULT,\t0), \\\nLINE(PARENT,\t   \"parent \",\t\tCOLOR_BLUE,\tCOLOR_DEFAULT,\t0), \\\nLINE(TREE,\t   \"tree \",\t\tCOLOR_BLUE,\tCOLOR_DEFAULT,\t0), \\\nLINE(AUTHOR,\t   \"author \",\t\tCOLOR_CYAN,\tCOLOR_DEFAULT,\t0), \\\nLINE(COMMITTER,\t   \"committer \",\tCOLOR_MAGENTA,\tCOLOR_DEFAULT,\t0), \\\n/* Misc */ \\\nLINE(DIFF_TREE,\t   \"diff-tree \",\tCOLOR_BLUE,\tCOLOR_DEFAULT,\t0), \\\nLINE(SIGNOFF,\t   \"    Signed-off-by\", COLOR_YELLOW,\tCOLOR_DEFAULT,\t0), \\\n/* UI colors */ \\\nLINE(DEFAULT,\t   \"\",\t\t\tCOLOR_DEFAULT,\tCOLOR_DEFAULT,\tA_NORMAL), \\\nLINE(CURSOR,\t   \"\",\t\t\tCOLOR_WHITE,\tCOLOR_GREEN,\tA_BOLD), \\\nLINE(STATUS,\t   \"\",\t\t\tCOLOR_GREEN,\tCOLOR_DEFAULT,\t0), \\\nLINE(TITLE_BLUR,   \"\",\t\t\tCOLOR_WHITE,\tCOLOR_BLUE,\t0), \\\nLINE(TITLE_FOCUS,  \"\",\t\t\tCOLOR_WHITE,\tCOLOR_BLUE,\tA_BOLD), \\\nLINE(MAIN_DATE,    \"\",\t\t\tCOLOR_BLUE,\tCOLOR_DEFAULT,\t0), \\\nLINE(MAIN_AUTHOR,  \"\",\t\t\tCOLOR_GREEN,\tCOLOR_DEFAULT,\t0), \\\nLINE(MAIN_COMMIT,  \"\",\t\t\tCOLOR_DEFAULT,\tCOLOR_DEFAULT,\t0), \\\nLINE(MAIN_DELIM,   \"\",\t\t\tCOLOR_MAGENTA,\tCOLOR_DEFAULT,\t0), \\\nLINE(MAIN_TAG,     \"\",\t\t\tCOLOR_MAGENTA,\tCOLOR_DEFAULT,\tA_BOLD), \\\nLINE(MAIN_REF,     \"\",\t\t\tCOLOR_CYAN,\tCOLOR_DEFAULT,\tA_BOLD),\n\nenum line_type {\n#define LINE(type, line, fg, bg, attr) \\\n\tLINE_##type\n\tLINE_INFO\n#undef\tLINE\n};\n\nstruct line_info {\n\tchar *line;\t\t/* The start of line to match. */\n\tint linelen;\t\t/* Size of string to match. */\n\tint fg, bg, attr;\t/* Color and text attributes for the lines. */\n};\n\nstatic struct line_info line_info[] = {\n#define LINE(type, line, fg, bg, attr) \\\n\t{ (line), STRING_SIZE(line), (fg), (bg), (attr) }\n\tLINE_INFO\n#undef\tLINE\n};\n\nstatic enum line_type\nget_line_type(char *line)\n{\n\tint linelen = strlen(line);\n\tenum line_type type;\n\n\tfor (type = 0; type < ARRAY_SIZE(line_info); type++)\n\t\t/* Case insensitive search matches Signed-off-by lines better. */\n\t\tif (linelen >= line_info[type].linelen &&\n\t\t    !strncasecmp(line_info[type].line, line, line_info[type].linelen))\n\t\t\treturn type;\n\n\treturn LINE_DEFAULT;\n}\n\nstatic inline int\nget_line_attr(enum line_type type)\n{\n\tassert(type < ARRAY_SIZE(line_info));\n\treturn COLOR_PAIR(type) | line_info[type].attr;\n}\n\nstatic void\ninit_colors(void)\n{\n\tint default_bg = COLOR_BLACK;\n\tint default_fg = COLOR_WHITE;\n\tenum line_type type;\n\n\tstart_color();\n\n\tif (use_default_colors() != ERR) {\n\t\tdefault_bg = -1;\n\t\tdefault_fg = -1;\n\t}\n\n\tfor (type = 0; type < ARRAY_SIZE(line_info); type++) {\n\t\tstruct line_info *info = &line_info[type];\n\t\tint bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;\n\t\tint fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;\n\n\t\tinit_pair(type, fg, bg);\n\t}\n}\n\n\n/**\n * ENVIRONMENT VARIABLES\n * ---------------------\n * Several options related to the interface with git can be configured\n * via environment options.\n *\n * Repository references\n * ~~~~~~~~~~~~~~~~~~~~~\n * Commits that are referenced by tags and branch heads will be marked\n * by the reference name surrounded by '[' and ']':\n *\n *\t2006-03-26 19:42 Petr Baudis         | [cogito-0.17.1] Cogito 0.17.1\n *\n * If you want to filter out certain directories under `.git/refs/`, say\n * `tmp` you can do it by setting the following variable:\n *\n *\t$ TIG_LS_REMOTE=\"git ls-remote . | sed /\\/tmp\\//d\" tig\n *\n * Or set the variable permanently in your environment.\n *\n * TIG_LS_REMOTE::\n *\tSet command for retrieving all repository references. The command\n *\tshould output data in the same format as git-ls-remote(1).\n **/\n\n#define TIG_LS_REMOTE \\\n\t\"git ls-remote . 2>/dev/null\"\n\n/**\n * [[view-commands]]\n * View commands\n * ~~~~~~~~~~~~~\n * It is possible to alter which commands are used for the different views.\n * If for example you prefer commits in the main view to be sorted by date\n * and only show 500 commits, use:\n *\n *\t$ TIG_MAIN_CMD=\"git log --date-order -n500 --pretty=raw %s\" tig\n *\n * Or set the variable permanently in your environment.\n *\n * Notice, how `%s` is used to specify the commit reference. There can\n * be a maximum of 5 `%s` ref specifications.\n *\n * TIG_DIFF_CMD::\n *\tThe command used for the diff view. By default, git show is used\n *\tas a backend.\n *\n * TIG_LOG_CMD::\n *\tThe command used for the log view. If you prefer to have both\n *\tauthor and committer shown in the log view be sure to pass\n *\t`--pretty=fuller` to git log.\n *\n * TIG_MAIN_CMD::\n *\tThe command used for the main view. Note, you must always specify\n *\tthe option: `--pretty=raw` since the main view parser expects to\n *\tread that format.\n **/\n\n#define TIG_DIFF_CMD \\\n\t\"git show --patch-with-stat --find-copies-harder -B -C %s\"\n\n#define TIG_LOG_CMD\t\\\n\t\"git log --cc --stat -n100 %s\"\n\n#define TIG_MAIN_CMD \\\n\t\"git log --topo-order --stat --pretty=raw %s\"\n\n/* ... silently ignore that the following are also exported. */\n\n#define TIG_HELP_CMD \\\n\t\"man tig 2>/dev/null\"\n\n#define TIG_PAGER_CMD \\\n\t\"\"\n\n/**\n * The viewer\n * ----------\n *\n * tig(1) presents various 'views' of a repository. Each view is based on output\n * from an external command, most often 'git log', 'git diff', or 'git show'.\n *\n * The main view::\n *\tIs the default view, and it shows a one line summary of each commit\n *\tin the chosen list of revisions. The summary includes commit date,\n *\tauthor, and the first line of the log message. Additionally, any\n *\trepository references, such as tags, will be shown.\n *\n * The log view::\n *\tPresents a more rich view of the revision log showing the whole log\n *\tmessage and the diffstat.\n *\n * The diff view::\n *\tShows either the diff of the current working tree, that is, what\n *\thas changed since the last commit, or the commit diff complete\n *\twith log message, diffstat and diff.\n *\n * The pager view::\n *\tIs used for displaying both input from stdin and output from git\n *\tcommands entered in the internal prompt.\n *\n * The help view::\n *\tDisplays the information from the tig(1) man page. For the help view\n *\tto work you need to have the tig(1) man page installed.\n **/\n\nstruct view {\n\tconst char *name;\t/* View name */\n\tchar *cmd_fmt;\t\t/* Default command line format */\n\tchar *cmd_env;\t\t/* Command line set via environment */\n\tchar *id;\t\t/* Points to either of ref_{head,commit} */\n\tsize_t objsize;\t\t/* Size of objects in the line index */\n\n\tstruct view_ops {\n\t\t/* What type of content being displayed. Used in the\n\t\t * title bar. */\n\t\tchar *type;\n\t\t/* Draw one line; @lineno must be < view->height. */\n\t\tbool (*draw)(struct view *view, unsigned int lineno);\n\t\t/* Read one line; updates view->line. */\n\t\tbool (*read)(struct view *view, char *line);\n\t\t/* Depending on view, change display based on current line. */\n\t\tbool (*enter)(struct view *view);\n\t} *ops;\n\n\tchar cmd[SIZEOF_CMD];\t/* Command buffer */\n\tchar ref[SIZEOF_REF];\t/* Hovered commit reference */\n\tchar vid[SIZEOF_REF];\t/* View ID. Set to id member when updating. */\n\n\tint height, width;\t/* The width and height of the main window */\n\tWINDOW *win;\t\t/* The main window */\n\tWINDOW *title;\t\t/* The title window living below the main window */\n\n\t/* Navigation */\n\tunsigned long offset;\t/* Offset of the window top */\n\tunsigned long lineno;\t/* Current line number */\n\n\t/* Buffering */\n\tunsigned long lines;\t/* Total number of lines */\n\tvoid **line;\t\t/* Line index; each line contains user data */\n\tunsigned int digits;\t/* Number of digits in the lines member. */\n\n\t/* Loading */\n\tFILE *pipe;\n\ttime_t start_time;\n};\n\nstatic struct view_ops pager_ops;\nstatic struct view_ops main_ops;\n\nstatic char ref_head[SIZEOF_REF]\t= \"HEAD\";\nstatic char ref_commit[SIZEOF_REF]\t= \"HEAD\";\n\n#define VIEW_STR(name, cmd, env, ref, objsize, ops) \\\n\t{ name, cmd, #env, ref, objsize, ops }\n\n#define VIEW_(id, name, ops, ref, objsize) \\\n\tVIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, objsize, ops)\n\nstatic struct view views[] = {\n\tVIEW_(MAIN,  \"main\",  &main_ops,  ref_head,   sizeof(struct commit)),\n\tVIEW_(DIFF,  \"diff\",  &pager_ops, ref_commit, sizeof(char)),\n\tVIEW_(LOG,   \"log\",   &pager_ops, ref_head,   sizeof(char)),\n\tVIEW_(HELP,  \"help\",  &pager_ops, ref_head,   sizeof(char)),\n\tVIEW_(PAGER, \"pager\", &pager_ops, \"static\",   sizeof(char)),\n};\n\n#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])\n\n/* The display array of active views and the index of the current view. */\nstatic struct view *display[2];\nstatic unsigned int current_view;\n\n#define foreach_view(view, i) \\\n\tfor (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)\n\n\nstatic void\nredraw_view_from(struct view *view, int lineno)\n{\n\tassert(0 <= lineno && lineno < view->height);\n\n\tfor (; lineno < view->height; lineno++) {\n\t\tif (!view->ops->draw(view, lineno))\n\t\t\tbreak;\n\t}\n\n\tredrawwin(view->win);\n\twrefresh(view->win);\n}\n\nstatic void\nredraw_view(struct view *view)\n{\n\twclear(view->win);\n\tredraw_view_from(view, 0);\n}\n\nstatic void\nresize_display(void)\n{\n\tint offset, i;\n\tstruct view *base = display[0];\n\tstruct view *view = display[1] ? display[1] : display[0];\n\n\t/* Setup window dimensions */\n\n\tgetmaxyx(stdscr, base->height, base->width);\n\n\t/* Make room for the status window. */\n\tbase->height -= 1;\n\n\tif (view != base) {\n\t\t/* Horizontal split. */\n\t\tview->width   = base->width;\n\t\tview->height  = SCALE_SPLIT_VIEW(base->height);\n\t\tbase->height -= view->height;\n\n\t\t/* Make room for the title bar. */\n\t\tview->height -= 1;\n\t}\n\n\t/* Make room for the title bar. */\n\tbase->height -= 1;\n\n\toffset = 0;\n\n\tforeach_view (view, i) {\n\t\t/* Keep the size of the all view windows one lager than is\n\t\t * required. This makes current line management easier when the\n\t\t * cursor will go outside the window. */\n\t\tif (!view->win) {\n\t\t\tview->win = newwin(view->height + 1, 0, offset, 0);\n\t\t\tif (!view->win)\n\t\t\t\tdie(\"Failed to create %s view\", view->name);\n\n\t\t\tscrollok(view->win, TRUE);\n\n\t\t\tview->title = newwin(1, 0, offset + view->height, 0);\n\t\t\tif (!view->title)\n\t\t\t\tdie(\"Failed to create title window\");\n\n\t\t} else {\n\t\t\twresize(view->win, view->height + 1, view->width);\n\t\t\tmvwin(view->win,   offset, 0);\n\t\t\tmvwin(view->title, offset + view->height, 0);\n\t\t\twrefresh(view->win);\n\t\t}\n\n\t\toffset += view->height + 1;\n\t}\n}\n\nstatic void\nupdate_view_title(struct view *view)\n{\n\tif (view == display[current_view])\n\t\twbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));\n\telse\n\t\twbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));\n\n\twerase(view->title);\n\twmove(view->title, 0, 0);\n\n\t/* [main] ref: 334b506... - commit 6 of 4383 (0%) */\n\n\tif (*view->ref)\n\t\twprintw(view->title, \"[%s] %s\", view->name, view->ref);\n\telse\n\t\twprintw(view->title, \"[%s]\", view->name);\n\n\tif (view->lines) {\n\t\twprintw(view->title, \" - %s %d of %d (%d%%)\",\n\t\t\tview->ops->type,\n\t\t\tview->lineno + 1,\n\t\t\tview->lines,\n\t\t\t(view->lineno + 1) * 100 / view->lines);\n\t}\n\n\twrefresh(view->title);\n}\n\n/*\n * Navigation\n */\n\n/* Scrolling backend */\nstatic void\ndo_scroll_view(struct view *view, int lines)\n{\n\t/* The rendering expects the new offset. */\n\tview->offset += lines;\n\n\tassert(0 <= view->offset && view->offset < view->lines);\n\tassert(lines);\n\n\t/* Redraw the whole screen if scrolling is pointless. */\n\tif (view->height < ABS(lines)) {\n\t\tredraw_view(view);\n\n\t} else {\n\t\tint line = lines > 0 ? view->height - lines : 0;\n\t\tint end = line + ABS(lines);\n\n\t\twscrl(view->win, lines);\n\n\t\tfor (; line < end; line++) {\n\t\t\tif (!view->ops->draw(view, line))\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Move current line into the view. */\n\tif (view->lineno < view->offset) {\n\t\tview->lineno = view->offset;\n\t\tview->ops->draw(view, 0);\n\n\t} else if (view->lineno >= view->offset + view->height) {\n\t\tif (view->lineno == view->offset + view->height) {\n\t\t\t/* Clear the hidden line so it doesn't show if the view\n\t\t\t * is scrolled up. */\n\t\t\twmove(view->win, view->height, 0);\n\t\t\twclrtoeol(view->win);\n\t\t}\n\t\tview->lineno = view->offset + view->height - 1;\n\t\tview->ops->draw(view, view->lineno - view->offset);\n\t}\n\n\tassert(view->offset <= view->lineno && view->lineno < view->lines);\n\n\tredrawwin(view->win);\n\twrefresh(view->win);\n\treport(\"\");\n}\n\n/* Scroll frontend */\nstatic void\nscroll_view(struct view *view, enum request request)\n{\n\tint lines = 1;\n\n\tswitch (request) {\n\tcase REQ_SCROLL_PAGE_DOWN:\n\t\tlines = view->height;\n\tcase REQ_SCROLL_LINE_DOWN:\n\t\tif (view->offset + lines > view->lines)\n\t\t\tlines = view->lines - view->offset;\n\n\t\tif (lines == 0 || view->offset + view->height >= view->lines) {\n\t\t\treport(\"Cannot scroll beyond the last line\");\n\t\t\treturn;\n\t\t}\n\t\tbreak;\n\n\tcase REQ_SCROLL_PAGE_UP:\n\t\tlines = view->height;\n\tcase REQ_SCROLL_LINE_UP:\n\t\tif (lines > view->offset)\n\t\t\tlines = view->offset;\n\n\t\tif (lines == 0) {\n\t\t\treport(\"Cannot scroll beyond the first line\");\n\t\t\treturn;\n\t\t}\n\n\t\tlines = -lines;\n\t\tbreak;\n\n\tdefault:\n\t\tdie(\"request %d not handled in switch\", request);\n\t}\n\n\tdo_scroll_view(view, lines);\n}\n\n/* Cursor moving */\nstatic void\nmove_view(struct view *view, enum request request)\n{\n\tint steps;\n\n\tswitch (request) {\n\tcase REQ_MOVE_FIRST_LINE:\n\t\tsteps = -view->lineno;\n\t\tbreak;\n\n\tcase REQ_MOVE_LAST_LINE:\n\t\tsteps = view->lines - view->lineno - 1;\n\t\tbreak;\n\n\tcase REQ_MOVE_PAGE_UP:\n\t\tsteps = view->height > view->lineno\n\t\t      ? -view->lineno : -view->height;\n\t\tbreak;\n\n\tcase REQ_MOVE_PAGE_DOWN:\n\t\tsteps = view->lineno + view->height >= view->lines\n\t\t      ? view->lines - view->lineno - 1 : view->height;\n\t\tbreak;\n\n\tcase REQ_MOVE_UP:\n\tcase REQ_MOVE_UP_ENTER:\n\t\tsteps = -1;\n\t\tbreak;\n\n\tcase REQ_MOVE_DOWN:\n\tcase REQ_MOVE_DOWN_ENTER:\n\t\tsteps = 1;\n\t\tbreak;\n\n\tdefault:\n\t\tdie(\"request %d not handled in switch\", request);\n\t}\n\n\tif (steps <= 0 && view->lineno == 0) {\n\t\treport(\"Cannot move beyond the first line\");\n\t\treturn;\n\n\t} else if (steps >= 0 && view->lineno + 1 >= view->lines) {\n\t\treport(\"Cannot move beyond the last line\");\n\t\treturn;\n\t}\n\n\t/* Move the current line */\n\tview->lineno += steps;\n\tassert(0 <= view->lineno && view->lineno < view->lines);\n\n\t/* Repaint the old \"current\" line if we be scrolling */\n\tif (ABS(steps) < view->height) {\n\t\tint prev_lineno = view->lineno - steps - view->offset;\n\n\t\twmove(view->win, prev_lineno, 0);\n\t\twclrtoeol(view->win);\n\t\tview->ops->draw(view, prev_lineno);\n\t}\n\n\t/* Check whether the view needs to be scrolled */\n\tif (view->lineno < view->offset ||\n\t    view->lineno >= view->offset + view->height) {\n\t\tif (steps < 0 && -steps > view->offset) {\n\t\t\tsteps = -view->offset;\n\n\t\t} else if (steps > 0) {\n\t\t\tif (view->lineno == view->lines - 1 &&\n\t\t\t    view->lines > view->height) {\n\t\t\t\tsteps = view->lines - view->offset - 1;\n\t\t\t\tif (steps >= view->height)\n\t\t\t\t\tsteps -= view->height - 1;\n\t\t\t}\n\t\t}\n\n\t\tdo_scroll_view(view, steps);\n\t\treturn;\n\t}\n\n\t/* Draw the current line */\n\tview->ops->draw(view, view->lineno - view->offset);\n\n\tredrawwin(view->win);\n\twrefresh(view->win);\n\treport(\"\");\n}\n\n\n/*\n * Incremental updating\n */\n\nstatic bool\nbegin_update(struct view *view)\n{\n\tchar *id = view->id;\n\n\tif (opt_cmd[0]) {\n\t\tstring_copy(view->cmd, opt_cmd);\n\t\topt_cmd[0] = 0;\n\t\t/* When running random commands, the view ref could have become\n\t\t * invalid so clear it. */\n\t\tview->ref[0] = 0;\n\t} else {\n\t\tchar *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;\n\n\t\tif (snprintf(view->cmd, sizeof(view->cmd), format,\n\t\t\t     id, id, id, id, id) >= sizeof(view->cmd))\n\t\t\treturn FALSE;\n\t}\n\n\t/* Special case for the pager view. */\n\tif (opt_pipe) {\n\t\tview->pipe = opt_pipe;\n\t\topt_pipe = NULL;\n\t} else {\n\t\tview->pipe = popen(view->cmd, \"r\");\n\t}\n\n\tif (!view->pipe)\n\t\treturn FALSE;\n\n\tset_nonblocking_input(TRUE);\n\n\tview->offset = 0;\n\tview->lines  = 0;\n\tview->lineno = 0;\n\tstring_copy(view->vid, id);\n\n\tif (view->line) {\n\t\tint i;\n\n\t\tfor (i = 0; i < view->lines; i++)\n\t\t\tif (view->line[i])\n\t\t\t\tfree(view->line[i]);\n\n\t\tfree(view->line);\n\t\tview->line = NULL;\n\t}\n\n\tview->start_time = time(NULL);\n\n\treturn TRUE;\n}\n\nstatic void\nend_update(struct view *view)\n{\n\tif (!view->pipe)\n\t\treturn;\n\tset_nonblocking_input(FALSE);\n\tif (view->pipe == stdin)\n\t\tfclose(view->pipe);\n\telse\n\t\tpclose(view->pipe);\n\tview->pipe = NULL;\n}\n\nstatic bool\nupdate_view(struct view *view)\n{\n\tchar buffer[BUFSIZ];\n\tchar *line;\n\tvoid **tmp;\n\t/* The number of lines to read. If too low it will cause too much\n\t * redrawing (and possible flickering), if too high responsiveness\n\t * will suffer. */\n\tunsigned long lines = view->height;\n\tint redraw_from = -1;\n\n\tif (!view->pipe)\n\t\treturn TRUE;\n\n\t/* Only redraw if lines are visible. */\n\tif (view->offset + view->height >= view->lines)\n\t\tredraw_from = view->lines - view->offset;\n\n\ttmp = realloc(view->line, sizeof(*view->line) * (view->lines + lines));\n\tif (!tmp)\n\t\tgoto alloc_error;\n\n\tview->line = tmp;\n\n\twhile ((line = fgets(buffer, sizeof(buffer), view->pipe))) {\n\t\tint linelen = strlen(line);\n\n\t\tif (linelen)\n\t\t\tline[linelen - 1] = 0;\n\n\t\tif (!view->ops->read(view, line))\n\t\t\tgoto alloc_error;\n\n\t\tif (lines-- == 1)\n\t\t\tbreak;\n\t}\n\n\t{\n\t\tint digits;\n\n\t\tlines = view->lines;\n\t\tfor (digits = 0; lines; digits++)\n\t\t\tlines /= 10;\n\n\t\t/* Keep the displayed view in sync with line number scaling. */\n\t\tif (digits != view->digits) {\n\t\t\tview->digits = digits;\n\t\t\tredraw_from = 0;\n\t\t}\n\t}\n\n\tif (redraw_from >= 0) {\n\t\t/* If this is an incremental update, redraw the previous line\n\t\t * since for commits some members could have changed when\n\t\t * loading the main view. */\n\t\tif (redraw_from > 0)\n\t\t\tredraw_from--;\n\n\t\t/* Incrementally draw avoids flickering. */\n\t\tredraw_view_from(view, redraw_from);\n\t}\n\n\t/* Update the title _after_ the redraw so that if the redraw picks up a\n\t * commit reference in view->ref it'll be available here. */\n\tupdate_view_title(view);\n\n\tif (ferror(view->pipe)) {\n\t\treport(\"Failed to read: %s\", strerror(errno));\n\t\tgoto end;\n\n\t} else if (feof(view->pipe)) {\n\t\ttime_t secs = time(NULL) - view->start_time;\n\n\t\tif (view == VIEW(REQ_VIEW_HELP)) {\n\t\t\tchar *msg = TIG_HELP;\n\n\t\t\tif (view->lines == 0) {\n\t\t\t\t/* Slightly ugly, but abusing view->ref keeps\n\t\t\t\t * the error message. */\n\t\t\t\tstring_copy(view->ref, \"No help available\");\n\t\t\t\tmsg = \"The tig(1) manpage is not installed\";\n\t\t\t}\n\n\t\t\treport(\"%s\", msg);\n\t\t\tgoto end;\n\t\t}\n\n\t\treport(\"Loaded %d lines in %ld second%s\", view->lines, secs,\n\t\t       secs == 1 ? \"\" : \"s\");\n\t\tgoto end;\n\t}\n\n\treturn TRUE;\n\nalloc_error:\n\treport(\"Allocation failure\");\n\nend:\n\tend_update(view);\n\treturn FALSE;\n}\n\nenum open_flags {\n\tOPEN_DEFAULT = 0,\t/* Use default view switching. */\n\tOPEN_SPLIT = 1,\t\t/* Split current view. */\n\tOPEN_BACKGROUNDED = 2,\t/* Backgrounded. */\n\tOPEN_RELOAD = 4,\t/* Reload view even if it is the current. */\n};\n\nstatic void\nopen_view(struct view *prev, enum request request, enum open_flags flags)\n{\n\tbool backgrounded = !!(flags & OPEN_BACKGROUNDED);\n\tbool split = !!(flags & OPEN_SPLIT);\n\tbool reload = !!(flags & OPEN_RELOAD);\n\tstruct view *view = VIEW(request);\n\tstruct view *displayed;\n\tint nviews;\n\n\t/* Cycle between displayed views and count the views. */\n\tforeach_view (displayed, nviews) {\n\t\tif (prev != view &&\n\t\t    view == displayed &&\n\t\t    !strcmp(view->vid, prev->vid)) {\n\t\t\tcurrent_view = nviews;\n\t\t\t/* Blur out the title of the previous view. */\n\t\t\tupdate_view_title(prev);\n\t\t\treport(\"\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (view == prev && nviews == 1 && !reload) {\n\t\treport(\"Already in %s view\", view->name);\n\t\treturn;\n\t}\n\n\tif ((reload || strcmp(view->vid, view->id)) &&\n\t    !begin_update(view)) {\n\t\treport(\"Failed to load %s view\", view->name);\n\t\treturn;\n\t}\n\n\tif (split) {\n\t\tdisplay[current_view + 1] = view;\n\t\tif (!backgrounded)\n\t\t\tcurrent_view++;\n\t} else {\n\t\t/* Maximize the current view. */\n\t\tmemset(display, 0, sizeof(display));\n\t\tcurrent_view = 0;\n\t\tdisplay[current_view] = view;\n\t}\n\n\tresize_display();\n\n\tif (split && prev->lineno - prev->offset >= prev->height) {\n\t\t/* Take the title line into account. */\n\t\tint lines = prev->lineno - prev->offset - prev->height + 1;\n\n\t\t/* Scroll the view that was split if the current line is\n\t\t * outside the new limited view. */\n\t\tdo_scroll_view(prev, lines);\n\t}\n\n\tif (prev && view != prev) {\n\t\t/* \"Blur\" the previous view. */\n\t\tif (!backgrounded)\n\t\t\tupdate_view_title(prev);\n\n\t\t/* Continue loading split views in the background. */\n\t\tif (!split)\n\t\t\tend_update(prev);\n\t}\n\n\tif (view->pipe) {\n\t\t/* Clear the old view and let the incremental updating refill\n\t\t * the screen. */\n\t\twclear(view->win);\n\t\treport(\"Loading...\");\n\t} else {\n\t\tredraw_view(view);\n\t\tif (view == VIEW(REQ_VIEW_HELP))\n\t\t\treport(\"%s\", TIG_HELP);\n\t\telse\n\t\t\treport(\"\");\n\t}\n\n\t/* If the view is backgrounded the above calls to report()\n\t * won't redraw the view title. */\n\tif (backgrounded)\n\t\tupdate_view_title(view);\n}\n\n\n/*\n * User request switch noodle\n */\n\nstatic int\nview_driver(struct view *view, enum request request)\n{\n\tint i;\n\n\tswitch (request) {\n\tcase REQ_MOVE_UP:\n\tcase REQ_MOVE_DOWN:\n\tcase REQ_MOVE_PAGE_UP:\n\tcase REQ_MOVE_PAGE_DOWN:\n\tcase REQ_MOVE_FIRST_LINE:\n\tcase REQ_MOVE_LAST_LINE:\n\t\tmove_view(view, request);\n\t\tbreak;\n\n\tcase REQ_SCROLL_LINE_DOWN:\n\tcase REQ_SCROLL_LINE_UP:\n\tcase REQ_SCROLL_PAGE_DOWN:\n\tcase REQ_SCROLL_PAGE_UP:\n\t\tscroll_view(view, request);\n\t\tbreak;\n\n\tcase REQ_VIEW_MAIN:\n\tcase REQ_VIEW_DIFF:\n\tcase REQ_VIEW_LOG:\n\tcase REQ_VIEW_HELP:\n\tcase REQ_VIEW_PAGER:\n\t\topen_view(view, request, OPEN_DEFAULT);\n\t\tbreak;\n\n\tcase REQ_MOVE_UP_ENTER:\n\tcase REQ_MOVE_DOWN_ENTER:\n\t\tmove_view(view, request);\n\t\t/* Fall-through */\n\n\tcase REQ_ENTER:\n\t\tif (!view->lines) {\n\t\t\treport(\"Nothing to enter\");\n\t\t\tbreak;\n\t\t}\n\t\treturn view->ops->enter(view);\n\n\tcase REQ_VIEW_NEXT:\n\t{\n\t\tint nviews = display[1] ? 2 : 1;\n\t\tint next_view = (current_view + 1) % nviews;\n\n\t\tif (next_view == current_view) {\n\t\t\treport(\"Only one view is displayed\");\n\t\t\tbreak;\n\t\t}\n\n\t\tcurrent_view = next_view;\n\t\t/* Blur out the title of the previous view. */\n\t\tupdate_view_title(view);\n\t\treport(\"\");\n\t\tbreak;\n\t}\n\tcase REQ_TOGGLE_LINE_NUMBERS:\n\t\topt_line_number = !opt_line_number;\n\t\tredraw_view(view);\n\t\tupdate_view_title(view);\n\t\tbreak;\n\n\tcase REQ_PROMPT:\n\t\t/* Always reload^Wrerun commands from the prompt. */\n\t\topen_view(view, opt_request, OPEN_RELOAD);\n\t\tbreak;\n\n\tcase REQ_STOP_LOADING:\n\t\tforeach_view (view, i) {\n\t\t\tif (view->pipe)\n\t\t\t\treport(\"Stopped loaded the %s view\", view->name),\n\t\t\tend_update(view);\n\t\t}\n\t\tbreak;\n\n\tcase REQ_SHOW_VERSION:\n\t\treport(\"Version: %s\", VERSION);\n\t\treturn TRUE;\n\n\tcase REQ_SCREEN_RESIZE:\n\t\tresize_display();\n\t\t/* Fall-through */\n\tcase REQ_SCREEN_REDRAW:\n\t\tforeach_view (view, i) {\n\t\t\tredraw_view(view);\n\t\t\tupdate_view_title(view);\n\t\t}\n\t\tbreak;\n\n\tcase REQ_SCREEN_UPDATE:\n\t\tdoupdate();\n\t\treturn TRUE;\n\n\tcase REQ_QUIT:\n\t\treturn FALSE;\n\n\tdefault:\n\t\t/* An unknown key will show most commonly used commands. */\n\t\treport(\"Unknown key, press 'h' for help\");\n\t\treturn TRUE;\n\t}\n\n\treturn TRUE;\n}\n\n\n/*\n * View backend handlers\n */\n\nstatic bool\npager_draw(struct view *view, unsigned int lineno)\n{\n\tenum line_type type;\n\tchar *line;\n\tint linelen;\n\tint attr;\n\n\tif (view->offset + lineno >= view->lines)\n\t\treturn FALSE;\n\n\tline = view->line[view->offset + lineno];\n\ttype = get_line_type(line);\n\n\twmove(view->win, lineno, 0);\n\n\tif (view->offset + lineno == view->lineno) {\n\t\tif (type == LINE_COMMIT) {\n\t\t\tstring_copy(view->ref, line + 7);\n\t\t\tstring_copy(ref_commit, view->ref);\n\t\t}\n\n\t\ttype = LINE_CURSOR;\n\t\twchgat(view->win, -1, 0, type, NULL);\n\t}\n\n\tattr = get_line_attr(type);\n\twattrset(view->win, attr);\n\n\tlinelen = strlen(line);\n\n\tif (opt_line_number || opt_tab_size < TABSIZE) {\n\t\tstatic char spaces[] = \"                    \";\n\t\tint col_offset = 0, col = 0;\n\n\t\tif (opt_line_number) {\n\t\t\tunsigned long real_lineno = view->offset + lineno + 1;\n\n\t\t\tif (real_lineno == 1 ||\n\t\t\t    (real_lineno % opt_num_interval) == 0) {\n\t\t\t\twprintw(view->win, \"%.*d\", view->digits, real_lineno);\n\n\t\t\t} else {\n\t\t\t\twaddnstr(view->win, spaces,\n\t\t\t\t\t MIN(view->digits, STRING_SIZE(spaces)));\n\t\t\t}\n\t\t\twaddstr(view->win, \": \");\n\t\t\tcol_offset = view->digits + 2;\n\t\t}\n\n\t\twhile (line && col_offset + col < view->width) {\n\t\t\tint cols_max = view->width - col_offset - col;\n\t\t\tchar *text = line;\n\t\t\tint cols;\n\n\t\t\tif (*line == '\\t') {\n\t\t\t\tassert(sizeof(spaces) > TABSIZE);\n\t\t\t\tline++;\n\t\t\t\ttext = spaces;\n\t\t\t\tcols = opt_tab_size - (col % opt_tab_size);\n\n\t\t\t} else {\n\t\t\t\tline = strchr(line, '\\t');\n\t\t\t\tcols = line ? line - text : strlen(text);\n\t\t\t}\n\n\t\t\twaddnstr(view->win, text, MIN(cols, cols_max));\n\t\t\tcol += cols;\n\t\t}\n\n\t} else {\n\t\tint col = 0, pos = 0;\n\n\t\tfor (; pos < linelen && col < view->width; pos++, col++)\n\t\t\tif (line[pos] == '\\t')\n\t\t\t\tcol += TABSIZE - (col % TABSIZE) - 1;\n\n\t\twaddnstr(view->win, line, pos);\n\t}\n\n\treturn TRUE;\n}\n\nstatic bool\npager_read(struct view *view, char *line)\n{\n\t/* Compress empty lines in the help view. */\n\tif (view == VIEW(REQ_VIEW_HELP) &&\n\t    !*line &&\n\t    view->lines &&\n\t    !*((char *) view->line[view->lines - 1]))\n\t\treturn TRUE;\n\n\tview->line[view->lines] = strdup(line);\n\tif (!view->line[view->lines])\n\t\treturn FALSE;\n\n\tview->lines++;\n\treturn TRUE;\n}\n\nstatic bool\npager_enter(struct view *view)\n{\n\tchar *line = view->line[view->lineno];\n\n\tif (get_line_type(line) == LINE_COMMIT) {\n\t\tif (view == VIEW(REQ_VIEW_LOG))\n\t\t\topen_view(view, REQ_VIEW_DIFF, OPEN_SPLIT | OPEN_BACKGROUNDED);\n\t\telse\n\t\t\topen_view(view, REQ_VIEW_DIFF, OPEN_DEFAULT);\n\t}\n\n\treturn TRUE;\n}\n\nstatic struct view_ops pager_ops = {\n\t\"line\",\n\tpager_draw,\n\tpager_read,\n\tpager_enter,\n};\n\n\nstatic struct ref **get_refs(char *id);\n\nstatic bool\nmain_draw(struct view *view, unsigned int lineno)\n{\n\tchar buf[DATE_COLS + 1];\n\tstruct commit *commit;\n\tenum line_type type;\n\tint col = 0;\n\tsize_t timelen;\n\n\tif (view->offset + lineno >= view->lines)\n\t\treturn FALSE;\n\n\tcommit = view->line[view->offset + lineno];\n\tif (!*commit->author)\n\t\treturn FALSE;\n\n\twmove(view->win, lineno, col);\n\n\tif (view->offset + lineno == view->lineno) {\n\t\tstring_copy(view->ref, commit->id);\n\t\tstring_copy(ref_commit, view->ref);\n\t\ttype = LINE_CURSOR;\n\t\twattrset(view->win, get_line_attr(type));\n\t\twchgat(view->win, -1, 0, type, NULL);\n\n\t} else {\n\t\ttype = LINE_MAIN_COMMIT;\n\t\twattrset(view->win, get_line_attr(LINE_MAIN_DATE));\n\t}\n\n\ttimelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);\n\twaddnstr(view->win, buf, timelen);\n\twaddstr(view->win, \" \");\n\n\tcol += DATE_COLS;\n\twmove(view->win, lineno, col);\n\tif (type != LINE_CURSOR)\n\t\twattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));\n\n\tif (strlen(commit->author) > 19) {\n\t\twaddnstr(view->win, commit->author, 18);\n\t\tif (type != LINE_CURSOR)\n\t\t\twattrset(view->win, get_line_attr(LINE_MAIN_DELIM));\n\t\twaddch(view->win, '~');\n\t} else {\n\t\twaddstr(view->win, commit->author);\n\t}\n\n\tcol += 20;\n\tif (type != LINE_CURSOR)\n\t\twattrset(view->win, A_NORMAL);\n\n\tmvwaddch(view->win, lineno, col, ACS_LTEE);\n\twmove(view->win, lineno, col + 2);\n\tcol += 2;\n\n\tif (commit->refs) {\n\t\tsize_t i = 0;\n\n\t\tdo {\n\t\t\tif (type == LINE_CURSOR)\n\t\t\t\t;\n\t\t\telse if (commit->refs[i]->tag)\n\t\t\t\twattrset(view->win, get_line_attr(LINE_MAIN_TAG));\n\t\t\telse\n\t\t\t\twattrset(view->win, get_line_attr(LINE_MAIN_REF));\n\t\t\twaddstr(view->win, \"[\");\n\t\t\twaddstr(view->win, commit->refs[i]->name);\n\t\t\twaddstr(view->win, \"]\");\n\t\t\tif (type != LINE_CURSOR)\n\t\t\t\twattrset(view->win, A_NORMAL);\n\t\t\twaddstr(view->win, \" \");\n\t\t\tcol += strlen(commit->refs[i]->name) + STRING_SIZE(\"[] \");\n\t\t} while (commit->refs[i++]->next);\n\t}\n\n\tif (type != LINE_CURSOR)\n\t\twattrset(view->win, get_line_attr(type));\n\n\t{\n\t\tint titlelen = strlen(commit->title);\n\n\t\tif (col + titlelen > view->width)\n\t\t\ttitlelen = view->width - col;\n\n\t\twaddnstr(view->win, commit->title, titlelen);\n\t}\n\n\treturn TRUE;\n}\n\n/* Reads git log --pretty=raw output and parses it into the commit struct. */\nstatic bool\nmain_read(struct view *view, char *line)\n{\n\tenum line_type type = get_line_type(line);\n\tstruct commit *commit;\n\n\tswitch (type) {\n\tcase LINE_COMMIT:\n\t\tcommit = calloc(1, sizeof(struct commit));\n\t\tif (!commit)\n\t\t\treturn FALSE;\n\n\t\tline += STRING_SIZE(\"commit \");\n\n\t\tview->line[view->lines++] = commit;\n\t\tstring_copy(commit->id, line);\n\t\tcommit->refs = get_refs(commit->id);\n\t\tbreak;\n\n\tcase LINE_AUTHOR:\n\t{\n\t\tchar *ident = line + STRING_SIZE(\"author \");\n\t\tchar *end = strchr(ident, '<');\n\n\t\tif (end) {\n\t\t\tfor (; end > ident && isspace(end[-1]); end--) ;\n\t\t\t*end = 0;\n\t\t}\n\n\t\tcommit = view->line[view->lines - 1];\n\t\tstring_copy(commit->author, ident);\n\n\t\t/* Parse epoch and timezone */\n\t\tif (end) {\n\t\t\tchar *secs = strchr(end + 1, '>');\n\t\t\tchar *zone;\n\t\t\ttime_t time;\n\n\t\t\tif (!secs || secs[1] != ' ')\n\t\t\t\tbreak;\n\n\t\t\tsecs += 2;\n\t\t\ttime = (time_t) atol(secs);\n\t\t\tzone = strchr(secs, ' ');\n\t\t\tif (zone && strlen(zone) == STRING_SIZE(\" +0700\")) {\n\t\t\t\tlong tz;\n\n\t\t\t\tzone++;\n\t\t\t\ttz  = ('0' - zone[1]) * 60 * 60 * 10;\n\t\t\t\ttz += ('0' - zone[2]) * 60 * 60;\n\t\t\t\ttz += ('0' - zone[3]) * 60;\n\t\t\t\ttz += ('0' - zone[4]) * 60;\n\n\t\t\t\tif (zone[0] == '-')\n\t\t\t\t\ttz = -tz;\n\n\t\t\t\ttime -= tz;\n\t\t\t}\n\t\t\tgmtime_r(&time, &commit->time);\n\t\t}\n\t\tbreak;\n\t}\n\tdefault:\n\t\t/* We should only ever end up here if there has already been a\n\t\t * commit line, however, be safe. */\n\t\tif (view->lines == 0)\n\t\t\tbreak;\n\n\t\t/* Fill in the commit title if it has not already been set. */\n\t\tcommit = view->line[view->lines - 1];\n\t\tif (commit->title[0])\n\t\t\tbreak;\n\n\t\t/* Require titles to start with a non-space character at the\n\t\t * offset used by git log. */\n\t\t/* FIXME: More gracefull handling of titles; append \"...\" to\n\t\t * shortened titles, etc. */\n\t\tif (strncmp(line, \"    \", 4) ||\n\t\t    isspace(line[4]))\n\t\t\tbreak;\n\n\t\tstring_copy(commit->title, line + 4);\n\t}\n\n\treturn TRUE;\n}\n\nstatic bool\nmain_enter(struct view *view)\n{\n\topen_view(view, REQ_VIEW_DIFF, OPEN_SPLIT | OPEN_BACKGROUNDED);\n\treturn TRUE;\n}\n\nstatic struct view_ops main_ops = {\n\t\"commit\",\n\tmain_draw,\n\tmain_read,\n\tmain_enter,\n};\n\n\n/**\n * KEYS\n * ----\n * Below the default key bindings are shown.\n **/\n\nstruct keymap {\n\tint alias;\n\tint request;\n};\n\nstatic struct keymap keymap[] = {\n\t/**\n\t * View switching\n\t * ~~~~~~~~~~~~~~\n\t * m::\n\t *\tSwitch to main view.\n\t * d::\n\t *\tSwitch to diff view.\n\t * l::\n\t *\tSwitch to log view.\n\t * p::\n\t *\tSwitch to pager view.\n\t * h::\n\t *\tShow man page.\n\t * Return::\n\t *\tIf on a commit line show the commit diff. Additionally, if in\n\t *\tmain or log view this will split the view. To open the commit\n\t *\tdiff in full size view either use 'd' or press Return twice.\n\t * Tab::\n\t *\tSwitch to next view.\n\t **/\n\t{ 'm',\t\tREQ_VIEW_MAIN },\n\t{ 'd',\t\tREQ_VIEW_DIFF },\n\t{ 'l',\t\tREQ_VIEW_LOG },\n\t{ 'p',\t\tREQ_VIEW_PAGER },\n\t{ 'h',\t\tREQ_VIEW_HELP },\n\n\t{ KEY_TAB,\tREQ_VIEW_NEXT },\n\t{ KEY_RETURN,\tREQ_ENTER },\n\n\t/**\n\t * Cursor navigation\n\t * ~~~~~~~~~~~~~~~~~\n\t * Up::\n\t *\tMove cursor one line up.\n\t * Down::\n\t *\tMove cursor one line down.\n\t * k::\n\t *\tMove cursor one line up and enter. When used in the main view\n\t *\tthis will always show the diff of the current commit in the\n\t *\tsplit diff view.\n\t * j::\n\t *\tMove cursor one line down and enter.\n\t * PgUp::\n\t *\tMove cursor one page up.\n\t * PgDown::\n\t *\tMove cursor one page down.\n\t * Home::\n\t *\tJump to first line.\n\t * End::\n\t *\tJump to last line.\n\t **/\n\t{ KEY_UP,\tREQ_MOVE_UP },\n\t{ KEY_DOWN,\tREQ_MOVE_DOWN },\n\t{ 'k',\t\tREQ_MOVE_UP_ENTER },\n\t{ 'j',\t\tREQ_MOVE_DOWN_ENTER },\n\t{ KEY_HOME,\tREQ_MOVE_FIRST_LINE },\n\t{ KEY_END,\tREQ_MOVE_LAST_LINE },\n\t{ KEY_NPAGE,\tREQ_MOVE_PAGE_DOWN },\n\t{ KEY_PPAGE,\tREQ_MOVE_PAGE_UP },\n\n\t/**\n\t * Scrolling\n\t * ~~~~~~~~~\n\t * Insert::\n\t *\tScroll view one line up.\n\t * Delete::\n\t *\tScroll view one line down.\n\t * w::\n\t *\tScroll view one page up.\n\t * s::\n\t *\tScroll view one page down.\n\t **/\n\t{ KEY_IC,\tREQ_SCROLL_LINE_UP },\n\t{ KEY_DC,\tREQ_SCROLL_LINE_DOWN },\n\t{ 'w',\t\tREQ_SCROLL_PAGE_UP },\n\t{ 's',\t\tREQ_SCROLL_PAGE_DOWN },\n\n\t/**\n\t * Misc\n\t * ~~~~\n\t * q::\n\t *\tQuit\n\t * r::\n\t *\tRedraw screen.\n\t * z::\n\t *\tStop all background loading. This can be useful if you use\n\t *\ttig(1) in a repository with a long history without limiting\n\t *\tthe revision log.\n\t * v::\n\t *\tShow version.\n\t * n::\n\t *\tToggle line numbers on/off.\n\t * ':'::\n\t *\tOpen prompt. This allows you to specify what git command\n\t *\tto run. Example:\n\t *\n\t *\t:log -p\n\t **/\n\t{ 'q',\t\tREQ_QUIT },\n\t{ 'z',\t\tREQ_STOP_LOADING },\n\t{ 'v',\t\tREQ_SHOW_VERSION },\n\t{ 'r',\t\tREQ_SCREEN_REDRAW },\n\t{ 'n',\t\tREQ_TOGGLE_LINE_NUMBERS },\n\t{ ':',\t\tREQ_PROMPT },\n\n\t/* wgetch() with nodelay() enabled returns ERR when there's no input. */\n\t{ ERR,\t\tREQ_SCREEN_UPDATE },\n\n\t/* Use the ncurses SIGWINCH handler. */\n\t{ KEY_RESIZE,\tREQ_SCREEN_RESIZE },\n};\n\nstatic enum request\nget_request(int key)\n{\n\tint i;\n\n\tfor (i = 0; i < ARRAY_SIZE(keymap); i++)\n\t\tif (keymap[i].alias == key)\n\t\t\treturn keymap[i].request;\n\n\treturn (enum request) key;\n}\n\n\n/*\n * Status management\n */\n\n/* Whether or not the curses interface has been initialized. */\nbool cursed = FALSE;\n\n/* The status window is used for polling keystrokes. */\nstatic WINDOW *status_win;\n\n/* Update status and title window. */\nstatic void\nreport(const char *msg, ...)\n{\n\tstatic bool empty = TRUE;\n\tstruct view *view = display[current_view];\n\n\tif (!empty || *msg) {\n\t\tva_list args;\n\n\t\tva_start(args, msg);\n\n\t\twerase(status_win);\n\t\twmove(status_win, 0, 0);\n\t\tif (*msg) {\n\t\t\tvwprintw(status_win, msg, args);\n\t\t\tempty = FALSE;\n\t\t} else {\n\t\t\tempty = TRUE;\n\t\t}\n\t\twrefresh(status_win);\n\n\t\tva_end(args);\n\t}\n\n\tupdate_view_title(view);\n\n\t/* Move the cursor to the right-most column of the cursor line.\n\t *\n\t * XXX: This could turn out to be a bit expensive, but it ensures that\n\t * the cursor does not jump around. */\n\tif (view->lines) {\n\t\twmove(view->win, view->lineno - view->offset, view->width - 1);\n\t\twrefresh(view->win);\n\t}\n}\n\n/* Controls when nodelay should be in effect when polling user input. */\nstatic void\nset_nonblocking_input(bool loading)\n{\n\tstatic unsigned int loading_views;\n\n\tif ((loading == FALSE && loading_views-- == 1) ||\n\t    (loading == TRUE  && loading_views++ == 0))\n\t\tnodelay(status_win, loading);\n}\n\nstatic void\ninit_display(void)\n{\n\tint x, y;\n\n\t/* Initialize the curses library */\n\tif (isatty(STDIN_FILENO)) {\n\t\tcursed = !!initscr();\n\t} else {\n\t\t/* Leave stdin and stdout alone when acting as a pager. */\n\t\tFILE *io = fopen(\"/dev/tty\", \"r+\");\n\n\t\tcursed = !!newterm(NULL, io, io);\n\t}\n\n\tif (!cursed)\n\t\tdie(\"Failed to initialize curses\");\n\n\tnonl();         /* Tell curses not to do NL->CR/NL on output */\n\tcbreak();       /* Take input chars one at a time, no wait for \\n */\n\tnoecho();       /* Don't echo input */\n\tleaveok(stdscr, TRUE);\n\n\tif (has_colors())\n\t\tinit_colors();\n\n\tgetmaxyx(stdscr, y, x);\n\tstatus_win = newwin(1, 0, y - 1, 0);\n\tif (!status_win)\n\t\tdie(\"Failed to create status window\");\n\n\t/* Enable keyboard mapping */\n\tkeypad(status_win, TRUE);\n\twbkgdset(status_win, get_line_attr(LINE_STATUS));\n}\n\n\n/*\n * Repository references\n */\n\nstatic struct ref *refs;\nstatic size_t refs_size;\n\nstatic struct ref **\nget_refs(char *id)\n{\n\tstruct ref **id_refs = NULL;\n\tsize_t id_refs_size = 0;\n\tsize_t i;\n\n\tfor (i = 0; i < refs_size; i++) {\n\t\tstruct ref **tmp;\n\n\t\tif (strcmp(id, refs[i].id))\n\t\t\tcontinue;\n\n\t\ttmp = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));\n\t\tif (!tmp) {\n\t\t\tif (id_refs)\n\t\t\t\tfree(id_refs);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tid_refs = tmp;\n\t\tif (id_refs_size > 0)\n\t\t\tid_refs[id_refs_size - 1]->next = 1;\n\t\tid_refs[id_refs_size] = &refs[i];\n\n\t\t/* XXX: The properties of the commit chains ensures that we can\n\t\t * safely modify the shared ref. The repo references will\n\t\t * always be similar for the same id. */\n\t\tid_refs[id_refs_size]->next = 0;\n\t\tid_refs_size++;\n\t}\n\n\treturn id_refs;\n}\n\nstatic int\nload_refs(void)\n{\n\tchar *cmd_env = getenv(\"TIG_LS_REMOTE\");\n\tchar *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;\n\tFILE *pipe = popen(cmd, \"r\");\n\tchar buffer[BUFSIZ];\n\tchar *line;\n\n\tif (!pipe)\n\t\treturn ERR;\n\n\twhile ((line = fgets(buffer, sizeof(buffer), pipe))) {\n\t\tchar *name = strchr(line, '\\t');\n\t\tstruct ref *ref;\n\t\tint namelen;\n\t\tbool tag = FALSE;\n\t\tbool tag_commit = FALSE;\n\n\t\tif (!name)\n\t\t\tcontinue;\n\n\t\t*name++ = 0;\n\t\tnamelen = strlen(name) - 1;\n\n\t\t/* Commits referenced by tags has \"^{}\" appended. */\n\t\tif (name[namelen - 1] == '}') {\n\t\t\twhile (namelen > 0 && name[namelen] != '^')\n\t\t\t\tnamelen--;\n\t\t\tif (namelen > 0)\n\t\t\t\ttag_commit = TRUE;\n\t\t}\n\t\tname[namelen] = 0;\n\n\t\tif (!strncmp(name, \"refs/tags/\", STRING_SIZE(\"refs/tags/\"))) {\n\t\t\tif (!tag_commit)\n\t\t\t\tcontinue;\n\t\t\tname += STRING_SIZE(\"refs/tags/\");\n\t\t\ttag = TRUE;\n\n\t\t} else if (!strncmp(name, \"refs/heads/\", STRING_SIZE(\"refs/heads/\"))) {\n\t\t\tname += STRING_SIZE(\"refs/heads/\");\n\n\t\t} else if (!strcmp(name, \"HEAD\")) {\n\t\t\tcontinue;\n\t\t}\n\n\t\trefs = realloc(refs, sizeof(*refs) * (refs_size + 1));\n\t\tif (!refs)\n\t\t\treturn ERR;\n\n\t\tref = &refs[refs_size++];\n\t\tref->tag = tag;\n\t\tref->name = strdup(name);\n\t\tif (!ref->name)\n\t\t\treturn ERR;\n\n\t\tstring_copy(ref->id, line);\n\t}\n\n\tif (ferror(pipe))\n\t\treturn ERR;\n\n\tpclose(pipe);\n\n\tif (refs_size == 0)\n\t\tdie(\"Not a git repository\");\n\n\treturn OK;\n}\n\n/*\n * Main\n */\n\nstatic void\nquit(int sig)\n{\n\t/* XXX: Restore tty modes and let the OS cleanup the rest! */\n\tif (cursed)\n\t\tendwin();\n\texit(0);\n}\n\nstatic void\ndie(const char *err, ...)\n{\n\tva_list args;\n\n\tendwin();\n\n\tva_start(args, err);\n\tfputs(\"tig: \", stderr);\n\tvfprintf(stderr, err, args);\n\tfputs(\"\\n\", stderr);\n\tva_end(args);\n\n\texit(1);\n}\n\nint\nmain(int argc, char *argv[])\n{\n\tstruct view *view;\n\tenum request request;\n\tsize_t i;\n\n\tsignal(SIGINT, quit);\n\n\tif (!parse_options(argc, argv))\n\t\treturn 0;\n\n\tif (load_refs() == ERR)\n\t\tdie(\"Failed to load refs.\");\n\n\tfor (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)\n\t\tview->cmd_env = getenv(view->cmd_env);\n\n\trequest = opt_request;\n\n\tinit_display();\n\n\twhile (view_driver(display[current_view], request)) {\n\t\tint key;\n\t\tint i;\n\n\t\tforeach_view (view, i)\n\t\t\tupdate_view(view);\n\n\t\t/* Refresh, accept single keystroke of input */\n\t\tkey = wgetch(status_win);\n\t\trequest = get_request(key);\n\n\t\t/* Some low-level request handling. This keeps access to\n\t\t * status_win restricted. */\n\t\tswitch (request) {\n\t\tcase REQ_PROMPT:\n\t\t\treport(\":\");\n\t\t\t/* Temporarily switch to line-oriented and echoed\n\t\t\t * input. */\n\t\t\tnocbreak();\n\t\t\techo();\n\n\t\t\tif (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {\n\t\t\t\tmemcpy(opt_cmd, \"git \", 4);\n\t\t\t\topt_request = REQ_VIEW_PAGER;\n\t\t\t} else {\n\t\t\t\trequest = ERR;\n\t\t\t}\n\n\t\t\tnoecho();\n\t\t\tcbreak();\n\t\t\tbreak;\n\n\t\tcase REQ_SCREEN_RESIZE:\n\t\t{\n\t\t\tint height, width;\n\n\t\t\tgetmaxyx(stdscr, height, width);\n\n\t\t\t/* Resize the status view and let the view driver take\n\t\t\t * care of resizing the displayed views. */\n\t\t\twresize(status_win, 1, width);\n\t\t\tmvwin(status_win, height - 1, 0);\n\t\t\twrefresh(status_win);\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tquit(0);\n\n\treturn 0;\n}\n\n/**\n * [[refspec]]\n * Revision specification\n * ----------------------\n * This section describes various ways to specify what revisions to display\n * or otherwise limit the view to. tig(1) does not itself parse the described\n * revision options so refer to the relevant git man pages for futher\n * information. Relevant man pages besides git-log(1) are git-diff(1) and\n * git-rev-list(1).\n *\n * You can tune the interaction with git by making use of the options\n * explained in this section. For example, by configuring the environment\n * variables described in the  <<view-commands, \"View commands\">> section.\n *\n * Limit by path name\n * ~~~~~~~~~~~~~~~~~~\n * If you are interested only in those revisions that made changes to a\n * specific file (or even several files) list the files like this:\n *\n *\t$ tig log Makefile\n *\n * To avoid ambiguity with repository references such as tag name, be sure\n * to separate file names from other git options using \"\\--\". So if you\n * have a file named 'master' it will clash with the reference named\n * 'master', and thus you will have to use:\n *\n *\t$ tig log -- master\n *\n * NOTE: For the main view, avoiding ambiguity will in some cases require\n * you to specify two \"\\--\" options. The first will make tig(1) stop\n * option processing and the latter will be passed to git log.\n *\n * Limit by date or number\n * ~~~~~~~~~~~~~~~~~~~~~~~\n * To speed up interaction with git, you can limit the amount of commits\n * to show both for the log and main view. Either limit by date using\n * e.g. `--since=1.month` or limit by the number of commits using `-n400`.\n *\n * If you are only interested in changed that happened between two dates\n * you can use:\n *\n *\t$ tig -- --after=May.5th --before=2006-05-16.15:44\n *\n * NOTE: The dot (\".\") is used as a separator instead of a space to avoid\n * having to quote the option value. If you prefer use `--after=\"May 5th\"`\n * instead of `--after=\"May 5th\"`.\n *\n * Limiting by commit ranges\n * ~~~~~~~~~~~~~~~~~~~~~~~~~\n * Alternatively, commits can be limited to a specific range, such as\n * \"all commits between 'tag-1.0' and 'tag-2.0'\". For example:\n *\n *\t$ tig log tag-1.0..tag-2.0\n *\n * This way of commit limiting makes it trivial to only browse the commits\n * which haven't been pushed to a remote branch. Assuming 'origin' is your\n * upstream remote branch, using:\n *\n *\t$ tig log origin..HEAD\n *\n * will list what will be pushed to the remote branch. Optionally, the ending\n * 'HEAD' can be left out since it is implied.\n *\n * Limiting by reachability\n * ~~~~~~~~~~~~~~~~~~~~~~~~\n * Git interprets the range specifier \"tag-1.0..tag-2.0\" as\n * \"all commits reachable from 'tag-2.0' but not from 'tag-1.0'\".\n * Where reachability refers to what commits are ancestors (or part of the\n * history) of the branch or tagged revision in question.\n *\n * If you prefer to specify which commit to preview in this way use the\n * following:\n *\n *\t$ tig log tag-2.0 ^tag-1.0\n *\n * You can think of '^' as a negation operator. Using this alternate syntax,\n * it is possible to further prune commits by specifying multiple branch\n * cut offs.\n *\n * Combining revisions specification\n * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n * Revisions options can to some degree be combined, which makes it possible\n * to say \"show at most 20 commits from within the last month that changed\n * files under the Documentation/ directory.\"\n *\n *\t$ tig -- --since=1.month -n20 -- Documentation/\n *\n * Examining all repository references\n * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n * In some cases, it can be useful to query changes across all references\n * in a repository. An example is to ask \"did any line of development in\n * this repository change a particular file within the last week\". This\n * can be accomplished using:\n *\n *\t$ tig -- --all --since=1.week -- Makefile\n *\n * BUGS\n * ----\n * Known bugs and problems:\n *\n * - If the screen width is very small the main view can draw\n *   outside the current view causing bad wrapping. Same goes\n *   for title and status windows.\n *\n * TODO\n * ----\n * Features that should be explored.\n *\n * - Searching.\n *\n * - Locale support.\n *\n * COPYRIGHT\n * ---------\n * Copyright (c) Jonas Fonseca <fonseca@diku.dk>, 2006\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * SEE ALSO\n * --------\n * [verse]\n * link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],\n * link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]\n * gitk(1): git repository browser written using tcl/tk,\n * qgit(1): git repository browser written using c++/Qt,\n * gitview(1): git repository browser written using python/gtk.\n **/\n"},{"id":"20138","messageId":"e4f5ko$sc1$1@sea.gmane.org","threadId":"4183","inReplyTo":"20060517120733.GA14041@diku.dk","subject":"Re: [ANNOUNCE] tig - text-mode interface for git","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2006-05-17T12:41:01Z","receivedAt":"2006-05-17T12:41:01Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Jonas Fonseca wrote:\n\n> Hello,\n> \n> I am pleased to announce tig, a simple git repository browser written\n> using ncurses. Basically, it just acts as a front-end for git-log and\n> git-show/git-diff. Additionally, you can also use it as a pager for git\n> commands.\n\nThanks a lot.\n\nAdded to http://git.or.cz/gitwiki/InterfacesFrontendsAndTools\n(under \"Graphical Interfaces\" section).\n\n-- \nJakub Narebski\nWarsaw, Poland\n"},{"id":"20142","messageId":"20060517165305.07b682b2.tihirvon@gmail.com","threadId":"4183","inReplyTo":"20060517120733.GA14041@diku.dk","subject":"Re: [ANNOUNCE] tig - text-mode interface for git","fromName":"Timo Hirvonen","fromEmail":"tihirvon@gmail.com","sentAt":"2006-05-17T13:53:05Z","receivedAt":"2006-05-17T13:53:05Z","isPatch":false,"sender":{"key":"tihirvon@gmail.com","avatar":null},"body":"Jonas Fonseca <fonseca@diku.dk> wrote:\n\n> Hello,\n> \n> I am pleased to announce tig, a simple git repository browser written\n> using ncurses. Basically, it just acts as a front-end for git-log and\n> git-show/git-diff. Additionally, you can also use it as a pager for git\n> commands.\n> \n> Currently, it just provides a minimum support for looking through\n> changes. I hope to slowly extend it to also be usable as a front-end for\n> git-blame and for tree browsing. Also, it doesn't do any fancy revision\n> graph rendering, but I want to at least give it a shot at some point. :)\n\nThanks.  This makes browsing repositories much easier.  It would be\ngreat if the colors were customizable.\n\n-- \nhttp://onion.dynserv.net/~timo/\n"},{"id":"20153","messageId":"20060517155310.GA12985@diku.dk","threadId":"4183","inReplyTo":"20060517165305.07b682b2.tihirvon@gmail.com","subject":"Re: [ANNOUNCE] tig - text-mode interface for git","fromName":"Jonas Fonseca","fromEmail":"fonseca@diku.dk","sentAt":"2006-05-17T15:53:10Z","receivedAt":"2006-05-17T15:53:10Z","isPatch":false,"sender":{"key":"fonseca@diku.dk","avatar":"https://gravatar.com/avatar/f82f3ad698717c51873b020c750a92438c820a24056dc39fe4d07baa10a92264?d=mp&s=160"},"body":"Timo Hirvonen <tihirvon@gmail.com> wrote Wed, May 17, 2006:\n> Jonas Fonseca <fonseca@diku.dk> wrote:\n> \n> > Hello,\n> > \n> > I am pleased to announce tig, a simple git repository browser written\n> > using ncurses. Basically, it just acts as a front-end for git-log and\n> > git-show/git-diff. Additionally, you can also use it as a pager for git\n> > commands.\n> > \n> > Currently, it just provides a minimum support for looking through\n> > changes. I hope to slowly extend it to also be usable as a front-end for\n> > git-blame and for tree browsing. Also, it doesn't do any fancy revision\n> > graph rendering, but I want to at least give it a shot at some point. :)\n> \n> Thanks.  This makes browsing repositories much easier.  It would be\n> great if the colors were customizable.\n\nI am open to providing a better default color scheme, since my fondness\nof green might be too dominating. For now you will have to edit the\ncolors defined under LINE_INFO, but sure. I think supporting something\nsimilar to CG_COLORS will suffice.\n\n-- \nJonas Fonseca\n"}]}