提交 c4a8354b 编写于 作者: J Junio C Hamano

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
...@@ -97,6 +97,12 @@ upstream:: ...@@ -97,6 +97,12 @@ upstream::
or "=" (in sync). Has no effect if the ref does not have or "=" (in sync). Has no effect if the ref does not have
tracking information associated with it. 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:: HEAD::
'*' if HEAD matches current ref (the checked out branch), ' ' '*' if HEAD matches current ref (the checked out branch), ' '
otherwise. otherwise.
......
...@@ -98,6 +98,31 @@ some output processing may assume ref names in UTF-8. ...@@ -98,6 +98,31 @@ some output processing may assume ref names in UTF-8.
`branch.<name>.merge`). A missing branchname defaults to the `branch.<name>.merge`). A missing branchname defaults to the
current one. 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':: '<rev>{caret}', e.g. 'HEAD{caret}, v1.5.1{caret}0'::
A suffix '{caret}' to a revision parameter means the first parent of A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e. that commit object. '{caret}<n>' means the <n>th parent (i.e.
......
...@@ -97,10 +97,6 @@ It contains: ...@@ -97,10 +97,6 @@ It contains:
The name of the remote listed in the configuration. The name of the remote listed in the configuration.
`remote`::
The struct remote for that remote.
`merge_name`:: `merge_name`::
An array of the "merge" lines in the configuration. An array of the "merge" lines in the configuration.
......
...@@ -123,14 +123,12 @@ static int branch_merged(int kind, const char *name, ...@@ -123,14 +123,12 @@ static int branch_merged(int kind, const char *name,
if (kind == REF_LOCAL_BRANCH) { if (kind == REF_LOCAL_BRANCH) {
struct branch *branch = branch_get(name); struct branch *branch = branch_get(name);
const char *upstream = branch_get_upstream(branch, NULL);
unsigned char sha1[20]; unsigned char sha1[20];
if (branch && if (upstream &&
branch->merge &&
branch->merge[0] &&
branch->merge[0]->dst &&
(reference_name = reference_name_to_free = (reference_name = reference_name_to_free =
resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING, resolve_refdup(upstream, RESOLVE_REF_READING,
sha1, NULL)) != NULL) sha1, NULL)) != NULL)
reference_rev = lookup_commit_reference(sha1); reference_rev = lookup_commit_reference(sha1);
} }
...@@ -427,25 +425,19 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, ...@@ -427,25 +425,19 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
int ours, theirs; int ours, theirs;
char *ref = NULL; char *ref = NULL;
struct branch *branch = branch_get(branch_name); struct branch *branch = branch_get(branch_name);
const char *upstream;
struct strbuf fancy = STRBUF_INIT; struct strbuf fancy = STRBUF_INIT;
int upstream_is_gone = 0; int upstream_is_gone = 0;
int added_decoration = 1; int added_decoration = 1;
switch (stat_tracking_info(branch, &ours, &theirs)) { if (stat_tracking_info(branch, &ours, &theirs, &upstream) < 0) {
case 0: if (!upstream)
/* no base */ return;
return;
case -1:
/* with "gone" base */
upstream_is_gone = 1; upstream_is_gone = 1;
break;
default:
/* with base */
break;
} }
if (show_upstream_ref) { 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)) if (want_color(branch_use_color))
strbuf_addf(&fancy, "%s%s%s", strbuf_addf(&fancy, "%s%s%s",
branch_get_color(BRANCH_COLOR_UPSTREAM), branch_get_color(BRANCH_COLOR_UPSTREAM),
......
...@@ -74,6 +74,7 @@ static struct { ...@@ -74,6 +74,7 @@ static struct {
{ "contents:body" }, { "contents:body" },
{ "contents:signature" }, { "contents:signature" },
{ "upstream" }, { "upstream" },
{ "push" },
{ "symref" }, { "symref" },
{ "flag" }, { "flag" },
{ "HEAD" }, { "HEAD" },
...@@ -659,15 +660,26 @@ static void populate_value(struct refinfo *ref) ...@@ -659,15 +660,26 @@ static void populate_value(struct refinfo *ref)
else if (starts_with(name, "symref")) else if (starts_with(name, "symref"))
refname = ref->symref ? ref->symref : ""; refname = ref->symref ? ref->symref : "";
else if (starts_with(name, "upstream")) { else if (starts_with(name, "upstream")) {
const char *branch_name;
/* only local branches may have an upstream */ /* only local branches may have an upstream */
if (!starts_with(ref->refname, "refs/heads/")) if (!skip_prefix(ref->refname, "refs/heads/",
&branch_name))
continue; continue;
branch = branch_get(ref->refname + 11); branch = branch_get(branch_name);
if (!branch || !branch->merge || !branch->merge[0] || refname = branch_get_upstream(branch, NULL);
!branch->merge[0]->dst) 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; continue;
refname = branch->merge[0]->dst;
} else if (starts_with(name, "color:")) { } else if (starts_with(name, "color:")) {
char color[COLOR_MAXLEN] = ""; char color[COLOR_MAXLEN] = "";
...@@ -713,11 +725,12 @@ static void populate_value(struct refinfo *ref) ...@@ -713,11 +725,12 @@ static void populate_value(struct refinfo *ref)
refname = shorten_unambiguous_ref(refname, refname = shorten_unambiguous_ref(refname,
warn_ambiguous_refs); warn_ambiguous_refs);
else if (!strcmp(formatp, "track") && else if (!strcmp(formatp, "track") &&
starts_with(name, "upstream")) { (starts_with(name, "upstream") ||
starts_with(name, "push"))) {
char buf[40]; char buf[40];
if (stat_tracking_info(branch, &num_ours, if (stat_tracking_info(branch, &num_ours,
&num_theirs) != 1) &num_theirs, NULL))
continue; continue;
if (!num_ours && !num_theirs) if (!num_ours && !num_theirs)
...@@ -735,11 +748,12 @@ static void populate_value(struct refinfo *ref) ...@@ -735,11 +748,12 @@ static void populate_value(struct refinfo *ref)
} }
continue; continue;
} else if (!strcmp(formatp, "trackshort") && } else if (!strcmp(formatp, "trackshort") &&
starts_with(name, "upstream")) { (starts_with(name, "upstream") ||
starts_with(name, "push"))) {
assert(branch); assert(branch);
if (stat_tracking_info(branch, &num_ours, if (stat_tracking_info(branch, &num_ours,
&num_theirs) != 1) &num_theirs, NULL))
continue; continue;
if (!num_ours && !num_theirs) if (!num_ours && !num_theirs)
......
...@@ -1632,16 +1632,13 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) ...@@ -1632,16 +1632,13 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
break; break;
default: default:
current_branch = branch_get(NULL); current_branch = branch_get(NULL);
if (!current_branch || !current_branch->merge upstream = branch_get_upstream(current_branch, NULL);
|| !current_branch->merge[0] if (!upstream) {
|| !current_branch->merge[0]->dst) {
fprintf(stderr, _("Could not find a tracked" fprintf(stderr, _("Could not find a tracked"
" remote branch, please" " remote branch, please"
" specify <upstream> manually.\n")); " specify <upstream> manually.\n"));
usage_with_options(cherry_usage, options); usage_with_options(cherry_usage, options);
} }
upstream = current_branch->merge[0]->dst;
} }
init_revisions(&revs, prefix); init_revisions(&revs, prefix);
......
...@@ -933,7 +933,7 @@ static int setup_with_upstream(const char ***argv) ...@@ -933,7 +933,7 @@ static int setup_with_upstream(const char ***argv)
if (!branch) if (!branch)
die(_("No current branch.")); die(_("No current branch."));
if (!branch->remote) if (!branch->remote_name)
die(_("No remote for the current branch.")); die(_("No remote for the current branch."));
if (!branch->merge_nr) if (!branch->merge_nr)
die(_("No default upstream defined for the current branch.")); die(_("No default upstream defined for the current branch."));
......
...@@ -49,10 +49,7 @@ static int branches_alloc; ...@@ -49,10 +49,7 @@ static int branches_alloc;
static int branches_nr; static int branches_nr;
static struct branch *current_branch; static struct branch *current_branch;
static const char *default_remote_name;
static const char *branch_pushremote_name;
static const char *pushremote_name; static const char *pushremote_name;
static int explicit_default_remote_name;
static struct rewrites rewrites; static struct rewrites rewrites;
static struct rewrites rewrites_push; static struct rewrites rewrites_push;
...@@ -367,16 +364,9 @@ static int handle_config(const char *key, const char *value, void *cb) ...@@ -367,16 +364,9 @@ static int handle_config(const char *key, const char *value, void *cb)
return 0; return 0;
branch = make_branch(name, subkey - name); branch = make_branch(name, subkey - name);
if (!strcmp(subkey, ".remote")) { if (!strcmp(subkey, ".remote")) {
if (git_config_string(&branch->remote_name, key, value)) return 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;
}
} else if (!strcmp(subkey, ".pushremote")) { } else if (!strcmp(subkey, ".pushremote")) {
if (branch == current_branch) return git_config_string(&branch->pushremote_name, key, value);
if (git_config_string(&branch_pushremote_name, key, value))
return -1;
} else if (!strcmp(subkey, ".merge")) { } else if (!strcmp(subkey, ".merge")) {
if (!value) if (!value)
return config_error_nonbool(key); return config_error_nonbool(key);
...@@ -501,12 +491,15 @@ static void alias_all_urls(void) ...@@ -501,12 +491,15 @@ static void alias_all_urls(void)
static void read_config(void) static void read_config(void)
{ {
static int loaded;
unsigned char sha1[20]; unsigned char sha1[20];
const char *head_ref; const char *head_ref;
int flag; int flag;
if (default_remote_name) /* did this already */
if (loaded)
return; return;
default_remote_name = "origin"; loaded = 1;
current_branch = NULL; current_branch = NULL;
head_ref = resolve_ref_unsafe("HEAD", 0, sha1, &flag); head_ref = resolve_ref_unsafe("HEAD", 0, sha1, &flag);
if (head_ref && (flag & REF_ISSYMREF) && if (head_ref && (flag & REF_ISSYMREF) &&
...@@ -514,10 +507,6 @@ static void read_config(void) ...@@ -514,10 +507,6 @@ static void read_config(void)
current_branch = make_branch(head_ref, 0); current_branch = make_branch(head_ref, 0);
} }
git_config(handle_config, NULL); git_config(handle_config, NULL);
if (branch_pushremote_name) {
free((char *)pushremote_name);
pushremote_name = branch_pushremote_name;
}
alias_all_urls(); alias_all_urls();
} }
...@@ -696,22 +685,45 @@ static int valid_remote_nick(const char *name) ...@@ -696,22 +685,45 @@ static int valid_remote_nick(const char *name)
return !strchr(name, '/'); /* no slash */ 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; struct remote *ret;
int name_given = 0; int name_given = 0;
read_config();
if (name) if (name)
name_given = 1; name_given = 1;
else { else
if (pushremote_name) { name = get_default(current_branch, &name_given);
name = pushremote_name;
name_given = 1;
} else {
name = default_remote_name;
name_given = explicit_default_remote_name;
}
}
ret = make_remote(name, 0); ret = make_remote(name, 0);
if (valid_remote_nick(name)) { if (valid_remote_nick(name)) {
...@@ -731,14 +743,12 @@ static struct remote *remote_get_1(const char *name, const char *pushremote_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) struct remote *remote_get(const char *name)
{ {
read_config(); return remote_get_1(name, remote_for_branch);
return remote_get_1(name, NULL);
} }
struct remote *pushremote_get(const char *name) struct remote *pushremote_get(const char *name)
{ {
read_config(); return remote_get_1(name, pushremote_for_branch);
return remote_get_1(name, pushremote_name);
} }
int remote_is_configured(const char *name) int remote_is_configured(const char *name)
...@@ -1633,15 +1643,31 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, ...@@ -1633,15 +1643,31 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
static void set_merge(struct branch *ret) static void set_merge(struct branch *ret)
{ {
struct remote *remote;
char *ref; char *ref;
unsigned char sha1[20]; unsigned char sha1[20];
int i; 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)); ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge));
for (i = 0; i < ret->merge_nr; i++) { for (i = 0; i < ret->merge_nr; i++) {
ret->merge[i] = xcalloc(1, sizeof(**ret->merge)); ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
ret->merge[i]->src = xstrdup(ret->merge_name[i]); 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, ".")) strcmp(ret->remote_name, "."))
continue; continue;
if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]), if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]),
...@@ -1661,11 +1687,7 @@ struct branch *branch_get(const char *name) ...@@ -1661,11 +1687,7 @@ struct branch *branch_get(const char *name)
ret = current_branch; ret = current_branch;
else else
ret = make_branch(name, 0); ret = make_branch(name, 0);
if (ret && ret->remote_name) { set_merge(ret);
ret->remote = remote_get(ret->remote_name);
if (ret->merge_nr)
set_merge(ret);
}
return ret; return ret;
} }
...@@ -1683,6 +1705,130 @@ int branch_merge_matches(struct branch *branch, ...@@ -1683,6 +1705,130 @@ int branch_merge_matches(struct branch *branch,
return refname_match(branch->merge[i]->src, refname); 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) static int ignore_symref_update(const char *refname)
{ {
unsigned char sha1[20]; unsigned char sha1[20];
...@@ -1877,12 +2023,15 @@ int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1) ...@@ -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 * 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 * Returns -1 if num_ours and num_theirs could not be filled in (e.g., no
* (with "gone" base), otherwise 1 (with base). * 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]; unsigned char sha1[20];
struct commit *ours, *theirs; struct commit *ours, *theirs;
...@@ -1892,12 +2041,13 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) ...@@ -1892,12 +2041,13 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
int rev_argc; int rev_argc;
/* Cannot stat unless we are marked to build on top of somebody else. */ /* Cannot stat unless we are marked to build on top of somebody else. */
if (!branch || base = branch_get_upstream(branch, NULL);
!branch->merge || !branch->merge[0] || !branch->merge[0]->dst) if (upstream_name)
return 0; *upstream_name = base;
if (!base)
return -1;
/* Cannot stat if what we used to build on no longer exists */ /* Cannot stat if what we used to build on no longer exists */
base = branch->merge[0]->dst;
if (read_ref(base, sha1)) if (read_ref(base, sha1))
return -1; return -1;
theirs = lookup_commit_reference(sha1); theirs = lookup_commit_reference(sha1);
...@@ -1913,7 +2063,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) ...@@ -1913,7 +2063,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
/* are we the same? */ /* are we the same? */
if (theirs == ours) { if (theirs == ours) {
*num_theirs = *num_ours = 0; *num_theirs = *num_ours = 0;
return 1; return 0;
} }
/* Run "rev-list --left-right ours...theirs" internally... */ /* 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) ...@@ -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 object flags smudged by the above traversal */
clear_commit_marks(ours, ALL_REV_FLAGS); clear_commit_marks(ours, ALL_REV_FLAGS);
clear_commit_marks(theirs, 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) ...@@ -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 format_tracking_info(struct branch *branch, struct strbuf *sb)
{ {
int ours, theirs; int ours, theirs;
const char *full_base;
char *base; char *base;
int upstream_is_gone = 0; int upstream_is_gone = 0;
switch (stat_tracking_info(branch, &ours, &theirs)) { if (stat_tracking_info(branch, &ours, &theirs, &full_base) < 0) {
case 0: if (!full_base)
/* no base */ return 0;
return 0;
case -1:
/* with "gone" base */
upstream_is_gone = 1; 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) { if (upstream_is_gone) {
strbuf_addf(sb, strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"), _("Your branch is based on '%s', but the upstream is gone.\n"),
......
...@@ -203,19 +203,42 @@ struct branch { ...@@ -203,19 +203,42 @@ struct branch {
const char *refname; const char *refname;
const char *remote_name; const char *remote_name;
struct remote *remote; const char *pushremote_name;
const char **merge_name; const char **merge_name;
struct refspec **merge; struct refspec **merge;
int merge_nr; int merge_nr;
int merge_alloc; int merge_alloc;
const char *push_tracking_ref;
}; };
struct branch *branch_get(const char *name); 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_has_merge_config(struct branch *branch);
int branch_merge_matches(struct branch *, int n, const char *); 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. */ /* Flags to match_refs. */
enum match_refs_flags { enum match_refs_flags {
MATCH_REFS_NONE = 0, MATCH_REFS_NONE = 0,
...@@ -226,7 +249,8 @@ enum match_refs_flags { ...@@ -226,7 +249,8 @@ enum match_refs_flags {
}; };
/* Reporting of tracking info */ /* 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); int format_tracking_info(struct branch *branch, struct strbuf *sb);
struct ref *get_local_heads(void); struct ref *get_local_heads(void);
......
...@@ -416,12 +416,12 @@ static int ambiguous_path(const char *path, int len) ...@@ -416,12 +416,12 @@ static int ambiguous_path(const char *path, int len)
return slash; 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; int i;
for (i = 0; i < ARRAY_SIZE(suffix); i++) { for (i = 0; i < nr; i++) {
int suffix_len = strlen(suffix[i]); int suffix_len = strlen(suffix[i]);
if (suffix_len <= len if (suffix_len <= len
&& !memcmp(string, suffix[i], suffix_len)) && !memcmp(string, suffix[i], suffix_len))
...@@ -430,6 +430,18 @@ static inline int upstream_mark(const char *string, int len) ...@@ -430,6 +430,18 @@ static inline int upstream_mark(const char *string, int len)
return 0; 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 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); 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, ...@@ -477,7 +489,8 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1,
nth_prior = 1; nth_prior = 1;
continue; 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); reflog_len = (len-1) - (at+2);
len = at; len = at;
} }
...@@ -1056,46 +1069,36 @@ static void set_shortened_ref(struct strbuf *buf, const char *ref) ...@@ -1056,46 +1069,36 @@ static void set_shortened_ref(struct strbuf *buf, const char *ref)
free(s); free(s);
} }
static const char *get_upstream_branch(const char *branch_buf, int len) static int interpret_branch_mark(const char *name, int namelen,
{ int at, struct strbuf *buf,
char *branch = xstrndup(branch_buf, len); int (*get_mark)(const char *, int),
struct branch *upstream = branch_get(*branch ? branch : NULL); const char *(*get_data)(struct branch *,
struct strbuf *))
/*
* 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)
{ {
int len; 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) if (!len)
return -1; return -1;
if (memchr(name, ':', at)) if (memchr(name, ':', at))
return -1; 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; return len + at;
} }
...@@ -1146,7 +1149,13 @@ int interpret_branch_name(const char *name, int namelen, struct strbuf *buf) ...@@ -1146,7 +1149,13 @@ int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
if (len > 0) if (len > 0)
return reinterpret(name, namelen, len, buf); 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) if (len > 0)
return len; return len;
} }
......
...@@ -150,7 +150,7 @@ test_expect_success 'branch@{u} works when tracking a local branch' ' ...@@ -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' ' test_expect_success 'branch@{u} error message when no upstream' '
cat >expect <<-EOF && cat >expect <<-EOF &&
fatal: No upstream configured for branch ${sq}non-tracking${sq} fatal: no upstream configured for branch ${sq}non-tracking${sq}
EOF EOF
error_message non-tracking@{u} 2>actual && error_message non-tracking@{u} 2>actual &&
test_i18ncmp expect actual test_i18ncmp expect actual
...@@ -158,7 +158,7 @@ test_expect_success 'branch@{u} error message when no upstream' ' ...@@ -158,7 +158,7 @@ test_expect_success 'branch@{u} error message when no upstream' '
test_expect_success '@{u} error message when no upstream' ' test_expect_success '@{u} error message when no upstream' '
cat >expect <<-EOF && cat >expect <<-EOF &&
fatal: No upstream configured for branch ${sq}master${sq} fatal: no upstream configured for branch ${sq}master${sq}
EOF EOF
test_must_fail git rev-parse --verify @{u} 2>actual && test_must_fail git rev-parse --verify @{u} 2>actual &&
test_i18ncmp expect actual test_i18ncmp expect actual
...@@ -166,7 +166,7 @@ test_expect_success '@{u} error message when no upstream' ' ...@@ -166,7 +166,7 @@ test_expect_success '@{u} error message when no upstream' '
test_expect_success 'branch@{u} error message with misspelt branch' ' test_expect_success 'branch@{u} error message with misspelt branch' '
cat >expect <<-EOF && cat >expect <<-EOF &&
fatal: No such branch: ${sq}no-such-branch${sq} fatal: no such branch: ${sq}no-such-branch${sq}
EOF EOF
error_message no-such-branch@{u} 2>actual && error_message no-such-branch@{u} 2>actual &&
test_i18ncmp expect actual test_i18ncmp expect actual
...@@ -183,7 +183,7 @@ test_expect_success '@{u} error message when not on a branch' ' ...@@ -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' ' test_expect_success 'branch@{u} error message if upstream branch not fetched' '
cat >expect <<-EOF && 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 EOF
error_message bad-upstream@{u} 2>actual && error_message bad-upstream@{u} 2>actual &&
test_i18ncmp expect actual test_i18ncmp expect actual
......
#!/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
...@@ -28,7 +28,10 @@ test_expect_success setup ' ...@@ -28,7 +28,10 @@ test_expect_success setup '
git update-ref refs/remotes/origin/master master && git update-ref refs/remotes/origin/master master &&
git remote add origin nowhere && git remote add origin nowhere &&
git config branch.master.remote origin && 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() { test_atom() {
...@@ -47,6 +50,7 @@ test_atom() { ...@@ -47,6 +50,7 @@ test_atom() {
test_atom head refname refs/heads/master test_atom head refname refs/heads/master
test_atom head upstream refs/remotes/origin/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 objecttype commit
test_atom head objectsize 171 test_atom head objectsize 171
test_atom head objectname $(git rev-parse refs/heads/master) test_atom head objectname $(git rev-parse refs/heads/master)
...@@ -83,6 +87,7 @@ test_atom head HEAD '*' ...@@ -83,6 +87,7 @@ test_atom head HEAD '*'
test_atom tag refname refs/tags/testtag test_atom tag refname refs/tags/testtag
test_atom tag upstream '' test_atom tag upstream ''
test_atom tag push ''
test_atom tag objecttype tag test_atom tag objecttype tag
test_atom tag objectsize 154 test_atom tag objectsize 154
test_atom tag objectname $(git rev-parse refs/tags/testtag) 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' ' ...@@ -347,6 +352,12 @@ test_expect_success 'Check that :track[short] works when upstream is invalid' '
test_cmp expected actual 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 cat >expected <<EOF
$(git rev-parse --short HEAD) $(git rev-parse --short HEAD)
EOF EOF
......
...@@ -1534,21 +1534,15 @@ static void wt_shortstatus_print_tracking(struct wt_status *s) ...@@ -1534,21 +1534,15 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
color_fprintf(s->fp, branch_color_local, "%s", branch_name); color_fprintf(s->fp, branch_color_local, "%s", branch_name);
switch (stat_tracking_info(branch, &num_ours, &num_theirs)) { if (stat_tracking_info(branch, &num_ours, &num_theirs, &base) < 0) {
case 0: if (!base) {
/* no base */ fputc(s->null_termination ? '\0' : '\n', s->fp);
fputc(s->null_termination ? '\0' : '\n', s->fp); return;
return; }
case -1:
/* with "gone" base */
upstream_is_gone = 1; upstream_is_gone = 1;
break;
default:
/* with base */
break;
} }
base = branch->merge[0]->dst;
base = shorten_unambiguous_ref(base, 0); base = shorten_unambiguous_ref(base, 0);
color_fprintf(s->fp, header_color, "..."); color_fprintf(s->fp, header_color, "...");
color_fprintf(s->fp, branch_color_remote, "%s", base); color_fprintf(s->fp, branch_color_remote, "%s", base);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册