Merge branch 'jk/at-push-sha1'

Introduce <branch>@{push} short-hand to denote the remote-tracking
branch that tracks the branch at the remote the <branch> would be
pushed to.

* jk/at-push-sha1:
  for-each-ref: accept "%(push)" format
  for-each-ref: use skip_prefix instead of starts_with
  sha1_name: implement @{push} shorthand
  sha1_name: refactor interpret_upstream_mark
  sha1_name: refactor upstream_mark
  remote.c: add branch_get_push
  remote.c: return upstream name from stat_tracking_info
  remote.c: untangle error logic in branch_get_upstream
  remote.c: report specific errors from branch_get_upstream
  remote.c: introduce branch_get_upstream helper
  remote.c: hoist read_config into remote_get_1
  remote.c: provide per-branch pushremote name
  remote.c: hoist branch.*.remote lookup out of remote_get_1
  remote.c: drop "remote" pointer from "struct branch"
  remote.c: refactor setup of branch->merge list
  remote.c: drop default_remote_name variable
This commit is contained in:
Junio C Hamano 2015-06-05 12:17:36 -07:00
commit c4a8354bc1
14 changed files with 424 additions and 149 deletions

View File

@ -97,6 +97,12 @@ upstream::
or "=" (in sync). Has no effect if the ref does not have
tracking information associated with it.
push::
The name of a local ref which represents the `@{push}` location
for the displayed ref. Respects `:short`, `:track`, and
`:trackshort` options as `upstream` does. Produces an empty
string if no `@{push}` ref is configured.
HEAD::
'*' if HEAD matches current ref (the checked out branch), ' '
otherwise.

View File

@ -98,6 +98,31 @@ some output processing may assume ref names in UTF-8.
`branch.<name>.merge`). A missing branchname defaults to the
current one.
'<branchname>@\{push\}', e.g. 'master@\{push\}', '@\{push\}'::
The suffix '@\{push}' reports the branch "where we would push to" if
`git push` were run while `branchname` was checked out (or the current
'HEAD' if no branchname is specified). Since our push destination is
in a remote repository, of course, we report the local tracking branch
that corresponds to that branch (i.e., something in 'refs/remotes/').
+
Here's an example to make it more clear:
+
------------------------------
$ git config push.default current
$ git config remote.pushdefault myfork
$ git checkout -b mybranch origin/master
$ git rev-parse --symbolic-full-name @{upstream}
refs/remotes/origin/master
$ git rev-parse --symbolic-full-name @{push}
refs/remotes/myfork/mybranch
------------------------------
+
Note in the example that we set up a triangular workflow, where we pull
from one location and push to another. In a non-triangular workflow,
'@\{push}' is the same as '@\{upstream}', and there is no need for it.
'<rev>{caret}', e.g. 'HEAD{caret}, v1.5.1{caret}0'::
A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e.

View File

@ -97,10 +97,6 @@ It contains:
The name of the remote listed in the configuration.
`remote`::
The struct remote for that remote.
`merge_name`::
An array of the "merge" lines in the configuration.

View File

