Merge branch 'nd/fetch-ref-summary'

Improve the look of the way "git fetch" reports what happened to
each ref that was fetched.

* nd/fetch-ref-summary:
  fetch: reduce duplicate in ref update status lines with placeholder
  fetch: align all "remote -> local" output
  fetch: change flag code for displaying tag update and deleted ref
  fetch: refactor ref update status formatting code
  git-fetch.txt: document fetch output
This commit is contained in:
Junio C Hamano 2016-07-19 13:22:21 -07:00
commit 566fdaf611
4 changed files with 243 additions and 40 deletions

View File

@ -1243,6 +1243,11 @@ fetch.prune::
If true, fetch will automatically behave as if the `--prune`
option was given on the command line. See also `remote.<name>.prune`.
fetch.output::
Control how ref update status is printed. Valid values are
`full` and `compact`. Default value is `full`. See section
OUTPUT in linkgit:git-fetch[1] for detail.
format.attach::
Enable multipart/mixed attachments as the default for
'format-patch'. The value can also be a double quoted string

View File

@ -99,6 +99,57 @@ The latter use of the `remote.<repository>.fetch` values can be
overridden by giving the `--refmap=<refspec>` parameter(s) on the
command line.
OUTPUT
------
The output of "git fetch" depends on the transport method used; this
section describes the output when fetching over the Git protocol
(either locally or via ssh) and Smart HTTP protocol.
The status of the fetch is output in tabular form, with each line
representing the status of a single ref. Each line is of the form:
-------------------------------
<flag> <summary> <from> -> <to> [<reason>]
-------------------------------
The status of up-to-date refs is shown only if the --verbose option is
used.
In compact output mode, specified with configuration variable
fetch.output, if either entire `<from>` or `<to>` is found in the
other string, it will be substituted with `*` in the other string. For
example, `master -> origin/master` becomes `master -> origin/*`.
flag::
A single character indicating the status of the ref:
(space);; for a successfully fetched fast-forward;
`+`;; for a successful forced update;
`-`;; for a successfully pruned ref;
`t`;; for a successful tag update;
`*`;; for a successfully fetched new ref;
`!`;; for a ref that was rejected or failed to update; and
`=`;; for a ref that was up to date and did not need fetching.
summary::
For a successfully fetched ref, the summary shows the old and new
values of the ref in a form suitable for using as an argument to
`git log` (this is `<old>..<new>` in most cases, and
`<old>...<new>` for forced non-fast-forward updates).
from::
The name of the remote ref being fetched from, minus its
`refs/<type>/` prefix. In the case of deletion, the name of
the remote ref is "(none)".
to::
The name of the local ref being updated, minus its
`refs/<type>/` prefix.
reason::
A human-readable explanation. In the case of successfully fetched
refs, no explanation is needed. For a failed ref, the reason for
failure is described.
EXAMPLES
--------

View File

