--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+\f
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
-/**
- * TIG(1)
- * ======
- *
- * NAME
- * ----
- * tig - text-mode interface for git
- *
- * SYNOPSIS
- * --------
- * [verse]
- * tig [options]
- * tig [options] [--] [git log options]
- * tig [options] log [git log options]
- * tig [options] diff [git diff options]
- * tig [options] show [git show options]
- * tig [options] < [git command output]
- *
- * DESCRIPTION
- * -----------
- * Browse changes in a git repository. Additionally, tig(1) can also act
- * as a pager for output of various git commands.
- *
- * When browsing repositories, tig(1) uses the underlying git commands
- * to present the user with various views, such as summarized commit log
- * and showing the commit with the log message, diffstat, and the diff.
- *
- * Using tig(1) as a pager, it will display input from stdin and try
- * to colorize it.
- **/
#ifndef VERSION
#define VERSION "tig-0.3"
static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
static void set_nonblocking_input(bool loading);
static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
+static void load_help_page(void);
#define ABS(x) ((x) >= 0 ? (x) : -(x))
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
#define SIZEOF_CMD 1024 /* Size of command buffer. */
+#define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
/* This color name can be used to refer to the default term colors. */
#define COLOR_DEFAULT (-1)
-#define TIG_HELP "(d)iff, (l)og, (m)ain, (q)uit, (h)elp"
-
/* The format and size of the date column in the main view. */
#define DATE_FORMAT "%Y-%m-%d %H:%M"
#define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
#define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
+#define TIG_LS_REMOTE \
+ "git ls-remote . 2>/dev/null"
+
+#define TIG_DIFF_CMD \
+ "git show --patch-with-stat --find-copies-harder -B -C %s"
+
+#define TIG_LOG_CMD \
+ "git log --cc --stat -n100 %s"
+
+#define TIG_MAIN_CMD \
+ "git log --topo-order --stat --pretty=raw %s"
+
+/* XXX: Needs to be defined to the empty string. */
+#define TIG_HELP_CMD ""
+#define TIG_PAGER_CMD ""
+
/* Some ascii-shorthands fitted into the ncurses namespace. */
#define KEY_TAB '\t'
#define KEY_RETURN '\r'
#define KEY_ESC 27
-/* User action requests. */
-enum request {
- /* Offset all requests to avoid conflicts with ncurses getch values. */
- REQ_OFFSET = KEY_MAX + 1,
-
- /* XXX: Keep the view request first and in sync with views[]. */
- REQ_VIEW_MAIN,
- REQ_VIEW_DIFF,
- REQ_VIEW_LOG,
- REQ_VIEW_HELP,
- REQ_VIEW_PAGER,
-
- REQ_ENTER,
- REQ_QUIT,
- REQ_PROMPT,
- REQ_SCREEN_REDRAW,
- REQ_SCREEN_RESIZE,
- REQ_SCREEN_UPDATE,
- REQ_SHOW_VERSION,
- REQ_STOP_LOADING,
- REQ_TOGGLE_LINE_NUMBERS,
- REQ_VIEW_NEXT,
- REQ_VIEW_CLOSE,
- REQ_NEXT,
- REQ_PREVIOUS,
-
- REQ_MOVE_UP,
- REQ_MOVE_DOWN,
- REQ_MOVE_PAGE_UP,
- REQ_MOVE_PAGE_DOWN,
- REQ_MOVE_FIRST_LINE,
- REQ_MOVE_LAST_LINE,
-
- REQ_SCROLL_LINE_UP,
- REQ_SCROLL_LINE_DOWN,
- REQ_SCROLL_PAGE_UP,
- REQ_SCROLL_PAGE_DOWN,
-};
-
struct ref {
char *name; /* Ref name; tag or head names are shortened. */
char id[41]; /* Commit SHA1 ID */
return name;
}
+static bool
+string_nformat(char *buf, size_t bufsize, int *bufpos, const char *fmt, ...)
+{
+ va_list args;
+ int pos = bufpos ? *bufpos : 0;
+
+ va_start(args, fmt);
+ pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
+ va_end(args);
+
+ if (bufpos)
+ *bufpos = pos;
+
+ return pos >= bufsize ? FALSE : TRUE;
+}
+
+#define string_format(buf, fmt, args...) \
+ string_nformat(buf, sizeof(buf), NULL, fmt, args)
+
+#define string_format_from(buf, from, fmt, args...) \
+ string_nformat(buf, sizeof(buf), from, fmt, args)
/* Shell quoting
*
}
-/**
- * OPTIONS
- * -------
- **/
+/*
+ * User requests
+ */
+
+#define REQ_INFO \
+ /* XXX: Keep the view request first and in sync with views[]. */ \
+ REQ_GROUP("View switching") \
+ REQ_(VIEW_MAIN, "Show main view"), \
+ REQ_(VIEW_DIFF, "Show diff view"), \
+ REQ_(VIEW_LOG, "Show log view"), \
+ REQ_(VIEW_HELP, "Show help page"), \
+ REQ_(VIEW_PAGER, "Show pager view"), \
+ \
+ REQ_GROUP("View manipulation") \
+ REQ_(ENTER, "Enter current line and scroll"), \
+ REQ_(NEXT, "Move to next"), \
+ REQ_(PREVIOUS, "Move to previous"), \
+ REQ_(VIEW_NEXT, "Move focus to next view"), \
+ REQ_(VIEW_CLOSE, "Close the current view"), \
+ REQ_(QUIT, "Close all views and quit"), \
+ \
+ REQ_GROUP("Cursor navigation") \
+ REQ_(MOVE_UP, "Move cursor one line up"), \
+ REQ_(MOVE_DOWN, "Move cursor one line down"), \
+ REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
+ REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
+ REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
+ REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
+ \
+ REQ_GROUP("Scrolling") \
+ REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
+ REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
+ REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
+ REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
+ \
+ REQ_GROUP("Misc") \
+ REQ_(PROMPT, "Bring up the prompt"), \
+ REQ_(SCREEN_UPDATE, "Update the screen"), \
+ REQ_(SCREEN_REDRAW, "Redraw the screen"), \
+ REQ_(SCREEN_RESIZE, "Resize the screen"), \
+ REQ_(SHOW_VERSION, "Show version information"), \
+ REQ_(STOP_LOADING, "Stop all loading views"), \
+ REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
+ REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"),
+
+
+/* User action requests. */
+enum request {
+#define REQ_GROUP(help)
+#define REQ_(req, help) REQ_##req
+
+ /* Offset all requests to avoid conflicts with ncurses getch values. */
+ REQ_OFFSET = KEY_MAX + 1,
+ REQ_INFO
+
+#undef REQ_GROUP
+#undef REQ_
+};
+
+struct request_info {
+ enum request request;
+ char *help;
+};
+
+static struct request_info req_info[] = {
+#define REQ_GROUP(help) { 0, (help) },
+#define REQ_(req, help) { REQ_##req, (help) }
+ REQ_INFO
+#undef REQ_GROUP
+#undef REQ_
+};
+
+/*
+ * Options
+ */
static const char usage[] =
VERSION " (" __DATE__ ")\n"
/* Option and state variables. */
static bool opt_line_number = FALSE;
+static bool opt_rev_graph = TRUE;
static int opt_num_interval = NUMBER_INTERVAL;
static int opt_tab_size = TABSIZE;
static enum request opt_request = REQ_VIEW_MAIN;
static bool opt_utf8 = TRUE;
static FILE *opt_pipe = NULL;
+enum option_type {
+ OPT_NONE,
+ OPT_INT,
+};
+
+static bool
+check_option(char *opt, char short_name, char *name, enum option_type type, ...)
+{
+ va_list args;
+ char *value = "";
+ int *number;
+
+ if (opt[0] != '-')
+ return FALSE;
+
+ if (opt[1] == '-') {
+ int namelen = strlen(name);
+
+ opt += 2;
+
+ if (strncmp(opt, name, namelen))
+ return FALSE;
+
+ if (opt[namelen] == '=')
+ value = opt + namelen + 1;
+
+ } else {
+ if (!short_name || opt[1] != short_name)
+ return FALSE;
+ value = opt + 2;
+ }
+
+ va_start(args, type);
+ if (type == OPT_INT) {
+ number = va_arg(args, int *);
+ if (isdigit(*value))
+ *number = atoi(value);
+ }
+ va_end(args);
+
+ return TRUE;
+}
+
/* Returns the index of log or diff command or -1 to exit. */
static bool
parse_options(int argc, char *argv[])
for (i = 1; i < argc; i++) {
char *opt = argv[i];
- /**
- * -l::
- * Start up in log view using the internal log command.
- **/
if (!strcmp(opt, "-l")) {
opt_request = REQ_VIEW_LOG;
continue;
}
- /**
- * -d::
- * Start up in diff view using the internal diff command.
- **/
if (!strcmp(opt, "-d")) {
opt_request = REQ_VIEW_DIFF;
continue;
}
- /**
- * -n[INTERVAL], --line-number[=INTERVAL]::
- * Prefix line numbers in log and diff view.
- * Optionally, with interval different than each line.
- **/
- if (!strncmp(opt, "-n", 2) ||
- !strncmp(opt, "--line-number", 13)) {
- char *num = opt;
-
- if (opt[1] == 'n') {
- num = opt + 2;
-
- } else if (opt[STRING_SIZE("--line-number")] == '=') {
- num = opt + STRING_SIZE("--line-number=");
- }
-
- if (isdigit(*num))
- opt_num_interval = atoi(num);
-
+ if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
opt_line_number = TRUE;
continue;
}
- /**
- * -b[NSPACES], --tab-size[=NSPACES]::
- * Set the number of spaces tabs should be expanded to.
- **/
- if (!strncmp(opt, "-b", 2) ||
- !strncmp(opt, "--tab-size", 10)) {
- char *num = opt;
-
- if (opt[1] == 'b') {
- num = opt + 2;
-
- } else if (opt[STRING_SIZE("--tab-size")] == '=') {
- num = opt + STRING_SIZE("--tab-size=");
- }
-
- if (isdigit(*num))
- opt_tab_size = MIN(atoi(num), TABSIZE);
+ if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
+ opt_tab_size = MIN(opt_tab_size, TABSIZE);
continue;
}
- /**
- * -v, --version::
- * Show version and exit.
- **/
- if (!strcmp(opt, "-v") ||
- !strcmp(opt, "--version")) {
+ if (check_option(opt, 'v', "version", OPT_NONE)) {
printf("tig version %s\n", VERSION);
return FALSE;
}
- /**
- * -h, --help::
- * Show help message and exit.
- **/
- if (!strcmp(opt, "-h") ||
- !strcmp(opt, "--help")) {
+ if (check_option(opt, 'h', "help", OPT_NONE)) {
printf(usage);
return FALSE;
}
- /**
- * \--::
- * End of tig(1) options. Useful when specifying command
- * options for the main view. Example:
- *
- * $ tig -- --since=1.month
- **/
if (!strcmp(opt, "--")) {
i++;
break;
}
- /**
- * log [git log options]::
- * Open log view using the given git log options.
- *
- * diff [git diff options]::
- * Open diff view using the given git diff options.
- *
- * show [git show options]::
- * Open diff view using the given git show options.
- **/
if (!strcmp(opt, "log") ||
!strcmp(opt, "diff") ||
!strcmp(opt, "show")) {
break;
}
- /**
- * [git log options]::
- * tig(1) will stop the option parsing when the first
- * command line parameter not starting with "-" is
- * encountered. All options including this one will be
- * passed to git log when loading the main view.
- * This makes it possible to say:
- *
- * $ tig tag-1.0..HEAD
- **/
if (opt[0] && opt[0] != '-')
break;
- die("unknown command '%s'", opt);
+ die("unknown option '%s'\n\n%s", opt, usage);
}
if (!isatty(STDIN_FILENO)) {
}
-/**
- * ENVIRONMENT VARIABLES
- * ---------------------
- * TIG_LS_REMOTE::
- * Set command for retrieving all repository references. The command
- * should output data in the same format as git-ls-remote(1).
- **/
-
-#define TIG_LS_REMOTE \
- "git ls-remote . 2>/dev/null"
-
-/**
- * TIG_DIFF_CMD::
- * The command used for the diff view. By default, git show is used
- * as a backend.
- *
- * TIG_LOG_CMD::
- * The command used for the log view. If you prefer to have both
- * author and committer shown in the log view be sure to pass
- * `--pretty=fuller` to git log.
- *
- * TIG_MAIN_CMD::
- * The command used for the main view. Note, you must always specify
- * the option: `--pretty=raw` since the main view parser expects to
- * read that format.
- **/
-
-#define TIG_DIFF_CMD \
- "git show --patch-with-stat --find-copies-harder -B -C %s"
-
-#define TIG_LOG_CMD \
- "git log --cc --stat -n100 %s"
-
-#define TIG_MAIN_CMD \
- "git log --topo-order --stat --pretty=raw %s"
-
-/* ... silently ignore that the following are also exported. */
-
-#define TIG_HELP_CMD \
- "man tig 2>/dev/null"
-
-#define TIG_PAGER_CMD \
- ""
-
-
-/**
- * FILES
- * -----
- * '~/.tigrc'::
- * User configuration file. See tigrc(5) for examples.
- *
- * '.git/config'::
- * Repository config file. Read on startup with the help of
- * git-repo-config(1).
- **/
-
-static struct int_map color_map[] = {
-#define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
- COLOR_MAP(DEFAULT),
- COLOR_MAP(BLACK),
- COLOR_MAP(BLUE),
- COLOR_MAP(CYAN),
- COLOR_MAP(GREEN),
- COLOR_MAP(MAGENTA),
- COLOR_MAP(RED),
- COLOR_MAP(WHITE),
- COLOR_MAP(YELLOW),
-};
-
-static struct int_map attr_map[] = {
-#define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
- ATTR_MAP(NORMAL),
- ATTR_MAP(BLINK),
- ATTR_MAP(BOLD),
- ATTR_MAP(DIM),
- ATTR_MAP(REVERSE),
- ATTR_MAP(STANDOUT),
- ATTR_MAP(UNDERLINE),
-};
+/*
+ * Line-oriented content detection.
+ */
#define LINE_INFO \
LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
-
-/*
- * Line-oriented content detection.
- */
-
enum line_type {
#define LINE(type, line, fg, bg, attr) \
LINE_##type
* User config file handling.
*/
+static struct int_map color_map[] = {
+#define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
+ COLOR_MAP(DEFAULT),
+ COLOR_MAP(BLACK),
+ COLOR_MAP(BLUE),
+ COLOR_MAP(CYAN),
+ COLOR_MAP(GREEN),
+ COLOR_MAP(MAGENTA),
+ COLOR_MAP(RED),
+ COLOR_MAP(WHITE),
+ COLOR_MAP(YELLOW),
+};
+
#define set_color(color, name, namelen) \
set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, namelen)
+static struct int_map attr_map[] = {
+#define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
+ ATTR_MAP(NORMAL),
+ ATTR_MAP(BLINK),
+ ATTR_MAP(BOLD),
+ ATTR_MAP(DIM),
+ ATTR_MAP(REVERSE),
+ ATTR_MAP(STANDOUT),
+ ATTR_MAP(UNDERLINE),
+};
+
#define set_attribute(attr, name, namelen) \
set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, namelen)
config_lineno = 0;
config_errors = FALSE;
- if (!home ||
- snprintf(buf, sizeof(buf), "%s/.tigrc", home) >= sizeof(buf))
+ if (!home || !string_format(buf, "%s/.tigrc", home))
return ERR;
/* It's ok that the file doesn't exist. */
/* Buffering */
unsigned long lines; /* Total number of lines */
struct line *line; /* Line index */
+ unsigned long line_size;/* Total number of allocated lines */
unsigned int digits; /* Number of digits in the lines member. */
/* Loading */
/* Draw one line; @lineno must be < view->height. */
bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
/* Read one line; updates view->line. */
- bool (*read)(struct view *view, struct line *prev, char *data);
+ bool (*read)(struct view *view, char *data);
/* Depending on view, change display based on current line. */
bool (*enter)(struct view *view, struct line *line);
};
wprintw(view->title, "[%s]", view->name);
if (view->lines || view->pipe) {
+ unsigned int view_lines = view->offset + view->height;
unsigned int lines = view->lines
- ? (view->lineno + 1) * 100 / view->lines
+ ? MIN(view_lines, view->lines) * 100 / view->lines
: 0;
wprintw(view->title, " - %s %d of %d (%d%%)",
} else {
const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
- if (snprintf(view->cmd, sizeof(view->cmd), format,
- id, id, id, id, id) >= sizeof(view->cmd))
+ if (!string_format(view->cmd, format, id, id, id, id, id))
return FALSE;
}
return TRUE;
}
+static struct line *
+realloc_lines(struct view *view, size_t line_size)
+{
+ struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
+
+ if (!tmp)
+ return NULL;
+
+ view->line = tmp;
+ view->line_size = line_size;
+ return view->line;
+}
+
static bool
update_view(struct view *view)
{
char buffer[BUFSIZ];
char *line;
- struct line *tmp;
/* The number of lines to read. If too low it will cause too much
* redrawing (and possible flickering), if too high responsiveness
* will suffer. */
if (view->offset + view->height >= view->lines)
redraw_from = view->lines - view->offset;
- tmp = realloc(view->line, sizeof(*view->line) * (view->lines + lines));
- if (!tmp)
+ if (!realloc_lines(view, view->lines + lines))
goto alloc_error;
- view->line = tmp;
-
while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
int linelen = strlen(line);
- struct line *prev = view->lines
- ? &view->line[view->lines - 1]
- : NULL;
-
if (linelen)
line[linelen - 1] = 0;
- if (!view->ops->read(view, prev, line))
+ if (!view->ops->read(view, line))
goto alloc_error;
if (lines-- == 1)
goto end;
} else if (feof(view->pipe)) {
- if (view == VIEW(REQ_VIEW_HELP)) {
- const char *msg = TIG_HELP;
-
- if (view->lines == 0) {
- /* Slightly ugly, but abusing view->ref keeps
- * the error message. */
- string_copy(view->ref, "No help available");
- msg = "The tig(1) manpage is not installed";
- }
-
- report("%s", msg);
- goto end;
- }
-
report("");
goto end;
}
return;
}
- if ((reload || strcmp(view->vid, view->id)) &&
- !begin_update(view)) {
+ if (view == VIEW(REQ_VIEW_HELP)) {
+ load_help_page();
+
+ } else if ((reload || strcmp(view->vid, view->id)) &&
+ !begin_update(view)) {
report("Failed to load %s view", view->name);
return;
}
if (split) {
- display[current_view + 1] = view;
+ display[1] = view;
if (!backgrounded)
- current_view++;
+ current_view = 1;
} else {
/* Maximize the current view. */
memset(display, 0, sizeof(display));
report("");
} else {
redraw_view(view);
- if (view == VIEW(REQ_VIEW_HELP))
- report("%s", TIG_HELP);
- else
- report("");
+ report("");
}
/* If the view is backgrounded the above calls to report()
report("");
break;
}
- case REQ_TOGGLE_LINE_NUMBERS:
+ case REQ_TOGGLE_LINENO:
opt_line_number = !opt_line_number;
redraw_display();
break;
+ case REQ_TOGGLE_REV_GRAPH:
+ opt_rev_graph = !opt_rev_graph;
+ redraw_display();
+ break;
+
case REQ_PROMPT:
/* Always reload^Wrerun commands from the prompt. */
open_view(view, opt_request, OPEN_RELOAD);
return TRUE;
}
+static void
+add_pager_refs(struct view *view, struct line *line)
+{
+ char buf[1024];
+ char *data = line->data;
+ struct ref **refs;
+ int bufpos = 0, refpos = 0;
+ const char *sep = "Refs: ";
+
+ assert(line->type == LINE_COMMIT);
+
+ refs = get_refs(data + STRING_SIZE("commit "));
+ if (!refs)
+ return;
+
+ do {
+ struct ref *ref = refs[refpos];
+ char *fmt = ref->tag ? "%s[%s]" : "%s%s";
+
+ if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
+ return;
+ sep = ", ";
+ } while (refs[refpos++]->next);
+
+ if (!realloc_lines(view, view->line_size + 1))
+ return;
+
+ line = &view->line[view->lines];
+ line->data = strdup(buf);
+ if (!line->data)
+ return;
+
+ line->type = LINE_PP_REFS;
+ view->lines++;
+}
+
static bool
-pager_read(struct view *view, struct line *prev, char *line)
+pager_read(struct view *view, char *data)
{
- /* Compress empty lines in the help view. */
- if (view == VIEW(REQ_VIEW_HELP) &&
- !*line && prev && !*((char *) prev->data))
- return TRUE;
+ struct line *line = &view->line[view->lines];
- view->line[view->lines].data = strdup(line);
- if (!view->line[view->lines].data)
+ line->data = strdup(data);
+ if (!line->data)
return FALSE;
- view->line[view->lines].type = get_line_type(line);
-
+ line->type = get_line_type(line->data);
view->lines++;
+
+ if (line->type == LINE_COMMIT &&
+ (view == VIEW(REQ_VIEW_DIFF) ||
+ view == VIEW(REQ_VIEW_LOG)))
+ add_pager_refs(view, line);
+
return TRUE;
}
*/
struct commit {
- char id[41]; /* SHA1 ID. */
- char title[75]; /* The first line of the commit message. */
- char author[75]; /* The author of the commit. */
- struct tm time; /* Date from the author ident. */
- struct ref **refs; /* Repository references; tags & branch heads. */
+ char id[41]; /* SHA1 ID. */
+ char title[75]; /* First line of the commit message. */
+ char author[75]; /* Author of the commit. */
+ struct tm time; /* Date from the author ident. */
+ struct ref **refs; /* Repository references. */
+ chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
+ size_t graph_size; /* The width of the graph array. */
};
static bool
if (type != LINE_CURSOR)
wattrset(view->win, A_NORMAL);
- mvwaddch(view->win, lineno, col, ACS_LTEE);
- wmove(view->win, lineno, col + 2);
- col += 2;
+ if (opt_rev_graph && commit->graph_size) {
+ size_t i;
+
+ wmove(view->win, lineno, col);
+ /* Using waddch() instead of waddnstr() ensures that
+ * they'll be rendered correctly for the cursor line. */
+ for (i = 0; i < commit->graph_size; i++)
+ waddch(view->win, commit->graph[i]);
+
+ col += commit->graph_size + 1;
+ }
+
+ wmove(view->win, lineno, col);
if (commit->refs) {
size_t i = 0;
/* Reads git log --pretty=raw output and parses it into the commit struct. */
static bool
-main_read(struct view *view, struct line *prev, char *line)
+main_read(struct view *view, char *line)
{
enum line_type type = get_line_type(line);
- struct commit *commit;
+ struct commit *commit = view->lines
+ ? view->line[view->lines - 1].data : NULL;
switch (type) {
case LINE_COMMIT:
view->line[view->lines++].data = commit;
string_copy(commit->id, line);
commit->refs = get_refs(commit->id);
+ commit->graph[commit->graph_size++] = ACS_LTEE;
break;
case LINE_AUTHOR:
char *ident = line + STRING_SIZE("author ");
char *end = strchr(ident, '<');
- if (!prev)
+ if (!commit)
break;
- commit = prev->data;
-
if (end) {
for (; end > ident && isspace(end[-1]); end--) ;
*end = 0;
break;
}
default:
- if (!prev)
+ if (!commit)
break;
- commit = prev->data;
-
/* Fill in the commit title if it has not already been set. */
if (commit->title[0])
break;
{ 'l', REQ_VIEW_LOG },
{ 'p', REQ_VIEW_PAGER },
{ 'h', REQ_VIEW_HELP },
+ { '?', REQ_VIEW_HELP },
/* View manipulation */
{ 'q', REQ_VIEW_CLOSE },
{ 'z', REQ_STOP_LOADING },
{ 'v', REQ_SHOW_VERSION },
{ 'r', REQ_SCREEN_REDRAW },
- { 'n', REQ_TOGGLE_LINE_NUMBERS },
+ { 'n', REQ_TOGGLE_LINENO },
+ { 'g', REQ_TOGGLE_REV_GRAPH},
{ ':', REQ_PROMPT },
/* wgetch() with nodelay() enabled returns ERR when there's no input. */
return (enum request) key;
}
+struct key {
+ char *name;
+ int value;
+};
+
+static struct key key_table[] = {
+ { "Enter", KEY_RETURN },
+ { "Space", ' ' },
+ { "Backspace", KEY_BACKSPACE },
+ { "Tab", KEY_TAB },
+ { "Escape", KEY_ESC },
+ { "Left", KEY_LEFT },
+ { "Right", KEY_RIGHT },
+ { "Up", KEY_UP },
+ { "Down", KEY_DOWN },
+ { "Insert", KEY_IC },
+ { "Delete", KEY_DC },
+ { "Home", KEY_HOME },
+ { "End", KEY_END },
+ { "PageUp", KEY_PPAGE },
+ { "PageDown", KEY_NPAGE },
+ { "F1", KEY_F(1) },
+ { "F2", KEY_F(2) },
+ { "F3", KEY_F(3) },
+ { "F4", KEY_F(4) },
+ { "F5", KEY_F(5) },
+ { "F6", KEY_F(6) },
+ { "F7", KEY_F(7) },
+ { "F8", KEY_F(8) },
+ { "F9", KEY_F(9) },
+ { "F10", KEY_F(10) },
+ { "F11", KEY_F(11) },
+ { "F12", KEY_F(12) },
+};
+
+static char *
+get_key(enum request request)
+{
+ static char buf[BUFSIZ];
+ static char key_char[] = "'X'";
+ int pos = 0;
+ char *sep = " ";
+ int i;
+
+ buf[pos] = 0;
+
+ for (i = 0; i < ARRAY_SIZE(keymap); i++) {
+ char *seq = NULL;
+ int key;
+
+ if (keymap[i].request != request)
+ continue;
+
+ for (key = 0; key < ARRAY_SIZE(key_table); key++)
+ if (key_table[key].value == keymap[i].alias)
+ seq = key_table[key].name;
+
+ if (seq == NULL &&
+ keymap[i].alias < 127 &&
+ isprint(keymap[i].alias)) {
+ key_char[1] = (char) keymap[i].alias;
+ seq = key_char;
+ }
+
+ if (!seq)
+ seq = "'?'";
+
+ if (!string_format_from(buf, &pos, "%s%s", sep, seq))
+ return "Too many keybindings!";
+ sep = ", ";
+ }
+
+ return buf;
+}
+
+static void load_help_page(void)
+{
+ char buf[BUFSIZ];
+ struct view *view = VIEW(REQ_VIEW_HELP);
+ int lines = ARRAY_SIZE(req_info) + 2;
+ int i;
+
+ if (view->lines > 0)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(req_info); i++)
+ if (!req_info[i].request)
+ lines++;
+
+ view->line = calloc(lines, sizeof(*view->line));
+ if (!view->line) {
+ report("Allocation failure");
+ return;
+ }
+
+ pager_read(view, "Quick reference for tig keybindings:");
+
+ for (i = 0; i < ARRAY_SIZE(req_info); i++) {
+ char *key;
+
+ if (!req_info[i].request) {
+ pager_read(view, "");
+ pager_read(view, req_info[i].help);
+ continue;
+ }
+
+ key = get_key(req_info[i].request);
+ if (!string_format(buf, "%-25s %s", key, req_info[i].help))
+ continue;
+
+ pager_read(view, buf);
+ }
+}
+
/*
* Unicode / UTF-8 handling
{
struct ref *ref;
bool tag = FALSE;
- bool tag_commit = FALSE;
- /* Commits referenced by tags has "^{}" appended. */
- if (name[namelen - 1] == '}') {
+ if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
+ /* Commits referenced by tags has "^{}" appended. */
+ if (name[namelen - 1] != '}')
+ return OK;
+
while (namelen > 0 && name[namelen] != '^')
namelen--;
- if (namelen > 0)
- tag_commit = TRUE;
- name[namelen] = 0;
- }
- if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
- if (!tag_commit)
- return OK;
- name += STRING_SIZE("refs/tags/");
tag = TRUE;
+ namelen -= STRING_SIZE("refs/tags/");
+ name += STRING_SIZE("refs/tags/");
} else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
- name += STRING_SIZE("refs/heads/");
+ namelen -= STRING_SIZE("refs/heads/");
+ name += STRING_SIZE("refs/heads/");
} else if (!strcmp(name, "HEAD")) {
return OK;
return ERR;
ref = &refs[refs_size++];
- ref->name = strdup(name);
+ ref->name = malloc(namelen + 1);
if (!ref->name)
return ERR;
+ strncpy(ref->name, name, namelen);
+ ref->name[namelen] = 0;
ref->tag = tag;
string_copy(ref->id, id);
static int
read_repo_config_option(char *name, int namelen, char *value, int valuelen)
{
- if (!strcmp(name, "i18n.commitencoding")) {
+ if (!strcmp(name, "i18n.commitencoding"))
string_copy(opt_encoding, value);
- }
return OK;
}
return 0;
}
-
-/**
- * include::BUGS[]
- *
- * COPYRIGHT
- * ---------
- * Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * SEE ALSO
- * --------
- * - link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
- * - link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
- *
- * Other git repository browsers:
- *
- * - gitk(1)
- * - qgit(1)
- * - gitview(1)
- *
- * Sites:
- *
- * include::SITES[]
- **/