@ -123,14 +123,12 @@ static int branch_merged(int kind, const char *name,
if (kind == REF_LOCAL_BRANCH) {
struct branch *branch = branch_get(name);
const char *upstream = branch_get_upstream(branch, NULL);
unsigned char sha1[20];
if (branch &&
branch->merge &&
branch->merge[0] &&
branch->merge[0]->dst &&
if (upstream &&
(reference_name = reference_name_to_free =
resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING,
resolve_refdup(upstream, RESOLVE_REF_READING,
sha1, NULL)) != NULL)
reference_rev = lookup_commit_reference(sha1);
}
@ -427,25 +425,19 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
int ours, theirs;
char *ref = NULL;
struct branch *branch = branch_get(branch_name);
const char *upstream;
struct strbuf fancy = STRBUF_INIT;
int upstream_is_gone = 0;
int added_decoration = 1;
switch (stat_tracking_info(branch, &ours, &theirs)) {
case 0:
/* no base */
return;
case -1:
/* with "gone" base */
if (stat_tracking_info(branch, &ours, &theirs, &upstream) < 0) {
if (!upstream)
return;
upstream_is_gone = 1;
break;
default:
/* with base */
break;
}
if (show_upstream_ref) {
ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
ref = shorten_unambiguous_ref(upstream, 0);
if (want_color(branch_use_color))
strbuf_addf(&fancy, "%s%s%s",
branch_get_color(BRANCH_COLOR_UPSTREAM),

View File

@ -74,6 +74,7 @@ static struct {
{ "contents:body" },
{ "contents:signature" },
{ "upstream" },
{ "push" },
{ "symref" },
{ "flag" },
{ "HEAD" },
@ -659,15 +660,26 @@ static void populate_value(struct refinfo *ref)
else if (starts_with(name, "symref"))
refname = ref->symref ? ref->symref : "";
else if (starts_with(name, "upstream")) {
const char *branch_name;
/* only local branches may have an upstream */
if (!starts_with(ref->refname, "refs/heads/"))
if (!skip_prefix(ref->refname, "refs/heads/",
&branch_name))
continue;
branch = branch_get(ref->refname + 11);
branch = branch_get(branch_name);
if (!branch || !branch->merge || !branch->merge[0] ||
!branch->merge[0]->dst)
refname = branch_get_upstream(branch, NULL);
if (!refname)
continue;
} else if (starts_with(name, "push")) {
const char *branch_name;
if (!skip_prefix(ref->refname, "refs/heads/",
&branch_name))
continue;
branch = branch_get(branch_name);
refname = branch_get_push(branch, NULL);
if (!refname)
continue;
refname = branch->merge[0]->dst;
} else if (starts_with(name, "color:")) {
char color[COLOR_MAXLEN] = "";
@ -713,11 +725,12 @@ static void populate_value(struct refinfo *ref)
refname = shorten_unambiguous_ref(refname,
warn_ambiguous_refs);
else if (!strcmp(formatp, "track") &&
starts_with(name, "upstream")) {
(starts_with(name, "upstream") ||
starts_with(name, "push"))) {
char buf[40];
if (stat_tracking_info(branch, &num_ours,
&num_theirs) != 1)
&num_theirs, NULL))
continue;
if (!num_ours && !num_theirs)
@ -735,11 +748,12 @@ static void populate_value(struct refinfo *ref)
}
continue;
} else if (!strcmp(formatp, "trackshort") &&
starts_with(name, "upstream")) {
(starts_with(name, "upstream") ||
starts_with(name, "push"))) {
assert(branch);
if (stat_tracking_info(branch, &num_ours,
&num_theirs) != 1)
&num_theirs, NULL))
continue;
if (!num_ours && !num_theirs)

View File

@ -1632,16 +1632,13 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
break;
default:
current_branch = branch_get(NULL);
if (!current_branch || !current_branch->merge
|| !current_branch->merge[0]
|| !current_branch->merge[0]->dst) {
upstream = branch_get_upstream(current_branch, NULL);
if (!upstream) {
fprintf(stderr, _("Could not find a tracked"
" remote branch, please"
" specify <upstream> manually.\n"));
usage_with_options(cherry_usage, options);
}
upstream = current_branch->merge[0]->dst;
}
init_revisions(&revs, prefix);

View File

@ -933,7 +933,7 @@ static int setup_with_upstream(const char ***argv)
if (!branch)
die(_("No current branch."));
if (!branch->remote)
if (!branch->remote_name)
die(_("No remote for the current branch."));
if (!branch->merge_nr)
die(_("No default upstream defined for the current branch."));

262
remote.c
View File

@ -49,10 +49,7 @@ static int branches_alloc;
static int branches_nr;
static struct branch *current_branch;
static const char *default_remote_name;
static const char *branch_pushremote_name;
static const char *pushremote_name;
static int explicit_default_remote_name;
static struct rewrites rewrites;
static struct rewrites rewrites_push;
@ -367,16 +364,9 @@ static int handle_config(const char *key, const char *value, void *cb)
return 0;
branch = make_branch(name, subkey - name);
if (!strcmp(subkey, ".remote")) {
if (git_config_string(&branch->remote_name, key, value))
return -1;
if (branch == current_branch) {
default_remote_name = branch->remote_name;
explicit_default_remote_name = 1;
}
return git_config_string(&branch->remote_name, key, value);
} else if (!strcmp(subkey, ".pushremote")) {
if (branch == current_branch)
if (git_config_string(&branch_pushremote_name, key, value))
return -1;
return git_config_string(&branch->pushremote_name, key, value);
} else if (!strcmp(subkey, ".merge")) {
if (!value)
return config_error_nonbool(key);
@ -501,12 +491,15 @@ static void alias_all_urls(void)
static void read_config(void)
{
static int loaded;
unsigned char sha1[20];
const char *head_ref;
int flag;
if (default_remote_name) /* did this already */
if (loaded)
return;
default_remote_name = "origin";
loaded = 1;
current_branch = NULL;
head_ref = resolve_ref_unsafe("HEAD", 0, sha1, &flag);
if (head_ref && (flag & REF_ISSYMREF) &&
@ -514,10 +507,6 @@ static void read_config(void)
current_branch = make_branch(head_ref, 0);
}
git_config(handle_config, NULL);
if (branch_pushremote_name) {
free((char *)pushremote_name);
pushremote_name = branch_pushremote_name;
}
alias_all_urls();
}
@ -696,22 +685,45 @@ static int valid_remote_nick(const char *name)
return !strchr(name, '/'); /* no slash */
}
static struct remote *remote_get_1(const char *name, const char *pushremote_name)
const char *remote_for_branch(struct branch *branch, int *explicit)
{
if (branch && branch->remote_name) {
if (explicit)
*explicit = 1;
return branch->remote_name;
}
if (explicit)
*explicit = 0;
return "origin";
}
const char *pushremote_for_branch(struct branch *branch, int *explicit)
{
if (branch && branch->pushremote_name) {
if (explicit)
*explicit = 1;
return branch->pushremote_name;
}
if (pushremote_name) {
if (explicit)
*explicit = 1;
return pushremote_name;
}
return remote_for_branch(branch, explicit);
}
static struct remote *remote_get_1(const char *name,
const char *(*get_default)(struct branch *, int *))
{
struct remote *ret;
int name_given = 0;
read_config();
if (name)
name_given = 1;
else {
if (pushremote_name) {
name = pushremote_name;
name_given = 1;
} else {
name = default_remote_name;
name_given = explicit_default_remote_name;
}
}
else
name = get_default(current_branch, &name_given);
ret = make_remote(name, 0);
if (valid_remote_nick(name)) {
@ -731,14 +743,12 @@ static struct remote *remote_get_1(const char *name, const char *pushremote_name
struct remote *remote_get(const char *name)
{
read_config();
return remote_get_1(name, NULL);
return remote_get_1(name, remote_for_branch);
}
struct remote *pushremote_get(const char *name)
{
read_config();
return remote_get_1(name, pushremote_name);
return remote_get_1(name, pushremote_for_branch);
}
int remote_is_configured(const char *name)
@ -1633,15 +1643,31 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
static void set_merge(struct branch *ret)
{
struct remote *remote;
char *ref;
unsigned char sha1[20];
int i;
if (!ret)
return; /* no branch */
if (ret->merge)
return; /* already run */
if (!ret->remote_name || !ret->merge_nr) {
/*
* no merge config; let's make sure we don't confuse callers
* with a non-zero merge_nr but a NULL merge
*/
ret->merge_nr = 0;
return;
}
remote = remote_get(ret->remote_name);
ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge));
for (i = 0; i < ret->merge_nr; i++) {
ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
ret->merge[i]->src = xstrdup(ret->merge_name[i]);
if (!remote_find_tracking(ret->remote, ret->merge[i]) ||
if (!remote_find_tracking(remote, ret->merge[i]) ||
strcmp(ret->remote_name, "."))
continue;
if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]),
@ -1661,11 +1687,7 @@ struct branch *branch_get(const char *name)
ret = current_branch;
else
ret = make_branch(name, 0);
if (ret && ret->remote_name) {
ret->remote = remote_get(ret->remote_name);
if (ret->merge_nr)
set_merge(ret);
}
set_merge(ret);
return ret;
}
@ -1683,6 +1705,130 @@ int branch_merge_matches(struct branch *branch,
return refname_match(branch->merge[i]->src, refname);
}
__attribute((format (printf,2,3)))
static const char *error_buf(struct strbuf *err, const char *fmt, ...)
{
if (err) {
va_list ap;
va_start(ap, fmt);
strbuf_vaddf(err, fmt, ap);
va_end(ap);
}
return NULL;
}
const char *branch_get_upstream(struct branch *branch, struct strbuf *err)
{
if (!branch)
return error_buf(err, _("HEAD does not point to a branch"));
if (!branch->merge || !branch->merge[0]) {
/*
* no merge config; is it because the user didn't define any,
* or because it is not a real branch, and get_branch
* auto-vivified it?
*/
if (!ref_exists(branch->refname))
return error_buf(err, _("no such branch: '%s'"),
branch->name);
return error_buf(err,
_("no upstream configured for branch '%s'"),
branch->name);
}
if (!branch->merge[0]->dst)
return error_buf(err,
_("upstream branch '%s' not stored as a remote-tracking branch"),
branch->merge[0]->src);
return branch->merge[0]->dst;
}
static const char *tracking_for_push_dest(struct remote *remote,
const char *refname,
struct strbuf *err)
{
char *ret;
ret = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);
if (!ret)
return error_buf(err,
_("push destination '%s' on remote '%s' has no local tracking branch"),
refname, remote->name);
return ret;
}
static const char *branch_get_push_1(struct branch *branch, struct strbuf *err)
{
struct remote *remote;
if (!branch)
return error_buf(err, _("HEAD does not point to a branch"));
remote = remote_get(pushremote_for_branch(branch, NULL));
if (!remote)
return error_buf(err,
_("branch '%s' has no remote for pushing"),
branch->name);
if (remote->push_refspec_nr) {
char *dst;
const char *ret;
dst = apply_refspecs(remote->push, remote->push_refspec_nr,
branch->refname);
if (!dst)
return error_buf(err,
_("push refspecs for '%s' do not include '%s'"),
remote->name, branch->name);
ret = tracking_for_push_dest(remote, dst, err);
free(dst);
return ret;
}
if (remote->mirror)
return tracking_for_push_dest(remote, branch->refname, err);
switch (push_default) {
case PUSH_DEFAULT_NOTHING:
return error_buf(err, _("push has no destination (push.default is 'nothing')"));
case PUSH_DEFAULT_MATCHING:
case PUSH_DEFAULT_CURRENT:
return tracking_for_push_dest(remote, branch->refname, err);
case PUSH_DEFAULT_UPSTREAM:
return branch_get_upstream(branch, err);
case PUSH_DEFAULT_UNSPECIFIED:
case PUSH_DEFAULT_SIMPLE:
{
const char *up, *cur;
up = branch_get_upstream(branch, err);
if (!up)
return NULL;
cur = tracking_for_push_dest(remote, branch->refname, err);
if (!cur)
return NULL;
if (strcmp(cur, up))
return error_buf(err,
_("cannot resolve 'simple' push to a single destination"));
return cur;
}
}
die("BUG: unhandled push situation");
}
const char *branch_get_push(struct branch *branch, struct strbuf *err)
{
if (!branch->push_tracking_ref)
branch->push_tracking_ref = branch_get_push_1(branch, err);
return branch->push_tracking_ref;
}
static int ignore_symref_update(const char *refname)
{
unsigned char sha1[20];
@ -1877,12 +2023,15 @@ int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1)
/*
* Compare a branch with its upstream, and save their differences (number
* of commits) in *num_ours and *num_theirs.
* of commits) in *num_ours and *num_theirs. The name of the upstream branch
* (or NULL if no upstream is defined) is returned via *upstream_name, if it
* is not itself NULL.
*
* Return 0 if branch has no upstream (no base), -1 if upstream is missing
* (with "gone" base), otherwise 1 (with base).
* Returns -1 if num_ours and num_theirs could not be filled in (e.g., no
* upstream defined, or ref does not exist), 0 otherwise.
*/
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
const char **upstream_name)
{
unsigned char sha1[20];
struct commit *ours, *theirs;
@ -1892,12 +2041,13 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
int rev_argc;
/* Cannot stat unless we are marked to build on top of somebody else. */
if (!branch ||
!branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
return 0;
base = branch_get_upstream(branch, NULL);
if (upstream_name)
*upstream_name = base;
if (!base)
return -1;
/* Cannot stat if what we used to build on no longer exists */
base = branch->merge[0]->dst;
if (read_ref(base, sha1))
return -1;
theirs = lookup_commit_reference(sha1);
@ -1913,7 +2063,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
/* are we the same? */
if (theirs == ours) {
*num_theirs = *num_ours = 0;
return 1;
return 0;
}
/* Run "rev-list --left-right ours...theirs" internally... */
@ -1949,7 +2099,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
/* clear object flags smudged by the above traversal */
clear_commit_marks(ours, ALL_REV_FLAGS);
clear_commit_marks(theirs, ALL_REV_FLAGS);
return 1;
return 0;
}
/*
@ -1958,23 +2108,17 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
int format_tracking_info(struct branch *branch, struct strbuf *sb)
{
int ours, theirs;
const char *full_base;
char *base;
int upstream_is_gone = 0;
switch (stat_tracking_info(branch, &ours, &theirs)) {
case 0:
/* no base */
return 0;
case -1:
/* with "gone" base */
if (stat_tracking_info(branch, &ours, &theirs, &full_base) < 0) {
if (!full_base)
return 0;
upstream_is_gone = 1;
break;
default:
/* with base */
break;
}
base = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
base = shorten_unambiguous_ref(full_base, 0);
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),

View File

@ -203,19 +203,42 @@ struct branch {
const char *refname;
const char *remote_name;
struct remote *remote;
const char *pushremote_name;
const char **merge_name;
struct refspec **merge;
int merge_nr;
int merge_alloc;
const char *push_tracking_ref;
};
struct branch *branch_get(const char *name);
const char *remote_for_branch(struct branch *branch, int *explicit);
const char *pushremote_for_branch(struct branch *branch, int *explicit);
int branch_has_merge_config(struct branch *branch);
int branch_merge_matches(struct branch *, int n, const char *);
/**
* Return the fully-qualified refname of the tracking branch for `branch`.
* I.e., what "branch@{upstream}" would give you. Returns NULL if no
* upstream is defined.
*
* If `err` is not NULL and no upstream is defined, a more specific error
* message is recorded there (if the function does not return NULL, then
* `err` is not touched).
*/
const char *branch_get_upstream(struct branch *branch, struct strbuf *err);
/**
* Return the tracking branch that corresponds to the ref we would push to
* given a bare `git push` while `branch` is checked out.
*
* The return value and `err` conventions match those of `branch_get_upstream`.
*/
const char *branch_get_push(struct branch *branch, struct strbuf *err);
/* Flags to match_refs. */
enum match_refs_flags {
MATCH_REFS_NONE = 0,
@ -226,7 +249,8 @@ enum match_refs_flags {
};
/* Reporting of tracking info */
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs);
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
const char **upstream_name);
int format_tracking_info(struct branch *branch, struct strbuf *sb);
struct ref *get_local_heads(void);

View File

@ -416,12 +416,12 @@ static int ambiguous_path(const char *path, int len)
return slash;
}
static inline int upstream_mark(const char *string, int len)
static inline int at_mark(const char *string, int len,
const char **suffix, int nr)
{
const char *suffix[] = { "@{upstream}", "@{u}" };
int i;
for (i = 0; i < ARRAY_SIZE(suffix); i++) {
for (i = 0; i < nr; i++) {
int suffix_len = strlen(suffix[i]);
if (suffix_len <= len
&& !memcmp(string, suffix[i], suffix_len))
@ -430,6 +430,18 @@ static inline int upstream_mark(const char *string, int len)
return 0;
}
static inline int upstream_mark(const char *string, int len)
{
const char *suffix[] = { "@{upstream}", "@{u}" };
return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
}
static inline int push_mark(const char *string, int len)
{
const char *suffix[] = { "@{push}" };
return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
}
static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
static int interpret_nth_prior_checkout(const char *name, int namelen, struct strbuf *buf);
@ -477,7 +489,8 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1,
nth_prior = 1;
continue;
}
if (!upstream_mark(str + at, len - at)) {
if (!upstream_mark(str + at, len - at) &&
!push_mark(str + at, len - at)) {
reflog_len = (len-1) - (at+2);
len = at;
}
@ -1056,46 +1069,36 @@ static void set_shortened_ref(struct strbuf *buf, const char *ref)
free(s);
}
static const char *get_upstream_branch(const char *branch_buf, int len)
{
char *branch = xstrndup(branch_buf, len);
struct branch *upstream = branch_get(*branch ? branch : NULL);
/*
* Upstream can be NULL only if branch refers to HEAD and HEAD
* points to something different than a branch.
*/
if (!upstream)
die(_("HEAD does not point to a branch"));
if (!upstream->merge || !upstream->merge[0]->dst) {
if (!ref_exists(upstream->refname))
die(_("No such branch: '%s'"), branch);
if (!upstream->merge) {
die(_("No upstream configured for branch '%s'"),
upstream->name);
}
die(
_("Upstream branch '%s' not stored as a remote-tracking branch"),
upstream->merge[0]->src);
}
free(branch);
return upstream->merge[0]->dst;
}
static int interpret_upstream_mark(const char *name, int namelen,
int at, struct strbuf *buf)
static int interpret_branch_mark(const char *name, int namelen,
int at, struct strbuf *buf,
int (*get_mark)(const char *, int),
const char *(*get_data)(struct branch *,
struct strbuf *))
{
int len;
struct branch *branch;
struct strbuf err = STRBUF_INIT;
const char *value;
len = upstream_mark(name + at, namelen - at);
len = get_mark(name + at, namelen - at);
if (!len)
return -1;
if (memchr(name, ':', at))
return -1;
set_shortened_ref(buf, get_upstream_branch(name, at));
if (at) {
char *name_str = xmemdupz(name, at);
branch = branch_get(name_str);
free(name_str);
} else
branch = branch_get(NULL);
value = get_data(branch, &err);
if (!value)
die("%s", err.buf);
set_shortened_ref(buf, value);
return len + at;
}
@ -1146,7 +1149,13 @@ int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
if (len > 0)
return reinterpret(name, namelen, len, buf);
len = interpret_upstream_mark(name, namelen, at - name, buf);
len = interpret_branch_mark(name, namelen, at - name, buf,
upstream_mark, branch_get_upstream);
if (len > 0)
return len;
len = interpret_branch_mark(name, namelen, at - name, buf,
push_mark, branch_get_push);
if (len > 0)
return len;
}