@ -15,6 +15,7 @@
#include "submodule.h"
#include "connected.h"
#include "argv-array.h"
#include "utf8.h"
static const char * const builtin_fetch_usage[] = {
N_("git fetch [<options>] [<repository> [<refspec>...]]"),
@ -449,7 +450,132 @@ fail:
: STORE_REF_ERROR_OTHER;
}
#define REFCOL_WIDTH 10
static int refcol_width = 10;
static int compact_format;
static void adjust_refcol_width(const struct ref *ref)
{
int max, rlen, llen, len;
/* uptodate lines are only shown on high verbosity level */
if (!verbosity && !oidcmp(&ref->peer_ref->old_oid, &ref->old_oid))
return;
max = term_columns();
rlen = utf8_strwidth(prettify_refname(ref->name));
llen = utf8_strwidth(prettify_refname(ref->peer_ref->name));
/*
* rough estimation to see if the output line is too long and
* should not be counted (we can't do precise calculation
* anyway because we don't know if the error explanation part
* will be printed in update_local_ref)
*/
if (compact_format) {
llen = 0;
max = max * 2 / 3;
}
len = 21 /* flag and summary */ + rlen + 4 /* -> */ + llen;
if (len >= max)
return;
/*
* Not precise calculation for compact mode because '*' can
* appear on the left hand side of '->' and shrink the column
* back.
*/
if (refcol_width < rlen)
refcol_width = rlen;
}
static void prepare_format_display(struct ref *ref_map)
{
struct ref *rm;
const char *format = "full";
git_config_get_string_const("fetch.output", &format);
if (!strcasecmp(format, "full"))
compact_format = 0;
else if (!strcasecmp(format, "compact"))
compact_format = 1;
else
die(_("configuration fetch.output contains invalid value %s"),
format);
for (rm = ref_map; rm; rm = rm->next) {
if (rm->status == REF_STATUS_REJECT_SHALLOW ||
!rm->peer_ref ||
!strcmp(rm->name, "HEAD"))
continue;
adjust_refcol_width(rm);
}
}
static void print_remote_to_local(struct strbuf *display,
const char *remote, const char *local)
{
strbuf_addf(display, "%-*s -> %s", refcol_width, remote, local);
}
static int find_and_replace(struct strbuf *haystack,
const char *needle,
const char *placeholder)
{
const char *p = strstr(haystack->buf, needle);
int plen, nlen;
if (!p)
return 0;
if (p > haystack->buf && p[-1] != '/')
return 0;
plen = strlen(p);
nlen = strlen(needle);
if (plen > nlen && p[nlen] != '/')
return 0;
strbuf_splice(haystack, p - haystack->buf, nlen,
placeholder, strlen(placeholder));
return 1;
}
static void print_compact(struct strbuf *display,
const char *remote, const char *local)
{
struct strbuf r = STRBUF_INIT;
struct strbuf l = STRBUF_INIT;
if (!strcmp(remote, local)) {
strbuf_addf(display, "%-*s -> *", refcol_width, remote);
return;
}
strbuf_addstr(&r, remote);
strbuf_addstr(&l, local);
if (!find_and_replace(&r, local, "*"))
find_and_replace(&l, remote, "*");
print_remote_to_local(display, r.buf, l.buf);
strbuf_release(&r);
strbuf_release(&l);
}
static void format_display(struct strbuf *display, char code,
const char *summary, const char *error,
const char *remote, const char *local)
{
strbuf_addf(display, "%c %-*s ", code, TRANSPORT_SUMMARY(summary));
if (!compact_format)
print_remote_to_local(display, remote, local);
else
print_compact(display, remote, local);
if (error)
strbuf_addf(display, " (%s)", error);
}
static int update_local_ref(struct ref *ref,
const char *remote,
@ -467,9 +593,8 @@ static int update_local_ref(struct ref *ref,
if (!oidcmp(&ref->old_oid, &ref->new_oid)) {
if (verbosity > 0)
strbuf_addf(display, "= %-*s %-*s -> %s",
TRANSPORT_SUMMARY(_("[up to date]")),
REFCOL_WIDTH, remote, pretty_ref);
format_display(display, '=', _("[up to date]"), NULL,
remote, pretty_ref);
return 0;
}
@ -481,10 +606,9 @@ static int update_local_ref(struct ref *ref,
* If this is the head, and it's not okay to update
* the head, and the old value of the head isn't empty...
*/
strbuf_addf(display,
_("! %-*s %-*s -> %s (can't fetch in current branch)"),
TRANSPORT_SUMMARY(_("[rejected]")),
REFCOL_WIDTH, remote, pretty_ref);
format_display(display, '!', _("[rejected]"),
_("can't fetch in current branch"),
remote, pretty_ref);
return 1;
}
@ -492,11 +616,9 @@ static int update_local_ref(struct ref *ref,
starts_with(ref->name, "refs/tags/")) {
int r;
r = s_update_ref("updating tag", ref, 0);
strbuf_addf(display, "%c %-*s %-*s -> %s%s",
r ? '!' : '-',
TRANSPORT_SUMMARY(_("[tag update]")),
REFCOL_WIDTH, remote, pretty_ref,
r ? _(" (unable to update local ref)") : "");
format_display(display, r ? '!' : 't', _("[tag update]"),
r ? _("unable to update local ref") : NULL,
remote, pretty_ref);
return r;
}
@ -527,11 +649,9 @@ static int update_local_ref(struct ref *ref,
(recurse_submodules != RECURSE_SUBMODULES_ON))
check_for_new_submodule_commits(ref->new_oid.hash);
r = s_update_ref(msg, ref, 0);
strbuf_addf(display, "%c %-*s %-*s -> %s%s",
r ? '!' : '*',
TRANSPORT_SUMMARY(what),
REFCOL_WIDTH, remote, pretty_ref,
r ? _(" (unable to update local ref)") : "");
format_display(display, r ? '!' : '*', what,
r ? _("unable to update local ref") : NULL,
remote, pretty_ref);
return r;
}
@ -545,11 +665,9 @@ static int update_local_ref(struct ref *ref,
(recurse_submodules != RECURSE_SUBMODULES_ON))
check_for_new_submodule_commits(ref->new_oid.hash);
r = s_update_ref("fast-forward", ref, 1);
strbuf_addf(display, "%c %-*s %-*s -> %s%s",
r ? '!' : ' ',
TRANSPORT_SUMMARY_WIDTH, quickref.buf,
REFCOL_WIDTH, remote, pretty_ref,
r ? _(" (unable to update local ref)") : "");
format_display(display, r ? '!' : ' ', quickref.buf,
r ? _("unable to update local ref") : NULL,
remote, pretty_ref);
strbuf_release(&quickref);
return r;
} else if (force || ref->force) {
@ -562,18 +680,14 @@ static int update_local_ref(struct ref *ref,
(recurse_submodules != RECURSE_SUBMODULES_ON))
check_for_new_submodule_commits(ref->new_oid.hash);
r = s_update_ref("forced-update", ref, 1);
strbuf_addf(display, "%c %-*s %-*s -> %s (%s)",
r ? '!' : '+',
TRANSPORT_SUMMARY_WIDTH, quickref.buf,
REFCOL_WIDTH, remote, pretty_ref,
r ? _("unable to update local ref") : _("forced update"));
format_display(display, r ? '!' : '+', quickref.buf,
r ? _("unable to update local ref") : _("forced update"),
remote, pretty_ref);
strbuf_release(&quickref);
return r;
} else {
strbuf_addf(display, "! %-*s %-*s -> %s %s",
TRANSPORT_SUMMARY(_("[rejected]")),
REFCOL_WIDTH, remote, pretty_ref,
_("(non-fast-forward)"));
format_display(display, '!', _("[rejected]"), _("non-fast-forward"),
remote, pretty_ref);
return 1;
}
}
@ -620,6 +734,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
goto abort;
}
prepare_format_display(ref_map);
/*
* We do a pass for each fetch_head_status type in their enum order, so
* merged entries are written before not-for-merge. That lets readers
@ -714,11 +830,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
rc |= update_local_ref(ref, what, rm, &note);
free(ref);
} else
strbuf_addf(&note, "* %-*s %-*s -> FETCH_HEAD",
TRANSPORT_SUMMARY_WIDTH,
*kind ? kind : "branch",
REFCOL_WIDTH,
*what ? what : "HEAD");
format_display(&note, '*',
*kind ? kind : "branch", NULL,
*what ? what : "HEAD",
"FETCH_HEAD");
if (note.len) {
if (verbosity >= 0 && !shown_url) {
fprintf(stderr, _("From %.*s\n"),
@ -812,13 +927,15 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map,
if (verbosity >= 0) {
for (ref = stale_refs; ref; ref = ref->next) {
struct strbuf sb = STRBUF_INIT;
if (!shown_url) {
fprintf(stderr, _("From %.*s\n"), url_len, url);
shown_url = 1;
}
fprintf(stderr, " x %-*s %-*s -> %s\n",
TRANSPORT_SUMMARY(_("[deleted]")),
REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name));
format_display(&sb, '-', _("[deleted]"), NULL,
_("(none)"), prettify_refname(ref->name));
fprintf(stderr, " %s\n",sb.buf);
strbuf_release(&sb);
warn_dangling_symref(stderr, dangling_msg, ref->name);
}
}

View File

@ -688,4 +688,34 @@ test_expect_success 'fetching with auto-gc does not lock up' '
)
'
test_expect_success 'fetch aligned output' '
git clone . full-output &&
test_commit looooooooooooong-tag &&
(
cd full-output &&
git -c fetch.output=full fetch origin 2>&1 | \
grep -e "->" | cut -c 22- >../actual
) &&
cat >expect <<-\EOF &&
master -> origin/master
looooooooooooong-tag -> looooooooooooong-tag
EOF
test_cmp expect actual
'
test_expect_success 'fetch compact output' '
git clone . compact &&
test_commit extraaa &&
(
cd compact &&
git -c fetch.output=compact fetch origin 2>&1 | \
grep -e "->" | cut -c 22- >../actual
) &&
cat >expect <<-\EOF &&
master -> origin/*
extraaa -> *
EOF
test_cmp expect actual
'
test_done