From 8391f01768864373d27cf7b1267ea69d54675cf6 Mon Sep 17 00:00:00 2001 From: Harald Nordgren Date: Wed, 22 Jul 2026 18:08:57 +0000 Subject: [PATCH 1/7] remote: pass repository to push tracking helper 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. Signed-off-by: Harald Nordgren Signed-off-by: Junio C Hamano --- remote.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/remote.c b/remote.c index b17648d6ef32e4..0dc36956c346c0 100644 --- a/remote.c +++ b/remote.c @@ -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)) { From 93775e35b7ed7983a8aaa33a566f45a8a1152718 Mon Sep 17 00:00:00 2001 From: Harald Nordgren Date: Wed, 22 Jul 2026 18:08:58 +0000 Subject: [PATCH 2/7] remote: find tracking branches for URL push destinations Git accepts a repository URL as branch..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. Signed-off-by: Harald Nordgren Signed-off-by: Junio C Hamano --- 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(-) diff --git a/Documentation/config/branch.adoc b/Documentation/config/branch.adoc index a4db9fa5c87eab..5a85fde8de9286 100644 --- a/Documentation/config/branch.adoc +++ b/Documentation/config/branch.adoc @@ -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..merge`:: Defines, together with `branch..remote`, the upstream branch diff --git a/Documentation/revisions.adoc b/Documentation/revisions.adoc index 6ea6c7cead1647..3fbfbd3d5fc091 100644 --- a/Documentation/revisions.adoc +++ b/Documentation/revisions.adoc @@ -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: + diff --git a/remote.c b/remote.c index 0dc36956c346c0..3a6abf12589999 100644 --- a/remote.c +++ b/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,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); + 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, diff --git a/remote.h b/remote.h index 72a54d84ad511d..cca02033b9d73b 100644 --- a/remote.h +++ b/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); diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 6f5e86dedeb209..9c2f140d5abe42 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -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 diff --git a/transport.c b/transport.c index fc144f0aedabd9..30a4ab2cd53b9f 100644 --- a/transport.c +++ b/transport.c @@ -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) From 47382f7398df0c8509c30aac6aafa75272099ca5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 22 Jul 2026 10:03:13 -0700 Subject: [PATCH 3/7] revision: honor --exclude-first-parent-only with SEEN first parent The '--exclude-first-parent-only' option instructs the revision walker to follow only the first parent of a merge commit to propagate down the UNINTERESTING bit. However, if the first parent has already been marked SEEN (for example, because it was explicitly specified on the command line), process_parents() skips it with a 'continue' statement. But the loop then continues on to process the second parent, because the check for the '--exclude-first-parent-only' option is near the end of the loop, which the 'continue' statement skips. Consequently, we end up marking the second parent as UNINTERESTING. Break out of the loop instead of continuing when the first parent is already SEEN or fails to parse. This ensures that we do not process subsequent parents and mark them as UNINTERESTING. Signed-off-by: Junio C Hamano Reviewed-by: Jerry Zhang Signed-off-by: Junio C Hamano --- revision.c | 10 ++++++++-- t/t6012-rev-list-simplify.sh | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/revision.c b/revision.c index 599b3a66c369ca..9b306636697242 100644 --- a/revision.c +++ b/revision.c @@ -1152,12 +1152,18 @@ static int process_parents(struct rev_info *revs, struct commit *commit, if (p) p->object.flags |= UNINTERESTING | CHILD_VISITED; - if (repo_parse_commit_gently(revs->repo, p, 1) < 0) + if (repo_parse_commit_gently(revs->repo, p, 1) < 0) { + if (revs->exclude_first_parent_only) + break; continue; + } if (p->parents) mark_parents_uninteresting(revs, p); - if (p->object.flags & SEEN) + if (p->object.flags & SEEN) { + if (revs->exclude_first_parent_only) + break; continue; + } p->object.flags |= (SEEN | NOT_USER_GIVEN); if (list) commit_list_insert_by_date(p, list); diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index 4cecb6224cb464..2284bbba12c726 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -285,4 +285,22 @@ test_expect_success 'log --graph --simplify-merges --show-pulls' ' test_cmp expect actual ' +test_expect_success 'exclude-first-parent-only with parent already seen' ' + git checkout --orphan test-seen && + git rm -rf . && + test_commit r1 && + git checkout -b branch-f && + test_commit f && + git checkout test-seen && + git merge --no-ff --no-edit -m r2 branch-f && + git tag r2 && + + git rev-list --exclude-first-parent-only f ^r2 >actual && + git rev-parse f >expect && + test_cmp expect actual && + + git rev-list --exclude-first-parent-only f r1 ^r2 >actual2 && + test_cmp expect actual2 +' + test_done From 2dbd1c85e12d3329bda20fa2ff049d3083522237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20NICOLAS?= Date: Thu, 23 Jul 2026 02:21:32 +0200 Subject: [PATCH 4/7] submodule: resolve insteadOf aliases when matching remote MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When ca62f524c1 (submodule: look up remotes by URL first, 2025-06-23) introduced a mechanism to identify which remote is to be used by a submodule, it compared the URL stored in the .gitmodules inventory to that of each available remote. The URLs of remotes are rewritten according to url..insteadOf, whereas those stored in the .gitmodules aren't. When such aliasing applies, no match can be made between the two corresponding sides, and the procedure degrades to its fallback logic electing either the only configured remote if there is only one, or "origin" otherwise. That behaviour is unfortunate when no remote is called "origin", because its last resort will have a submodule update command look for a non-existent remote-tracking reference and fail to proceed, instead of using the remote whose rewritten URL matches. Resolve the alias in the URL inventoried in .gitmodules before comparing it against those of the corresponding submodule's configured remotes. Signed-off-by: Éric NICOLAS Signed-off-by: Junio C Hamano --- remote.c | 14 +++++++++++--- t/t7406-submodule-update.sh | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/remote.c b/remote.c index b17648d6ef32e4..b1fed58e79e35e 100644 --- a/remote.c +++ b/remote.c @@ -1821,17 +1821,25 @@ const char *repo_default_remote(struct repository *repo) const char *repo_remote_from_url(struct repository *repo, const char *url) { + char *rewritten_url; + const char *remote_name = NULL; + read_config(repo, 0); + if ((rewritten_url = alias_url(url, &repo->remote_state->rewrites))) + url = rewritten_url; for (int i = 0; i < repo->remote_state->remotes_nr; i++) { struct remote *remote = repo->remote_state->remotes[i]; if (!remote) continue; - if (remote_has_url(remote, url)) - return remote->name; + if (remote_has_url(remote, url)) { + remote_name = remote->name; + break; + } } - return NULL; + free(rewritten_url); + return remote_name; } int branch_has_merge_config(struct branch *branch) diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 9554720152f6f0..10adeabf0fcc85 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -256,6 +256,25 @@ test_expect_success 'submodule update --remote should fetch upstream changes' ' ) ' +test_expect_success 'submodule update --remote resolves URL rewrites' ' + test_config_global "url.$(pwd)/.insteadOf" local: && + mkdir alias-super alias-submodule && + ( + cd alias-submodule && + git init && + git commit --allow-empty --message "Initial commit" + ) && + ( + cd alias-super && + git init && + git submodule add local:alias-submodule submodule && + git submodule update --force && + git -C submodule remote rename origin upstream && + git -C submodule remote add fork user@host && + git submodule update --remote + ) +' + test_expect_success 'submodule update --remote should fetch upstream changes with .' ' ( cd super && From 1034ad383f149bf59f4d78f792a049c94240c8a5 Mon Sep 17 00:00:00 2001 From: Adrian Friedli Date: Fri, 24 Jul 2026 14:41:38 +0200 Subject: [PATCH 5/7] builtin/clone: fix segfault when using --revision with protocol v0 Servers supporting protocol v2 do not advertise excess refs and honor `transport_ls_refs_options.ref_prefixes` when $ git clone --revision=refs/heads/main $URL contacts them, but when talking to a server that does not support protocol v2 the client segfaults. This can also be observed when v0 is enforced for example by $ git -c protocol.version=0 clone --revision=refs/heads/main $URL In the protocol v2 case the server honors `transport_ls_refs_options.ref_prefixes` and in `cmd_clone()` the linked list `refs` returned by `transport_get_remote_refs()` only contains a single item, which is the ref requested with the --revision argument. Both `remote_head` returned by `find_ref_by_name()` and `remote_head_points_at` returned by `guess_remote_head()` are NULL. The guard in `update_remote_refs()` skips a the affected code because `remote_head_points_at` is NULL. In the protocol v0 case in `cmd_clone()` the linked list `refs` returned by `transport_get_remote_refs()` contains many items, amongst others "HEAD". `remote_head` returned by `find_ref_by_name()` is not NULL and `remote_head_points_at` returned by `guess_remote_head()` is not NULL but its field `peer_ref` is NULL. Because `remote_head_points_at` is not NULL the guard in `update_remote_refs()` does not skip the affected code and `remote_head_points_at->peer_ref->name` is accessed, which causes a segfault later on. Signed-off-by: Adrian Friedli Signed-off-by: Junio C Hamano --- builtin/clone.c | 2 +- t/t5621-clone-revision.sh | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/builtin/clone.c b/builtin/clone.c index d60d1b60bc238c..5cbebbf7c23058 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -557,7 +557,7 @@ static void update_remote_refs(const struct ref *refs, write_followtags(refs, msg); } - if (remote_head_points_at && !option_bare) { + if (remote_head_points_at && remote_head_points_at->peer_ref && !option_bare) { struct strbuf head_ref = STRBUF_INIT; strbuf_addstr(&head_ref, branch_top); strbuf_addstr(&head_ref, "HEAD"); diff --git a/t/t5621-clone-revision.sh b/t/t5621-clone-revision.sh index db3b8cff558f0b..54789423f8cccc 100755 --- a/t/t5621-clone-revision.sh +++ b/t/t5621-clone-revision.sh @@ -90,6 +90,14 @@ test_expect_success 'clone with --revision and --bare' ' test_must_fail git -C dst config remote.origin.fetch ' +test_expect_success 'clone with --revision and protocol v0' ' + test_when_finished "rm -rf dst" && + git -c protocol.version=0 clone --no-local --revision=refs/heads/main . dst && + git rev-parse refs/heads/main >expect && + git -C dst rev-parse HEAD >actual && + test_cmp expect actual +' + test_expect_success 'clone with --revision being a short raw commit hash' ' test_when_finished "rm -rf dst" && oid=$(git rev-parse --short refs/heads/feature) && From dcef3bf041c949061878803d3b7c7f7e2373b413 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 25 Jul 2026 09:03:24 -0700 Subject: [PATCH 6/7] remote: plug memory leaks The in-core data structure used to keep track of 'url..{insteadOf,pushInsteadOf} = ' settings is not properly cleaned up when the process is done with it. 'struct rewrites' is embedded in 'remote_state' and serves as the top level of the rewrite data. This holds an array of a variable number of pointers to 'struct rewrite' allocated individually on the heap. Each 'struct rewrite' holds a '.base' string and an array of 'struct counted_string' called '.instead_of', which is allocated contiguously on the heap. Each 'struct counted_string' has a pointer to a string allocated on the heap. Amid these pointers, rewrites_release() fails to free everything other than 'struct rewrite''s '.base' member and the 'struct rewrite' instances themselves. Fix rewrites_release() to also free the contiguous array storing '.instead_of', the string pointers within each '.instead_of' element, and each 'struct rewrite' instance individually allocated on the heap. Signed-off-by: Junio C Hamano --- remote.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/remote.c b/remote.c index a664cd166aa3b9..6c84adb36a3f0d 100644 --- a/remote.c +++ b/remote.c @@ -304,8 +304,15 @@ static struct rewrite *make_rewrite(struct rewrites *r, static void rewrites_release(struct rewrites *r) { - for (int i = 0; i < r->rewrite_nr; i++) - free((char *)r->rewrite[i]->base); + for (int i = 0; i < r->rewrite_nr; i++) { + struct rewrite *rewrite = r->rewrite[i]; + + free((char *)rewrite->base); + for (int j = 0; j < rewrite->instead_of_nr; j++) + free((char *)rewrite->instead_of[j].s); + free(rewrite->instead_of); + free(rewrite); + } free(r->rewrite); memset(r, 0, sizeof(*r)); } From 5b2471720c93ee30e5764a19f3d3b3ae9ec9712a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 3 Aug 2026 09:31:20 -0700 Subject: [PATCH 7/7] The 10th batch Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.56.0.adoc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Documentation/RelNotes/2.56.0.adoc b/Documentation/RelNotes/2.56.0.adoc index 6cee8bb8e89134..a57ca5426fb4ca 100644 --- a/Documentation/RelNotes/2.56.0.adoc +++ b/Documentation/RelNotes/2.56.0.adoc @@ -445,3 +445,24 @@ Fixes since v2.55 'gitweb' has been corrected to work even when the index line carries a trailing file mode. (merge fda513d6fe tl/gitweb-shorten-hashes-with-modes later to maint). + + * 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}'). + + * Traversals with '--exclude-first-parent-only' have been corrected + to properly stop after the first parent even when it has already + been marked as 'SEEN'. + (merge 47382f7398 jc/exclude-first-parent-seen later to maint). + + * A segfault when 'git clone --revision' talks to a server that does not + support protocol v2 (falling back to protocol v0) has been corrected. + (merge 1034ad383f af/clone-revision-v0-segfault-fix later to maint). + + * rewrites_release() in 'remote.c' has been updated to free 'struct + rewrite' instances, their '.instead_of' arrays, and their contents. + (merge dcef3bf041 jc/remote-insteadof-leakfix later to maint). + + * The remote-matching logic for submodules has been corrected to resolve + 'url.*.insteadOf' aliases before comparing the inventoried URL from + '.gitmodules' with the URLs of configured remotes.