View File

@ -150,7 +150,7 @@ test_expect_success 'branch@{u} works when tracking a local branch' '
test_expect_success 'branch@{u} error message when no upstream' '
cat >expect <<-EOF &&
fatal: No upstream configured for branch ${sq}non-tracking${sq}
fatal: no upstream configured for branch ${sq}non-tracking${sq}
EOF
error_message non-tracking@{u} 2>actual &&
test_i18ncmp expect actual
@ -158,7 +158,7 @@ test_expect_success 'branch@{u} error message when no upstream' '
test_expect_success '@{u} error message when no upstream' '
cat >expect <<-EOF &&
fatal: No upstream configured for branch ${sq}master${sq}
fatal: no upstream configured for branch ${sq}master${sq}
EOF
test_must_fail git rev-parse --verify @{u} 2>actual &&
test_i18ncmp expect actual
@ -166,7 +166,7 @@ test_expect_success '@{u} error message when no upstream' '
test_expect_success 'branch@{u} error message with misspelt branch' '
cat >expect <<-EOF &&
fatal: No such branch: ${sq}no-such-branch${sq}
fatal: no such branch: ${sq}no-such-branch${sq}
EOF
error_message no-such-branch@{u} 2>actual &&
test_i18ncmp expect actual
@ -183,7 +183,7 @@ test_expect_success '@{u} error message when not on a branch' '
test_expect_success 'branch@{u} error message if upstream branch not fetched' '
cat >expect <<-EOF &&
fatal: Upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
fatal: upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
EOF
error_message bad-upstream@{u} 2>actual &&
test_i18ncmp expect actual

