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:
commit
c4a8354bc1
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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
262
remote.c
@ -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"),
|
||||
|
28
remote.h
28
remote.h
@ -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);
|
||||
|
81
sha1_name.c
81
sha1_name.c
@ -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;
|
||||
}
|
||||
|
@ -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
63
t/t1514-rev-parse-push.sh
Executable 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
|
@ -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
|
||||
|
18
wt-status.c
18
wt-status.c
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user