Merge branch 'hn/url-push-tracking' into jch
When the push remote is specified as a URL, the fetch refspec of a
uniquely matching configured remote is now used to find and update
the remote-tracking branch (e.g., '@{push}').
* hn/url-push-tracking:
remote: find tracking branches for URL push destinations
remote: pass repository to push tracking helper
jch
commit
740ffec908
|
|
@ -55,6 +55,7 @@ This option defaults to `never`.
|
|||
repository), you would want to set `remote.pushDefault` to
|
||||
specify the remote to push to for all branches, and use this
|
||||
option to override it for a specific branch.
|
||||
The value may be the name of a configured remote or a repository URL.
|
||||
|
||||
`branch.<name>.merge`::
|
||||
Defines, together with `branch.<name>.remote`, the upstream branch
|
||||
|
|
|
|||
|
|
@ -127,6 +127,9 @@ some output processing may assume ref names in UTF-8.
|
|||
`git push` were run while `branchname` was checked out (or the current
|
||||
`HEAD` if no branchname is specified). Like for '@\{upstream\}', we report
|
||||
the remote-tracking branch that corresponds to that branch at the remote.
|
||||
If the push destination is a URL and exactly one configured remote uses
|
||||
that URL for pushing, '@\{push}' reports that remote's remote-tracking
|
||||
branch.
|
||||
+
|
||||
Here's an example to make it more clear:
|
||||
+
|
||||
|
|
|
|||
54
remote.c
54
remote.c
|
|
@ -954,6 +954,17 @@ struct strvec *push_url_of_remote(struct remote *remote)
|
|||
return remote->pushurl.nr ? &remote->pushurl : &remote->url;
|
||||
}
|
||||
|
||||
static bool remote_has_push_url(struct remote *remote, const char *url)
|
||||
{
|
||||
const struct strvec *push_urls = push_url_of_remote(remote);
|
||||
|
||||
for (size_t i = 0; i < push_urls->nr; i++) {
|
||||
if (!strcmp(push_urls->v[i], url))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ref_push_report_free(struct ref_push_report *report)
|
||||
{
|
||||
while (report) {
|
||||
|
|
@ -1887,12 +1898,45 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err)
|
|||
return branch->merge[0]->dst;
|
||||
}
|
||||
|
||||
static char *tracking_for_push_dest(struct remote *remote,
|
||||
struct remote *repo_remote_for_push_tracking(struct repository *repo,
|
||||
struct remote *remote)
|
||||
{
|
||||
const struct strvec *push_urls;
|
||||
struct remote *first_match = NULL;
|
||||
struct remote_state *remote_state = repo->remote_state;
|
||||
const char *check_url;
|
||||
|
||||
if (remote->origin != REMOTE_UNCONFIGURED)
|
||||
return remote;
|
||||
|
||||
push_urls = push_url_of_remote(remote);
|
||||
if (push_urls->nr != 1)
|
||||
return remote;
|
||||
check_url = push_urls->v[0];
|
||||
|
||||
for (int i = 0; i < remote_state->remotes_nr; i++) {
|
||||
struct remote *candidate = remote_state->remotes[i];
|
||||
|
||||
if (!candidate || candidate == remote ||
|
||||
!remote_is_configured(candidate, 0) ||
|
||||
!remote_has_push_url(candidate, check_url))
|
||||
continue;
|
||||
if (first_match)
|
||||
return remote;
|
||||
first_match = candidate;
|
||||
}
|
||||
|
||||
return first_match ? first_match : remote;
|
||||
}
|
||||
|
||||
static char *tracking_for_push_dest(struct repository *repo,
|
||||
struct remote *remote,
|
||||
const char *refname,
|
||||
struct strbuf *err)
|
||||
{
|
||||
char *ret;
|
||||
|
||||
remote = repo_remote_for_push_tracking(repo, remote);
|
||||
ret = apply_refspecs(&remote->fetch, refname);
|
||||
if (!ret)
|
||||
return error_buf(err,
|
||||
|
|
@ -1925,13 +1969,13 @@ static char *branch_get_push_1(struct repository *repo,
|
|||
_("push refspecs for '%s' do not include '%s'"),
|
||||
remote->name, branch->name);
|
||||
|
||||
ret = tracking_for_push_dest(remote, dst, err);
|
||||
ret = tracking_for_push_dest(repo, remote, dst, err);
|
||||
free(dst);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (remote->mirror)
|
||||
return tracking_for_push_dest(remote, branch->refname, err);
|
||||
return tracking_for_push_dest(repo, remote, branch->refname, err);
|
||||
|
||||
switch (repo_config_values(repo)->push_default) {
|
||||
case PUSH_DEFAULT_NOTHING:
|
||||
|
|
@ -1939,7 +1983,7 @@ static char *branch_get_push_1(struct repository *repo,
|
|||
|
||||
case PUSH_DEFAULT_MATCHING:
|
||||
case PUSH_DEFAULT_CURRENT:
|
||||
return tracking_for_push_dest(remote, branch->refname, err);
|
||||
return tracking_for_push_dest(repo, remote, branch->refname, err);
|
||||
|
||||
case PUSH_DEFAULT_UPSTREAM:
|
||||
return xstrdup_or_null(branch_get_upstream(branch, err));
|
||||
|
|
@ -1953,7 +1997,7 @@ static char *branch_get_push_1(struct repository *repo,
|
|||
up = branch_get_upstream(branch, err);
|
||||
if (!up)
|
||||
return NULL;
|
||||
cur = tracking_for_push_dest(remote, branch->refname, err);
|
||||
cur = tracking_for_push_dest(repo, remote, branch->refname, err);
|
||||
if (!cur)
|
||||
return NULL;
|
||||
if (strcmp(cur, up)) {
|
||||
|
|
|
|||
2
remote.h
2
remote.h
|
|
@ -345,6 +345,8 @@ char *remote_ref_for_branch(struct branch *branch, int for_push);
|
|||
|
||||
const char *repo_default_remote(struct repository *repo);
|
||||
const char *repo_remote_from_url(struct repository *repo, const char *url);
|
||||
struct remote *repo_remote_for_push_tracking(struct repository *repo,
|
||||
struct remote *remote);
|
||||
|
||||
/* returns true if the given branch has merge configuration given. */
|
||||
int branch_has_merge_config(struct branch *branch);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,28 @@ setup_repository () {
|
|||
)
|
||||
}
|
||||
|
||||
setup_url_pushremote () {
|
||||
rm -rf fork.git client &&
|
||||
git clone --bare one fork.git &&
|
||||
git clone one client &&
|
||||
fork_url="file://$TRASH_DIRECTORY/fork.git" &&
|
||||
(
|
||||
cd client &&
|
||||
git checkout -b topic --track origin/main &&
|
||||
git commit --allow-empty -m topic-change &&
|
||||
git config push.default current &&
|
||||
git config status.compareBranches "@{upstream} @{push}" &&
|
||||
git config branch.topic.pushRemote "$fork_url" &&
|
||||
git push
|
||||
)
|
||||
}
|
||||
|
||||
check_status () {
|
||||
git -C client status >actual &&
|
||||
cat >expected &&
|
||||
test_cmp expected actual
|
||||
}
|
||||
|
||||
tokens_match () {
|
||||
echo "$1" | tr ' ' '\012' | sort | sed -e '/^$/d' >expect &&
|
||||
echo "$2" | tr ' ' '\012' | sort | sed -e '/^$/d' >actual &&
|
||||
|
|
@ -1018,6 +1040,128 @@ test_expect_success 'rename a remote renames repo remote.pushDefault but keeps g
|
|||
)
|
||||
'
|
||||
|
||||
test_expect_success 'URL-valued pushRemote without matching remote is not trackable' '
|
||||
setup_url_pushremote &&
|
||||
|
||||
check_status <<-EOF
|
||||
On branch topic
|
||||
Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
|
||||
(use "git push" to publish your local commits)
|
||||
|
||||
nothing to commit, working tree clean
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'adding matching remote makes URL-valued pushRemote trackable' '
|
||||
setup_url_pushremote &&
|
||||
|
||||
(
|
||||
cd client &&
|
||||
git remote rename origin upstream &&
|
||||
git remote add -f origin "$fork_url"
|
||||
) &&
|
||||
|
||||
check_status <<-EOF
|
||||
On branch topic
|
||||
Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
|
||||
|
||||
Your branch is up to date with ${SQ}origin/topic${SQ}.
|
||||
|
||||
nothing to commit, working tree clean
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'configured pushurl makes URL-valued pushRemote trackable' '
|
||||
setup_url_pushremote &&
|
||||
|
||||
(
|
||||
cd client &&
|
||||
git remote rename origin upstream &&
|
||||
git remote add -f origin ../fork.git &&
|
||||
git remote set-url --push origin "$fork_url"
|
||||
) &&
|
||||
|
||||
check_status <<-EOF
|
||||
On branch topic
|
||||
Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
|
||||
|
||||
Your branch is up to date with ${SQ}origin/topic${SQ}.
|
||||
|
||||
nothing to commit, working tree clean
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'pushInsteadOf URL pushRemote is trackable' '
|
||||
setup_url_pushremote &&
|
||||
(
|
||||
cd client &&
|
||||
git remote rename origin upstream &&
|
||||
git remote add -f origin "$fork_url" &&
|
||||
git config "url.$fork_url.pushInsteadOf" fork: &&
|
||||
git config branch.topic.pushRemote fork:
|
||||
) &&
|
||||
|
||||
check_status <<-EOF
|
||||
On branch topic
|
||||
Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
|
||||
|
||||
Your branch is up to date with ${SQ}origin/topic${SQ}.
|
||||
|
||||
nothing to commit, working tree clean
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'up-to-date URL push refreshes stale tracking branch' '
|
||||
setup_url_pushremote &&
|
||||
(
|
||||
cd client &&
|
||||
git remote rename origin upstream &&
|
||||
git remote add -f origin "$fork_url" &&
|
||||
git commit --allow-empty -m another-topic-change &&
|
||||
git -C ../fork.git fetch ../client topic:topic
|
||||
) &&
|
||||
|
||||
check_status <<-EOF &&
|
||||
On branch topic
|
||||
Your branch is ahead of ${SQ}upstream/main${SQ} by 2 commits.
|
||||
|
||||
Your branch is ahead of ${SQ}origin/topic${SQ} by 1 commit.
|
||||
(use "git push" to publish your local commits)
|
||||
|
||||
nothing to commit, working tree clean
|
||||
EOF
|
||||
|
||||
git -C client push >actual 2>&1 &&
|
||||
test_grep "Everything up-to-date" actual &&
|
||||
|
||||
check_status <<-EOF
|
||||
On branch topic
|
||||
Your branch is ahead of ${SQ}upstream/main${SQ} by 2 commits.
|
||||
|
||||
Your branch is up to date with ${SQ}origin/topic${SQ}.
|
||||
|
||||
nothing to commit, working tree clean
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'duplicate remote URL leaves URL-valued pushRemote ambiguous' '
|
||||
setup_url_pushremote &&
|
||||
(
|
||||
cd client &&
|
||||
git remote rename origin upstream &&
|
||||
git remote add -f origin "$fork_url" &&
|
||||
git remote add duplicate "$fork_url"
|
||||
) &&
|
||||
|
||||
check_status <<-EOF
|
||||
On branch topic
|
||||
Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
|
||||
(use "git push" to publish your local commits)
|
||||
|
||||
nothing to commit, working tree clean
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'rename handles remote without fetch refspec' '
|
||||
git clone --bare one no-refspec.git &&
|
||||
# confirm assumption that bare clone does not create refspec
|
||||
|
|
|
|||
|
|
@ -1597,8 +1597,11 @@ int transport_push(struct repository *r,
|
|||
if (!(flags & (TRANSPORT_PUSH_DRY_RUN |
|
||||
TRANSPORT_RECURSE_SUBMODULES_ONLY))) {
|
||||
struct ref *ref;
|
||||
struct remote *tracking_remote = repo_remote_for_push_tracking(
|
||||
r, transport->remote);
|
||||
|
||||
for (ref = remote_refs; ref; ref = ref->next)
|
||||
transport_update_tracking_ref(transport->remote, ref, verbose);
|
||||
transport_update_tracking_ref(tracking_remote, ref, verbose);
|
||||
}
|
||||
|
||||
if (porcelain && !push_ret)
|
||||
|
|
|
|||
Loading…
Reference in New Issue