63
t/t1514-rev-parse-push.sh Executable file
View File

@ -0,0 +1,63 @@
#!/bin/sh
test_description='test <branch>@{push} syntax'
. ./test-lib.sh
resolve () {
echo "$2" >expect &&
git rev-parse --symbolic-full-name "$1" >actual &&
test_cmp expect actual
}
test_expect_success 'setup' '
git init --bare parent.git &&
git init --bare other.git &&
git remote add origin parent.git &&
git remote add other other.git &&
test_commit base &&
git push origin HEAD &&
git branch --set-upstream-to=origin/master master &&
git branch --track topic origin/master &&
git push origin topic &&
git push other topic
'
test_expect_success '@{push} with default=nothing' '
test_config push.default nothing &&
test_must_fail git rev-parse master@{push}
'
test_expect_success '@{push} with default=simple' '
test_config push.default simple &&
resolve master@{push} refs/remotes/origin/master
'
test_expect_success 'triangular @{push} fails with default=simple' '
test_config push.default simple &&
test_must_fail git rev-parse topic@{push}
'
test_expect_success '@{push} with default=current' '
test_config push.default current &&
resolve topic@{push} refs/remotes/origin/topic
'
test_expect_success '@{push} with default=matching' '
test_config push.default matching &&
resolve topic@{push} refs/remotes/origin/topic
'
test_expect_success '@{push} with pushremote defined' '
test_config push.default current &&
test_config branch.topic.pushremote other &&
resolve topic@{push} refs/remotes/other/topic
'
test_expect_success '@{push} with push refspecs' '
test_config push.default nothing &&
test_config remote.origin.push refs/heads/*:refs/heads/magic/* &&
git push &&
resolve topic@{push} refs/remotes/origin/magic/topic
'
test_done

View File

@ -28,7 +28,10 @@ test_expect_success setup '
git update-ref refs/remotes/origin/master master &&
git remote add origin nowhere &&
git config branch.master.remote origin &&
git config branch.master.merge refs/heads/master
git config branch.master.merge refs/heads/master &&
git remote add myfork elsewhere &&
git config remote.pushdefault myfork &&
git config push.default current
'
test_atom() {
@ -47,6 +50,7 @@ test_atom() {
test_atom head refname refs/heads/master
test_atom head upstream refs/remotes/origin/master
test_atom head push refs/remotes/myfork/master
test_atom head objecttype commit
test_atom head objectsize 171
test_atom head objectname $(git rev-parse refs/heads/master)
@ -83,6 +87,7 @@ test_atom head HEAD '*'
test_atom tag refname refs/tags/testtag
test_atom tag upstream ''
test_atom tag push ''
test_atom tag objecttype tag
test_atom tag objectsize 154
test_atom tag objectname $(git rev-parse refs/tags/testtag)
@ -347,6 +352,12 @@ test_expect_success 'Check that :track[short] works when upstream is invalid' '
test_cmp expected actual
'
test_expect_success '%(push) supports tracking specifiers, too' '
echo "[ahead 1]" >expected &&
git for-each-ref --format="%(push:track)" refs/heads >actual &&
test_cmp expected actual
'
cat >expected <<EOF
$(git rev-parse --short HEAD)
EOF

View File

@ -1534,21 +1534,15 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
color_fprintf(s->fp, branch_color_local, "%s", branch_name);
switch (stat_tracking_info(branch, &num_ours, &num_theirs)) {
case 0:
/* no base */
fputc(s->null_termination ? '\0' : '\n', s->fp);
return;
case -1:
/* with "gone" base */
if (stat_tracking_info(branch, &num_ours, &num_theirs, &base) < 0) {
if (!base) {
fputc(s->null_termination ? '\0' : '\n', s->fp);
return;
}
upstream_is_gone = 1;
break;
default:
/* with base */
break;
}
base = branch->merge[0]->dst;
base = shorten_unambiguous_ref(base, 0);
color_fprintf(s->fp, header_color, "...");
color_fprintf(s->fp, branch_color_remote, "%s", base);