Move tig(1) material to tig.1.txt
[tig] / tig.c
diff --git a/tig.c b/tig.c
index 22943f0..859681c 100644 (file)
--- a/tig.c
+++ b/tig.c
  * 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"
@@ -94,6 +64,22 @@ static void load_help_page(void);
 
 #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'
@@ -164,6 +150,27 @@ chomp_string(char *name)
        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
  *
@@ -277,10 +284,9 @@ static struct request_info req_info[] = {
 #undef REQ_
 };
 
-/**
- * OPTIONS
- * -------
- **/
+/*
+ * Options
+ */
 
 static const char usage[] =
 VERSION " (" __DATE__ ")\n"
@@ -320,29 +326,16 @@ 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;
@@ -361,10 +354,6 @@ parse_options(int argc, char *argv[])
                        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;
@@ -381,48 +370,23 @@ parse_options(int argc, char *argv[])
                        continue;
                }
 
-               /**
-                * -v, --version::
-                *      Show version and exit.
-                **/
                if (!strcmp(opt, "-v") ||
                    !strcmp(opt, "--version")) {
                        printf("tig version %s\n", VERSION);
                        return FALSE;
                }
 
-               /**
-                * -h, --help::
-                *      Show help message and exit.
-                **/
                if (!strcmp(opt, "-h") ||
                    !strcmp(opt, "--help")) {
                        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")) {
@@ -431,16 +395,6 @@ parse_options(int argc, char *argv[])
                        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;
 
@@ -481,62 +435,6 @@ parse_options(int argc, char *argv[])
 }
 
 
-/**
- * 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 \
-       ""
-
-#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),
@@ -582,6 +480,7 @@ LINE(PP_MERGE,         "Merge: ",           COLOR_BLUE,     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), \
@@ -798,8 +697,7 @@ load_options(void)
        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. */
@@ -876,7 +774,7 @@ struct view_ops {
        /* 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);
 };
@@ -1271,8 +1169,7 @@ begin_update(struct view *view)
        } 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;
        }
 
@@ -1347,14 +1244,10 @@ update_view(struct view *view)
        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)
@@ -1708,16 +1601,59 @@ pager_draw(struct view *view, struct line *line, unsigned int lineno)
        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)
 {
-       view->line[view->lines].data = strdup(line);
-       if (!view->line[view->lines].data)
-               return FALSE;
+       struct line *line = &view->line[view->lines];
 
-       view->line[view->lines].type = get_line_type(line);
+       line->data = strdup(data);
+       if (!line->data)
+               return FALSE;
 
+       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;
 }
 
@@ -1868,10 +1804,11 @@ main_draw(struct view *view, struct line *line, unsigned int lineno)
 
 /* 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:
@@ -1891,11 +1828,9 @@ main_read(struct view *view, struct line *prev, char *line)
                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;
@@ -1934,11 +1869,9 @@ main_read(struct view *view, struct line *prev, char *line)
                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;
@@ -2110,8 +2043,7 @@ get_key(enum request request)
                if (!seq)
                        seq = "'?'";
 
-               pos += snprintf(buf + pos, sizeof(buf) - pos, "%s%s", sep, seq);
-               if (pos >= sizeof(buf))
+               if (!string_format_from(buf, &pos, "%s%s", sep, seq))
                        return "Too many keybindings!";
                sep = ", ";
        }
@@ -2139,23 +2071,22 @@ static void load_help_page(void)
                return;
        }
 
-       pager_read(view, NULL, "Quick reference for tig keybindings:");
+       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, NULL, "");
-                       pager_read(view, NULL, req_info[i].help);
+                       pager_read(view, "");
+                       pager_read(view, req_info[i].help);
                        continue;
                }
 
                key = get_key(req_info[i].request);
-               if (snprintf(buf, sizeof(buf), "%-25s %s", key, req_info[i].help)
-                   >= sizeof(buf))
+               if (!string_format(buf, "%-25s %s", key, req_info[i].help))
                        continue;
 
-               pager_read(view, NULL, buf);
+               pager_read(view, buf);
        }
 }
 
@@ -2703,31 +2634,3 @@ main(int argc, char *argv[])
 
        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[]
- **/