remote: resolve url push tracking
cover remote: resolve url push tracking
After renaming remotes, git status may stop showing the push branch even though Git still pushes to the right URL, use the remote with the same URL to find it.
Harald Nordgren (2):
remote: pass repository to push tracking helper
remote: resolve URL-valued push tracking remotes
Documentation/revisions.adoc | 3 + remote.c | 36 ++++++++++-- remote.h | 2 + t/t5505-remote.sh | 104 +++++++++++++++++++++++++++++++++++ transport.c | 5 +- 5 files changed, 144 insertions(+), 6 deletions(-)
base-commit: 41365c2a9ba347870b80881c0d67454edd22fd49
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2358%2FHaraldNordgren%2Fremote-resolve-url-push-tracking-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2358/HaraldNordgren/remote-resolve-url-push-tracking-v1
Pull-Request: https://github.com/git/git/pull/2358
Signature · 1 line
gitgitgadget
PATCH 1/2 remote: pass repository to push tracking helper
From: Harald Nordgren <[email protected]>
The push tracking helper currently only needs the push remote. However, resolving a URL-valued remote requires access to the repository's list of configured remotes.
Pass the repository through the existing callers and mark the parameter as unused for now. This prepares the helper for that lookup without changing its behavior.
--- remote.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)
remote.c +6 -5
@@ -1887,7 +1887,8 @@ 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, +static char *tracking_for_push_dest(struct repository *repo UNUSED, +struct remote *remote,const char *refname,struct strbuf *err) {
@@ -1925,13 +1926,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 (push_default) {case PUSH_DEFAULT_NOTHING :
@@ -1939,7 +1940,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 +1954,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)) {
Signature · 1 line
gitgitgadget
Re: [PATCH 1/2] remote: pass repository to push tracking helper
Quoted 5 lines · Harald Nordgren via GitGitGadget
From: Harald Nordgren <[email protected]>
The push tracking helper currently only needs the push remote. However, resolving a URL-valued remote requires access to the repository's list of configured remotes.
It is unclear to me what 'resolving a URL-valued remote' means. Could you describe what you are trying to achieve, without relying on unexplained terms like 'to resolve' and 'URL-valued remote', which seem to carry specialized meanings in this context?
Thanks.
Quoted 57 lines
Pass the repository through the existing callers and mark the parameter as unused for now. This prepares the helper for that lookup without changing its behavior.
Signed-off-by Harald Nordgren <[email protected]>--- remote.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)remote.c +6
-5@@ -1887,7 +1887,8 @@ 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, +static char *tracking_for_push_dest(struct repository *repo UNUSED, +struct remote *remote,const char *refname,struct strbuf *err) {@@ -1925,13 +1926,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 (push_default) {case PUSH_DEFAULT_NOTHING :@@ -1939,7 +1940,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 +1954,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)) {
PATCH 2/2 remote: resolve URL-valued push tracking remotes
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
A branch may name its push destination with a URL instead of a configured remote. This is useful in fork workflows, where the original remote is renamed to "upstream", the fork is added as "origin", and an existing branch.<name>.pushRemote continues to contain the fork URL.
Git can still push through the anonymous remote created for that URL. However, the anonymous remote has no fetch refspec. Git therefore cannot resolve @{push} to origin/<branch> or update that remote-tracking branch after a push. The push can succeed, or report that everything is up to date, while status continues to compare against a stale tracking ref or cannot show the push branch at all.
A uniquely matching configured remote already provides the missing mapping. Use its fetch refspec when resolving the push tracking branch and when updating tracking refs after a push. This changes neither the push destination nor configuration. Keep the existing behavior when no remote matches or multiple remotes share the URL, since either case is ambiguous.
--- Documentation/revisions.adoc | 3 + remote.c | 27 ++++++++- remote.h | 2 + t/t5505-remote.sh | 104 +++++++++++++++++++++++++++++++++++ transport.c | 5 +- 5 files changed, 139 insertions(+), 2 deletions(-)
Documentation/revisions.adoc +3 -0
@@ -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 remote is specified as a URL, the fetch refspec of a uniquely + matching configured remote is used to find and update the remote-tracking + branch. + Here's an example to make it more clear: +
remote.c +26 -1
@@ -1887,13 +1887,38 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err)return branch->merge[0 ]->dst; } -static char *tracking_for_push_dest(struct repository *repo UNUSED, +struct remote *repo_remote_for_push_tracking (struct repository *repo, +struct remote *remote) +{ +struct remote *first_match =NULL ; +struct remote_state *remote_state = repo->remote_state; + +if (remote->origin !=REMOTE_UNCONFIGURED || remote->url.nr !=1 ) +return remote; + +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_url (candidate, remote->url.v[0 ])) +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,
remote.h +2 -0
@@ -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);
t/t5505-remote.sh +104 -0
116 lines not on this page · raw message
transport.c +4 -1
@@ -1553,8 +1553,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)
Signature · 1 line
gitgitgadget
Re: [PATCH 2/2] remote: resolve URL-valued push tracking remotes
Quoted 7 lines · Harald Nordgren via GitGitGadget
From: Harald Nordgren <[email protected]>
A branch may name its push destination with a URL instead of a configured remote. This is useful in fork workflows, where the original remote is renamed to "upstream", the fork is added as "origin", and an existing branch.<name>.pushRemote continues to contain the fork URL.
Quoted 6 lines
Git can still push through the anonymous remote created for that URL. However, the anonymous remote has no fetch refspec. Git therefore cannot resolve @{push} to origin/<branch> or update that remote-tracking branch after a push. The push can succeed, or report that everything is up to date, while status continues to compare against a stale tracking ref or cannot show the push branch at all.
Let me try to think aloud, rephrasing the explanation with a slightly more concrete illustration, to see whether I understand what you are trying to achieve.
The current system allows you to set:
[branch "mytopic"]
pushRemote = https://hosting.site/users/me/mine.git/
[remote "notlinked"]
url = https://hosting.site/users/me/mine.git/
push = refs/heads/mytopic
fetch = refs/heads/*:refs/remotes/notlinked/*
but when on the 'mytopic' branch, @{push} cannot determine which branch at the remote repository to update, so it cannot map it back to our remote-tracking branch ('refs/remotes/notlinked/mytopic' in the above illustration).
A question. Do we currently accept a string that is not a remote name as the value for 'branch.<name>.pushRemote' by design?
The 'git config --help' output explains that:
- 'branch.<name>.pushRemote' overrides 'branch.<name>.remote' and 'remote.pushDefault'; and
- 'branch.<name>.remote' and 'remote.pushDefault' tell 'git fetch' and 'git push' which remote to work with.
It therefore seems clear that setting a string that is not a remote name (such as a URL) as the value for these three variables is a misconfiguration in the current system.
I am not saying that it should stay that way forever. But please re-read your first sentence and tell me whether it is clear that the patch extends the current system with a new feature. It was far from clear to me and caused significant confusion. Writing it like this:
Under the current system, a branch cannot name its push destination using a URL. If we were to extend the system to allow this, such and such benefits would become possible.
would have been far less confusing.
If that is what you are doing, that is.
Quoted 2 lines
A uniquely matching configured remote already provides the missing mapping.
A very good consideration. It was the first thing that came to my mind while I was thinking aloud, constructing an illustration with 'notlinked', wondering "what if there is another remote, with the same URL, but different 'push' configuration?".
Quoted 2 lines
Use its fetch refspec when resolving the push tracking branch and when updating tracking refs after a push.
Is this not needless, and is mentioning it not confusing? If I understand correctly, what the change entails is:
* If the value of 'branch.<name>.pushRemote' (call it X) is 'not' a remote name, try to see whether there is a unique remote that has either (1) a 'pushurl' whose value matches X, or (2) no 'pushurl' but a 'url' whose value matches X. If no such remote exists, simply abort and refuse to proceed.
* If there is such a remote, pretend that the value of 'branch.<name>.pushRemote' were the name of that remote, and do everything else as usual.
And mapping the current branch name to its push destination via 'remote.<name>.push' to find the name of the destination branch at the remote, and then mapping it back to our remote-tracking branch using 'remote.<name>.fetch', is not something new that this topic needs to update, no?
Thanks. Once I understand what you are trying to achieve, I will offer further comments on the implementation, as I find this topic potentially quite interesting.
Re: [PATCH 2/2] remote: resolve URL-valued push tracking remotes
Thanks for your continued support on all my topics!
Yes, I should clarify in the commit message what the actual motivation is, which is for me to handle remote renames in a smoother way, since 'gh' renmames remotes when forking a repo which is messing with @{push} and compareBranches for 'git status'.
Harald
Re: [PATCH 2/2] remote: resolve URL-valued push tracking remotes
Quoted 6 lines · Harald Nordgren
Thanks for your continued support on all my topics!
Yes, I should clarify in the commit message what the actual motivation is, which is for me to handle remote renames in a smoother way, since 'gh' renmames remotes when forking a repo which is messing with @{push} and compareBranches for 'git status'.
Yeah, it would be a good thing to do in an updated version.
Thanks.
Re: [PATCH 2/2] remote: resolve URL-valued push tracking remotes
In reply to Harald Nordgren via GitGitGadgetQuoted 21 lines · Harald Nordgren via GitGitGadget
From: Harald Nordgren <[email protected]>
A branch may name its push destination with a URL instead of a configured remote. This is useful in fork workflows, where the original remote is renamed to "upstream", the fork is added as "origin", and an existing branch.<name>.pushRemote continues to contain the fork URL.
Git can still push through the anonymous remote created for that URL. However, the anonymous remote has no fetch refspec. Git therefore cannot resolve @{push} to origin/<branch> or update that remote-tracking branch after a push. The push can succeed, or report that everything is up to date, while status continues to compare against a stale tracking ref or cannot show the push branch at all.
A uniquely matching configured remote already provides the missing mapping. Use its fetch refspec when resolving the push tracking branch and when updating tracking refs after a push. This changes neither the push destination nor configuration. Keep the existing behavior when no remote matches or multiple remotes share the URL, since either case is ambiguous.
...
remote.c const char *branch_get_upstream(struct branch *branch, struct strbuf *err) · line 1897
Quoted 8 lines
remote.c +8
-0+struct remote *repo_remote_for_push_tracking (struct repository *repo, +struct remote *remote) +{ +struct remote *first_match =NULL ; +struct remote_state *remote_state = repo->remote_state; + +if (remote->origin !=REMOTE_UNCONFIGURED || remote->url.nr !=1 ) +return remote;
I briefly wondered what should happen when a caller passes NULL as the remote parameter to this function, but it turns out that no caller passes NULL. One caller is tracking_for_push_dest(), which is called from branch_get_push_1(). The latter refuses to proceed when !remote is true and does not call tracking_for_push_dest(), meaning it cannot pass NULL to this function. The other caller is transport_push(), which passes transport->remote. This value comes from transport_get(), which ensures transport->remote is not NULL before returning, so it cannot pass NULL to this function either.
Therefore, it is OK to assume remote is not NULL, and let the program crash loudly if that assumption is violated. Adding an explicit BUG() check would be overkill here:
if (!repo || !remote)
BUG("...");remote.c const char *branch_get_upstream(struct branch *branch, struct strbuf *err) · line 1905
Quoted 7 lines
remote.c +7
-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_url (candidate, remote->url.v[0 ])) +continue ;
This check, as well as the safety uniqueness check at the beginning of the function, only pays attention to the url member. However, it should also consider the pushurl member and, when it exists, ignore the url member. The upfront check would then look something like this (please sanity check the details):
const char *check_url = NULL;
if (remote->origin != REMOTE_UNCONFIGURED)
return remote;
if (remote->pushurl.nr) {
if (remote->pushurl.nr != 1)
return remote;
check_url = remote->pushurl.v[0];
} else if (remote->url.nr != 1) {
return remote;
} else {
check_url = remote->url.v[0];
}The test inside the loop would then use check_url:
!remote_has_url(candidate, check_url)
instead of testing remote->url.v[0] directly.
Thanks.
v2 cover remote: renamed remote push tracking
In reply to Harald Nordgren via GitGitGadgetKeep git status showing the push branch after remotes are renamed by finding the configured remote with the same URL.
Changes in v3:
* Revamp commit messages to clarify motivation.
Changes in v2:
* Clarify that URL push destinations already work and that this change only restores their tracking information.
* Document URL values for branch.<name>.pushRemote and their @{push} behavior.
Harald Nordgren (2):
remote: pass repository to push tracking helper
remote: find tracking branches for URL push destinations
Documentation/config/branch.adoc | 2 + Documentation/revisions.adoc | 3 + remote.c | 36 +++++++++-- remote.h | 2 + t/t5505-remote.sh | 104 +++++++++++++++++++++++++++++++ transport.c | 5 +- 6 files changed, 146 insertions(+), 6 deletions(-)
base-commit: 48bbf81c29ca9a4479ec7850fe206518682cdb2f
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2358%2FHaraldNordgren%2Fremote-resolve-url-push-tracking-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2358/HaraldNordgren/remote-resolve-url-push-tracking-v2
Pull-Request: https://github.com/git/git/pull/2358
Range-diff vs v1:
1: fc70895732 ! 1: b1ac49de87 remote: pass repository to push tracking helper
@@ Metadata
## Commit message ##
remote: pass repository to push tracking helper
- The push tracking helper currently only needs the push remote. However,
- resolving a URL-valued remote requires access to the repository's list
- of configured remotes.
+ The next commit needs tracking_for_push_dest() to inspect the
+ repository's configured remotes. Pass the repository through the
+ existing callers and mark the new parameter as unused.
- Pass the repository through the existing callers and mark the parameter
- as unused for now. This prepares the helper for that lookup without
- changing its behavior.
+ No change in behavior.
2: ff645b2159 ! 2: 6e924a7fec remote: resolve URL-valued push tracking remotes
@@ Metadata
Author: Harald Nordgren <[email protected]>
## Commit message ##
- remote: resolve URL-valued push tracking remotes
+ remote: find tracking branches for URL push destinations
- A branch may name its push destination with a URL instead of a
- configured remote. This is useful in fork workflows, where the original
- remote is renamed to "upstream", the fork is added as "origin", and an
- existing branch.<name>.pushRemote continues to contain the fork URL.
+ Git already accepts a repository URL as branch.<name>.pushRemote and
+ can push to it. When a configured remote has the same URL, however,
+ "git status" cannot show that remote's push branch.
- Git can still push through the anonymous remote created for that URL.
- However, the anonymous remote has no fetch refspec. Git therefore cannot
- resolve @{push} to origin/<branch> or update that remote-tracking branch
- after a push. The push can succeed, or report that everything is up to
- date, while status continues to compare against a stale tracking ref or
- cannot show the push branch at all.
+ This can happen in fork workflows when the original remote is renamed
+ to "upstream", the fork is added as "origin", and an existing
+ pushRemote value still contains the fork URL. The URL still points to
+ the right repository, so pushing works. However, @{push} is unavailable
+ because Git does not connect the URL to "origin". As a result,
+ "git status" cannot show the push branch, and an up-to-date push can
+ leave its local tracking information stale.
- A uniquely matching configured remote already provides the missing
- mapping. Use its fetch refspec when resolving the push tracking branch
- and when updating tracking refs after a push. This changes neither the
- push destination nor configuration. Keep the existing behavior when no
- remote matches or multiple remotes share the URL, since either case is
- ambiguous.
+ When exactly one configured remote has the URL as one of its
+ remote.<name>.url values, use its fetch refspec to find and refresh the
+ push branch. Keep the URL as the push destination so the configured
+ remote's push settings do not change existing behavior. Keep the
+ current behavior when no remote matches or multiple remotes match.
+ ## Documentation/config/branch.adoc ##
+@@ Documentation/config/branch.adoc: 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. A URL is used directly as the push destination.
+
+ branch.<name>.merge::
+ Defines, together with branch.<name>.remote, the upstream branch
+
## Documentation/revisions.adoc ##
@@ Documentation/revisions.adoc: 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 remote is specified as a URL, the fetch refspec of a uniquely -+ matching configured remote is used to find and update the remote-tracking -+ branch.
++ If the push destination is a URL and exactly one configured remote has the ++ same remote.<name>.url, '@\{push}' reports the remote-tracking branch for ++ that remote.
+
Here's an example to make it more clear:
+
Signature · 1 line
gitgitgadget
PATCH v2 1/2 remote: pass repository to push tracking helper
From: Harald Nordgren <[email protected]>
The next commit needs tracking_for_push_dest() to inspect the repository's configured remotes. Pass the repository through the existing callers and mark the new parameter as unused.
No change in behavior.
--- remote.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)
remote.c +6 -5
@@ -1887,7 +1887,8 @@ 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, +static char *tracking_for_push_dest(struct repository *repo UNUSED, +struct remote *remote,const char *refname,struct strbuf *err) {
@@ -1925,13 +1926,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 (push_default) {case PUSH_DEFAULT_NOTHING :
@@ -1939,7 +1940,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 +1954,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)) {
Signature · 1 line
gitgitgadget
PATCH v2 2/2 remote: find tracking branches for URL push destinations
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
Git already accepts a repository URL as branch.<name>.pushRemote and can push to it. When a configured remote has the same URL, however, "git status" cannot show that remote's push branch.
This can happen in fork workflows when the original remote is renamed to "upstream", the fork is added as "origin", and an existing pushRemote value still contains the fork URL. The URL still points to the right repository, so pushing works. However, @{push} is unavailable because Git does not connect the URL to "origin". As a result, "git status" cannot show the push branch, and an up-to-date push can leave its local tracking information stale.
When exactly one configured remote has the URL as one of its remote.<name>.url values, use its fetch refspec to find and refresh the push branch. Keep the URL as the push destination so the configured remote's push settings do not change existing behavior. Keep the current behavior when no remote matches or multiple remotes match.
--- Documentation/config/branch.adoc | 2 + Documentation/revisions.adoc | 3 + remote.c | 27 +++++++- remote.h | 2 + t/t5505-remote.sh | 104 +++++++++++++++++++++++++++++++ transport.c | 5 +- 6 files changed, 141 insertions(+), 2 deletions(-)
Documentation/config/branch.adoc +2 -0
@@ -55,6 +55,8 @@ 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. A URL is used directly as the push destination. `branch.<name>.merge`:: Defines, together with `branch.<name>.remote`, the upstream branch
Documentation/revisions.adoc +3 -0
@@ -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 has the + same `remote.<name>.url`, '@\{push}' reports the remote-tracking branch for + that remote. + Here's an example to make it more clear: +
remote.c +26 -1
@@ -1887,13 +1887,38 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err)return branch->merge[0 ]->dst; } -static char *tracking_for_push_dest(struct repository *repo UNUSED, +struct remote *repo_remote_for_push_tracking (struct repository *repo, +struct remote *remote) +{ +struct remote *first_match =NULL ; +struct remote_state *remote_state = repo->remote_state; + +if (remote->origin !=REMOTE_UNCONFIGURED || remote->url.nr !=1 ) +return remote; + +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_url (candidate, remote->url.v[0 ])) +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,
remote.h +2 -0
@@ -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);
t/t5505-remote.sh +104 -0
116 lines not on this page · raw message
transport.c +4 -1
@@ -1553,8 +1553,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)
Signature · 1 line
gitgitgadget
Re: [PATCH v2 0/2] remote: renamed remote push tracking
In reply to Harald Nordgren via GitGitGadgetHi Harald,
Quoted 70 lines · Harald Nordgren via GitGitGadget
Keep git status showing the push branch after remotes are renamed by finding the configured remote with the same URL.
Changes in v3:
* Revamp commit messages to clarify motivation.
Changes in v2:
* Clarify that URL push destinations already work and that this change only restores their tracking information.
* Document URL values for branch.<name>.pushRemote and their @{push} behavior.Harald Nordgren (2):
remote: pass repository to push tracking helper
remote: find tracking branches for URL push destinationsDocumentation/config/branch.adoc | 2 + Documentation/revisions.adoc | 3 + remote.c | 36 +++++++++-- remote.h | 2 + t/t5505-remote.sh | 104 +++++++++++++++++++++++++++++++ transport.c | 5 +- 6 files changed, 146 insertions(+), 6 deletions(-)base-commit:
48bbf81c29ca9a4479ec7850fe206518682cdb2f
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2358%2FHaraldNordgren%2Fremote-resolve-url-push-tracking-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2358/HaraldNordgren/remote-resolve-url-push-tracking-v2
Pull-Request: https://github.com/git/git/pull/2358Range-diff vs v1:
1:
fc70895732! 1:b1ac49de87remote: pass repository to push tracking helper
@@ Metadata
## Commit message ##
remote: pass repository to push tracking helper- The push tracking helper currently only needs the push remote. However,
- resolving a URL-valued remote requires access to the repository's list
- of configured remotes.
+ The next commit needstracking_for_push_dest()to inspect the
+ repository's configured remotes. Pass the repository through the
+ existing callers and mark the new parameter as unused.- Pass the repository through the existing callers and mark the parameter
- as unused for now. This prepares the helper for that lookup without
- changing its behavior.
+ No change in behavior.Signed-off-by Harald Nordgren <[email protected]>2:
ff645b2159! 2:6e924a7fecremote: resolve URL-valued push tracking remotes
@@ Metadata
Author: Harald Nordgren <[email protected]>## Commit message ##
- remote: resolve URL-valued push tracking remotes
+ remote: find tracking branches for URL push destinations- A branch may name its push destination with a URL instead of a
- configured remote. This is useful in fork workflows, where the original
- remote is renamed to "upstream", the fork is added as "origin", and an
- existing branch.<name>.pushRemote continues to contain the fork URL.
+ Git already accepts a repository URL as branch.<name>.pushRemote and
+ can push to it. When a configured remote has the same URL, however,
+ "git status" cannot show that remote's push branch.
Quoted 13 lines
- Git can still push through the anonymous remote created for that URL.
- However, the anonymous remote has no fetch refspec. Git therefore cannot
- resolve @{push} to origin/<branch> or update that remote-tracking branch
- after a push. The push can succeed, or report that everything is up to
- date, while status continues to compare against a stale tracking ref or
- cannot show the push branch at all.
+ This can happen in fork workflows when the original remote is renamed
+ to "upstream", the fork is added as "origin", and an existing
+ pushRemote value still contains the fork URL. The URL still points to
+ the right repository, so pushing works. However, @{push} is unavailable
+ because Git does not connect the URL to "origin". As a result,
+ "git status" cannot show the push branch, and an up-to-date push can
+ leave its local tracking information stale.
I'm a bit confused about the problem scenario here: if the pushRemote value contains a URL, then renaming a remote has nothing to do with it, right?
And if the pushRemote value contains a remote name, then renaming the remote should propagate there as well, right? (At least, that's my recollection of renaming; when I have used the GitHub CLI in the past it has worked pretty well in that case, but maybe they've changed things recently?)
I do think the URL<->remote matching for user display is a nice touch, so I'm not against the series! Just want to understand the problem statement well. Maybe I should read over the test cases, or you could suggest a "how I hit this in the real world" recipe? (Explicit commands are easier for me than natural language in that case.)
Signature · 1 line
D. Ben Knoble
Re: [PATCH v2 0/2] remote: renamed remote push tracking
It's very valid and I need to explain it better if you are confused.
Harald
v3 cover remote: url-based pushRemote with renamed remotes
In reply to Harald Nordgren via GitGitGadgetFix git status not showing the push branch after remotes are renamed, when branch.<name>.pushRemote is a URL matching exactly one configured remote.
Changes in v4:
* Match against the actual push URL, including pushurl and pushInsteadOf.
* Clarify how rearranging remotes exposes the git status tracking problem.
* Simplify and correct the documentation for URL-valued pushRemote.
Changes in v3:
* Revamp commit messages to clarify motivation.
Changes in v2:
* Clarify that URL push destinations already work and that this change only restores their tracking information.
* Document URL values for branch.<name>.pushRemote and their @{push} behavior.
Harald Nordgren (2):
remote: pass repository to push tracking helper
remote: find tracking branches for URL push destinations
Documentation/config/branch.adoc | 1 + Documentation/revisions.adoc | 3 + remote.c | 43 +++++++++-- remote.h | 2 + t/t5505-remote.sh | 124 +++++++++++++++++++++++++++++++ transport.c | 5 +- 6 files changed, 172 insertions(+), 6 deletions(-)
base-commit: 48bbf81c29ca9a4479ec7850fe206518682cdb2f
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2358%2FHaraldNordgren%2Fremote-resolve-url-push-tracking-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2358/HaraldNordgren/remote-resolve-url-push-tracking-v3
Pull-Request: https://github.com/git/git/pull/2358
Range-diff vs v2:
1: b1ac49de87 = 1: b1ac49de87 remote: pass repository to push tracking helper
2: 6e924a7fec ! 2: a343af9d50 remote: find tracking branches for URL push destinations
@@ Metadata
## Commit message ##
remote: find tracking branches for URL push destinations
- Git already accepts a repository URL as branch.<name>.pushRemote and
- can push to it. When a configured remote has the same URL, however,
- "git status" cannot show that remote's push branch.
+ Git accepts a repository URL as branch.<name>.pushRemote and can push
+ to it. This branch setting takes precedence over remote.pushDefault.
- This can happen in fork workflows when the original remote is renamed
- to "upstream", the fork is added as "origin", and an existing
- pushRemote value still contains the fork URL. The URL still points to
- the right repository, so pushing works. However, @{push} is unavailable
- because Git does not connect the URL to "origin". As a result,
+ A branch can be configured with a URL-valued pushRemote before any push
+ occurs. If the remotes are later rearranged with "git remote rename" and
+ "git remote add", the newly added remote may use that URL. The URL value
+ is unaffected by the rename and continues to take precedence over
+ remote.pushDefault. The URL and the remote then point to the same
+ repository, but Git does not connect them for tracking. Pushing works,
+ but @{push} cannot identify the remote's tracking branch. As a result,
"git status" cannot show the push branch, and an up-to-date push can
- leave its local tracking information stale.
+ leave its tracking information stale.
- When exactly one configured remote has the URL as one of its
- remote.<name>.url values, use its fetch refspec to find and refresh the
- push branch. Keep the URL as the push destination so the configured
- remote's push settings do not change existing behavior. Keep the
- current behavior when no remote matches or multiple remotes match.
+ When exactly one configured remote uses the push destination URL, use
+ that remote for push tracking. Continue to push to the URL so the
+ configured remote's push settings do not change existing behavior. Keep
+ the current behavior when no remote matches or multiple remotes match.
Signed-off-by: Harald Nordgren <[email protected]>
@@ Documentation/config/branch.adoc: 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. A URL is used directly as the push destination.
++ 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
@@ Documentation/revisions.adoc: some output processing may assume ref names in UTF
`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 has the
-+ same `remote.<name>.url`, '@\{push}' reports the remote-tracking branch for
-+ that remote.
++ If the push destination is a URL and exactly one configured remote has
++ that URL among its `remote.<name>.url` values, '@\{push}' reports that
++ remote's remote-tracking branch.
+
Here's an example to make it more clear:
+
@@ remote.c: const char *branch_get_upstream(struct branch *branch, struct strbuf *
+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 || remote->url.nr != 1)
++ 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_url(candidate, remote->url.v[0]))
++ !remote_has_url(candidate, check_url))
+ continue;
+ if (first_match)
+ return remote;
@@ t/t5505-remote.sh: setup_repository () {
+ rm -rf fork.git client &&
+ git clone --bare one fork.git &&
+ git clone one client &&
-+ fork_url="$TRASH_DIRECTORY/fork.git" &&
++ fork_url="file://$TRASH_DIRECTORY/fork.git" &&
+ (
+ cd client &&
+ git checkout -b topic --track origin/main &&
@@ t/t5505-remote.sh: test_expect_success 'rename a remote renames repo remote.push
+ EOF
+'
+
-+test_expect_success 'adding fork remote makes URL-valued pushRemote trackable' '
++test_expect_success 'adding matching remote makes URL-valued pushRemote trackable' '
+ setup_url_pushremote &&
+
+ (
@@ t/t5505-remote.sh: test_expect_success 'rename a remote renames repo remote.push
+ 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 &&
+ (Signature · 1 line
gitgitgadget
PATCH v3 1/2 remote: pass repository to push tracking helper
From: Harald Nordgren <[email protected]>
The next commit needs tracking_for_push_dest() to inspect the repository's configured remotes. Pass the repository through the existing callers and mark the new parameter as unused.
No change in behavior.
--- remote.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)
remote.c +6 -5
@@ -1887,7 +1887,8 @@ 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, +static char *tracking_for_push_dest(struct repository *repo UNUSED, +struct remote *remote,const char *refname,struct strbuf *err) {
@@ -1925,13 +1926,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 (push_default) {case PUSH_DEFAULT_NOTHING :
@@ -1939,7 +1940,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 +1954,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)) {
Signature · 1 line
gitgitgadget
PATCH v3 2/2 remote: find tracking branches for URL push destinations
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
Git accepts a repository URL as branch.<name>.pushRemote and can push to it. This branch setting takes precedence over remote.pushDefault.
A branch can be configured with a URL-valued pushRemote before any push occurs. If the remotes are later rearranged with "git remote rename" and "git remote add", the newly added remote may use that URL. The URL value is unaffected by the rename and continues to take precedence over remote.pushDefault. The URL and the remote then point to the same repository, but Git does not connect them for tracking. Pushing works, but @{push} cannot identify the remote's tracking branch. As a result, "git status" cannot show the push branch, and an up-to-date push can leave its tracking information stale.
When exactly one configured remote uses the push destination URL, use that remote for push tracking. Continue to push to the URL so the configured remote's push settings do not change existing behavior. Keep the current behavior when no remote matches or multiple remotes match.
--- Documentation/config/branch.adoc | 1 + Documentation/revisions.adoc | 3 + remote.c | 34 ++++++++- remote.h | 2 + t/t5505-remote.sh | 124 +++++++++++++++++++++++++++++++ transport.c | 5 +- 6 files changed, 167 insertions(+), 2 deletions(-)
Documentation/config/branch.adoc +1 -0
@@ -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
Documentation/revisions.adoc +3 -0
@@ -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 has + that URL among its `remote.<name>.url` values, '@\{push}' reports that + remote's remote-tracking branch. + Here's an example to make it more clear: +
remote.c +33 -1
@@ -1887,13 +1887,45 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err)return branch->merge[0 ]->dst; } -static char *tracking_for_push_dest(struct repository *repo UNUSED, +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_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,
remote.h +2 -0
@@ -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);
t/t5505-remote.sh +124 -0
136 lines not on this page · raw message
transport.c +4 -1
@@ -1553,8 +1553,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)
Signature · 1 line
gitgitgadget
Re: [PATCH v3 2/2] remote: find tracking branches for URL push destinations
Quoted 21 lines · Harald Nordgren via GitGitGadget
From: Harald Nordgren <[email protected]>
Git accepts a repository URL as branch.<name>.pushRemote and can push to it. This branch setting takes precedence over remote.pushDefault.
A branch can be configured with a URL-valued pushRemote before any push occurs. If the remotes are later rearranged with "git remote rename" and "git remote add", the newly added remote may use that URL. The URL value is unaffected by the rename and continues to take precedence over remote.pushDefault. The URL and the remote then point to the same repository, but Git does not connect them for tracking. Pushing works, but @{push} cannot identify the remote's tracking branch. As a result, "git status" cannot show the push branch, and an up-to-date push can leave its tracking information stale.
When exactly one configured remote uses the push destination URL, use that remote for push tracking. Continue to push to the URL so the configured remote's push settings do not change existing behavior. Keep the current behavior when no remote matches or multiple remotes match.
Signed-off-by Harald Nordgren <[email protected]>
Quoted 8 lines
--- Documentation/config/branch.adoc | 1 + Documentation/revisions.adoc | 3 + remote.c | 34 ++++++++- remote.h | 2 + t/t5505-remote.sh | 124 +++++++++++++++++++++++++++++++ transport.c | 5 +- 6 files changed, 167 insertions(+), 2 deletions(-)
Hmph, the changes since the previous round look a bit incoherent.
Quoted 17 lines
Documentation/config/branch.adoc +1
-0@@ -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 branchDocumentation/revisions.adoc +0
-0
Documentation/revisions.adoc some output processing may assume ref names in UTF-8. · line 132
Quoted 6 lines
Documentation/revisions.adoc +3
-0@@ -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 has + that URL among its `remote.<name>.url` values, '@\{push}' reports that + remote's remote-tracking branch.
We claim we use remote.<name>.url here.
Quoted 16 lines
Documentation/revisions.adoc +0
-0Here's an example to make it more clear: +remote.c +5
-1@@ -1887,13 +1887,45 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err)return branch->merge[0 ]->dst; } -static char *tracking_for_push_dest(struct repository *repo UNUSED, +struct remote *repo_remote_for_push_tracking (struct repository *repo, +struct remote *remote) +{ +const struct strvec *push_urls; +struct remote *first_match =NULL ;
remote.c const char *branch_get_upstream(struct branch *branch, struct strbuf *err) · line 1904
Quoted 10 lines
remote.c +10
-0+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 ];
But we correctly pay attention to both .url and .pushurl, giving precedence to the latter.
Quoted 72 lines
remote.c +14
-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_url (candidate, check_url)) +continue ; +if (first_match) +return remote; + first_match = candidate; + } + +return first_match ? first_match : remote; +}t/t5505-remote.sh +42
-0@@ -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,108 @@ 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" + ) &&
t/t5505-remote.sh test_expect_success 'rename a remote renames repo remote.pushDefault but keeps g · line 1072
Quoted 10 lines
t/t5505-remote.sh +10
-0+ + 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 +'
But the test does not seem to exercise remote.<name>.pushURL anywhere.
t/t5505-remote.sh test_expect_success 'rename a remote renames repo remote.pushDefault but keeps g · line 1082
Quoted 9 lines
t/t5505-remote.sh +9
-0+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: + ) &&
Testing insteadof is a nice touch, though.
Quoted 7 lines
t/t5505-remote.sh +7
-0+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"
t/t5505-remote.sh test_expect_success 'rename a remote renames repo remote.pushDefault but keeps g · line 1143
Quoted 10 lines
t/t5505-remote.sh +10
-0+ ) && + + 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 +'
So is a test that checks non-unique case where the machinery should not kick in.
Thanks.
Re: [PATCH v3 0/2] remote: url-based pushRemote with renamed remotes
In reply to Harald Nordgren via GitGitGadgetQuoted 81 lines · Harald Nordgren via GitGitGadget
Fix git status not showing the push branch after remotes are renamed, when branch.<name>.pushRemote is a URL matching exactly one configured remote.
Changes in v4:
* Match against the actual push URL, including pushurl and pushInsteadOf.
* Clarify how rearranging remotes exposes the git status tracking problem.
* Simplify and correct the documentation for URL-valued pushRemote.Changes in v3:
* Revamp commit messages to clarify motivation.
Changes in v2:
* Clarify that URL push destinations already work and that this change only restores their tracking information.
* Document URL values for branch.<name>.pushRemote and their @{push} behavior.Harald Nordgren (2):
remote: pass repository to push tracking helper
remote: find tracking branches for URL push destinationsDocumentation/config/branch.adoc | 1 + Documentation/revisions.adoc | 3 + remote.c | 43 +++++++++-- remote.h | 2 + t/t5505-remote.sh | 124 +++++++++++++++++++++++++++++++ transport.c | 5 +- 6 files changed, 172 insertions(+), 6 deletions(-)base-commit:
48bbf81c29ca9a4479ec7850fe206518682cdb2f
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2358%2FHaraldNordgren%2Fremote-resolve-url-push-tracking-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2358/HaraldNordgren/remote-resolve-url-push-tracking-v3
Pull-Request: https://github.com/git/git/pull/2358Range-diff vs v2:
1: b1ac49de87 = 1: b1ac49de87 remote: pass repository to push tracking helper 2: 6e924a7fec ! 2: a343af9d50 remote: find tracking branches for URL push destinations@@ Metadata
## Commit message ##
remote: find tracking branches for URL push destinations- Git already accepts a repository URL as branch.<name>.pushRemote and
- can push to it. When a configured remote has the same URL, however,
- "git status" cannot show that remote's push branch.
+ Git accepts a repository URL as branch.<name>.pushRemote and can push
+ to it. This branch setting takes precedence over remote.pushDefault.- This can happen in fork workflows when the original remote is renamed
- to "upstream", the fork is added as "origin", and an existing
- pushRemote value still contains the fork URL. The URL still points to
- the right repository, so pushing works. However, @{push} is unavailable
- because Git does not connect the URL to "origin". As a result,
+ A branch can be configured with a URL-valued pushRemote before any push
+ occurs. If the remotes are later rearranged with "git remote rename" and
+ "git remote add", the newly added remote may use that URL. The URL value
+ is unaffected by the rename and continues to take precedence over
+ remote.pushDefault. The URL and the remote then point to the same
+ repository, but Git does not connect them for tracking. Pushing works,
+ but @{push} cannot identify the remote's tracking branch. As a result,
"git status" cannot show the push branch, and an up-to-date push can
- leave its local tracking information stale.
+ leave its tracking information stale.- When exactly one configured remote has the URL as one of its
- remote.<name>.url values, use its fetch refspec to find and refresh the
- push branch. Keep the URL as the push destination so the configured
- remote's push settings do not change existing behavior. Keep the
- current behavior when no remote matches or multiple remotes match.
+ When exactly one configured remote uses the push destination URL, use
+ that remote for push tracking. Continue to push to the URL so the
+ configured remote's push settings do not change existing behavior. Keep
+ the current behavior when no remote matches or multiple remotes match.
Quoted 1 line
Signed-off-by Harald Nordgren <[email protected]>
I find this explanation makes more sense ; it’s not how I use remote renames, since I don’t usually use URL-valued remotes, but I can see how things arise now. Thanks!
v4 cover remote: url-based pushRemote with renamed remotes
In reply to Harald Nordgren via GitGitGadgetFix git status not showing the push branch after remotes are renamed, when branch.<name>.pushRemote is a URL matching exactly one configured remote.
Changes in v4:
* Match configured remotes by effective push URL, preferring pushurl over url.
* Update the documentation and rationale to describe where the remote would push.
Changes in v3:
* Revamp commit messages to clarify motivation.
Changes in v2:
* Clarify that URL push destinations already work and that this change only restores their tracking information.
* Document URL values for branch.<name>.pushRemote and their @{push} behavior.
Harald Nordgren (2):
remote: pass repository to push tracking helper
remote: find tracking branches for URL push destinations
Documentation/config/branch.adoc | 1 + Documentation/revisions.adoc | 3 + remote.c | 54 ++++++++++-- remote.h | 2 + t/t5505-remote.sh | 144 +++++++++++++++++++++++++++++++ transport.c | 5 +- 6 files changed, 203 insertions(+), 6 deletions(-)
base-commit: 5d2e7709234afea1b6ddb25cd4f60d3d5fb3c200
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2358%2FHaraldNordgren%2Fremote-resolve-url-push-tracking-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2358/HaraldNordgren/remote-resolve-url-push-tracking-v4
Pull-Request: https://github.com/git/git/pull/2358
Range-diff vs v3:
1: b1ac49de87 = 1: 494287bade remote: pass repository to push tracking helper
2: a343af9d50 ! 2: 08c432a2d4 remote: find tracking branches for URL push destinations
@@ Commit message
"git status" cannot show the push branch, and an up-to-date push can
leave its tracking information stale.
- When exactly one configured remote uses the push destination URL, use
- that remote for push tracking. Continue to push to the URL so the
- configured remote's push settings do not change existing behavior. Keep
- the current behavior when no remote matches or multiple remotes match.
+ When exactly one configured remote would push to the same URL, use that
+ remote for push tracking. Continue to push to the URL so the configured
+ remote's push settings do not change existing behavior. Keep the current
+ behavior when no remote matches or multiple remotes match.
Signed-off-by: Harald Nordgren <[email protected]>
@@ Documentation/revisions.adoc: some output processing may assume ref names in UTF
`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 has
-+ that URL among its `remote.<name>.url` values, '@\{push}' reports that
-+ remote's remote-tracking branch.
++ 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:
+
## remote.c ##
+@@ remote.c: 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) {
@@ remote.c: const char *branch_get_upstream(struct branch *branch, struct strbuf *err)
return branch->merge[0]->dst;
}
@@ remote.c: const char *branch_get_upstream(struct branch *branch, struct strbuf *
+
+ if (!candidate || candidate == remote ||
+ !remote_is_configured(candidate, 0) ||
-+ !remote_has_url(candidate, check_url))
++ !remote_has_push_url(candidate, check_url))
+ continue;
+ if (first_match)
+ return remote;
@@ t/t5505-remote.sh: test_expect_success 'rename a remote renames repo remote.push
+ 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 &&
+ (Signature · 1 line
gitgitgadget
PATCH v4 1/2 remote: pass repository to push tracking helper
From: Harald Nordgren <[email protected]>
The next commit needs tracking_for_push_dest() to inspect the repository's configured remotes. Pass the repository through the existing callers and mark the new parameter as unused.
No change in behavior.
--- remote.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)
remote.c +6 -5
@@ -1887,7 +1887,8 @@ 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, +static char *tracking_for_push_dest(struct repository *repo UNUSED, +struct remote *remote,const char *refname,struct strbuf *err) {
@@ -1925,13 +1926,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 (push_default) {case PUSH_DEFAULT_NOTHING :
@@ -1939,7 +1940,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 +1954,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)) {
Signature · 1 line
gitgitgadget
PATCH v4 2/2 remote: find tracking branches for URL push destinations
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
Git accepts a repository URL as branch.<name>.pushRemote and can push to it. This branch setting takes precedence over remote.pushDefault.
A branch can be configured with a URL-valued pushRemote before any push occurs. If the remotes are later rearranged with "git remote rename" and "git remote add", the newly added remote may use that URL. The URL value is unaffected by the rename and continues to take precedence over remote.pushDefault. The URL and the remote then point to the same repository, but Git does not connect them for tracking. Pushing works, but @{push} cannot identify the remote's tracking branch. As a result, "git status" cannot show the push branch, and an up-to-date push can leave its tracking information stale.
When exactly one configured remote would push to the same URL, use that remote for push tracking. Continue to push to the URL so the configured remote's push settings do not change existing behavior. Keep the current behavior when no remote matches or multiple remotes match.
--- Documentation/config/branch.adoc | 1 + Documentation/revisions.adoc | 3 + remote.c | 45 +++++++++- remote.h | 2 + t/t5505-remote.sh | 144 +++++++++++++++++++++++++++++++ transport.c | 5 +- 6 files changed, 198 insertions(+), 2 deletions(-)
Documentation/config/branch.adoc +1 -0
@@ -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
Documentation/revisions.adoc +3 -0
@@ -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: +
remote.c +44 -1
63 lines not on this page · raw message
remote.h +2 -0
@@ -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);
t/t5505-remote.sh +144 -0
156 lines not on this page · raw message
transport.c +4 -1
@@ -1553,8 +1553,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)
Signature · 1 line
gitgitgadget
Re: [PATCH v4 2/2] remote: find tracking branches for URL push destinations
remote.c struct strvec *push_url_of_remote(struct remote *remote) · line 966
Quoted 10 lines · Harald Nordgren via GitGitGadget
remote.c +10
-0+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; +}
A new helper is very much welcome.
Quoted 20 lines
void
ref_push_report_free(struct ref_push_report *report){remote.c +12
-1while (report) {@@ -1887,13 +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 repository *repo UNUSED, +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);
remote.c const char *branch_get_upstream(struct branch *branch, struct strbuf *err) · line 1922
Quoted 10 lines
remote.c +10
-0+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))
This part used to use remote_has_url(candidate, remote->url.v[0]), which only looked at the .url and ignored .pushurl. Now it uses remote_has_push_url() so we grab the effective push URL for the remote we are dealing with and match it against the effective push URL of the candidates. Looks correct.
transport.c int transport_push(struct repository *r, · line 1557
Quoted 10 lines
transport.c +2
-0@@ -1553,8 +1553,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);
Personally, I would have line-wrapped the above more like this:
struct remote *tracking_remote = repo_remote_for_push_tracking(r, transport->remote);
This is just for a future reference; it is certainly not critical enough to warrant a new iteration just for this.
Thanks.
Re: [PATCH v4 0/2] remote: url-based pushRemote with renamed remotes
In reply to Harald Nordgren via GitGitGadgetQuoted 9 lines · Harald Nordgren via GitGitGadget
Fix git status not showing the push branch after remotes are renamed, when branch.<name>.pushRemote is a URL matching exactly one configured remote.
Changes in v4:
* Match configured remotes by effective push URL, preferring pushurl over url.
* Update the documentation and rationale to describe where the remote would push.
My cursory review did not spot anything obviously wrong anymore.
As it somehow seems to be a slow week, I do not expect to see many eyeballs from others helping to review the topics in flight as quickly as we would have liked to move them forward, though.
Thanks.