Skip to content

Commit fcf43d7

Browse files
committed
Merge branch 'hn/url-push-tracking' into seen
When the push remote is specified as a URL, the fetch refspec of a uniquely matching configured remote is now used to find and update the remote-tracking branch (e.g., '@{push}'). * hn/url-push-tracking: remote: resolve URL-valued push tracking remotes remote: pass repository to push tracking helper
2 parents 15b21a2 + 98bec76 commit fcf43d7

5 files changed

Lines changed: 144 additions & 6 deletions

File tree

Documentation/revisions.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ some output processing may assume ref names in UTF-8.
127127
`git push` were run while `branchname` was checked out (or the current
128128
`HEAD` if no branchname is specified). Like for '@\{upstream\}', we report
129129
the remote-tracking branch that corresponds to that branch at the remote.
130+
If the push remote is specified as a URL, the fetch refspec of a uniquely
131+
matching configured remote is used to find and update the remote-tracking
132+
branch.
130133
+
131134
Here's an example to make it more clear:
132135
+

remote.c

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1887,12 +1887,38 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err)
18871887
return branch->merge[0]->dst;
18881888
}
18891889

1890-
static char *tracking_for_push_dest(struct remote *remote,
1890+
struct remote *repo_remote_for_push_tracking(struct repository *repo,
1891+
struct remote *remote)
1892+
{
1893+
struct remote *first_match = NULL;
1894+
struct remote_state *remote_state = repo->remote_state;
1895+
1896+
if (remote->origin != REMOTE_UNCONFIGURED || remote->url.nr != 1)
1897+
return remote;
1898+
1899+
for (int i = 0; i < remote_state->remotes_nr; i++) {
1900+
struct remote *candidate = remote_state->remotes[i];
1901+
1902+
if (!candidate || candidate == remote ||
1903+
!remote_is_configured(candidate, 0) ||
1904+
!remote_has_url(candidate, remote->url.v[0]))
1905+
continue;
1906+
if (first_match)
1907+
return remote;
1908+
first_match = candidate;
1909+
}
1910+
1911+
return first_match ? first_match : remote;
1912+
}
1913+
1914+
static char *tracking_for_push_dest(struct repository *repo,
1915+
struct remote *remote,
18911916
const char *refname,
18921917
struct strbuf *err)
18931918
{
18941919
char *ret;
18951920

1921+
remote = repo_remote_for_push_tracking(repo, remote);
18961922
ret = apply_refspecs(&remote->fetch, refname);
18971923
if (!ret)
18981924
return error_buf(err,
@@ -1925,21 +1951,21 @@ static char *branch_get_push_1(struct repository *repo,
19251951
_("push refspecs for '%s' do not include '%s'"),
19261952
remote->name, branch->name);
19271953

1928-
ret = tracking_for_push_dest(remote, dst, err);
1954+
ret = tracking_for_push_dest(repo, remote, dst, err);
19291955
free(dst);
19301956
return ret;
19311957
}
19321958

19331959
if (remote->mirror)
1934-
return tracking_for_push_dest(remote, branch->refname, err);
1960+
return tracking_for_push_dest(repo, remote, branch->refname, err);
19351961

19361962
switch (repo_config_values(repo)->push_default) {
19371963
case PUSH_DEFAULT_NOTHING:
19381964
return error_buf(err, _("push has no destination (push.default is 'nothing')"));
19391965

19401966
case PUSH_DEFAULT_MATCHING:
19411967
case PUSH_DEFAULT_CURRENT:
1942-
return tracking_for_push_dest(remote, branch->refname, err);
1968+
return tracking_for_push_dest(repo, remote, branch->refname, err);
19431969

19441970
case PUSH_DEFAULT_UPSTREAM:
19451971
return xstrdup_or_null(branch_get_upstream(branch, err));
@@ -1953,7 +1979,7 @@ static char *branch_get_push_1(struct repository *repo,
19531979
up = branch_get_upstream(branch, err);
19541980
if (!up)
19551981
return NULL;
1956-
cur = tracking_for_push_dest(remote, branch->refname, err);
1982+
cur = tracking_for_push_dest(repo, remote, branch->refname, err);
19571983
if (!cur)
19581984
return NULL;
19591985
if (strcmp(cur, up)) {

remote.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,8 @@ char *remote_ref_for_branch(struct branch *branch, int for_push);
345345

346346
const char *repo_default_remote(struct repository *repo);
347347
const char *repo_remote_from_url(struct repository *repo, const char *url);
348+
struct remote *repo_remote_for_push_tracking(struct repository *repo,
349+
struct remote *remote);
348350

349351
/* returns true if the given branch has merge configuration given. */
350352
int branch_has_merge_config(struct branch *branch);

t/t5505-remote.sh

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,28 @@ setup_repository () {
2424
)
2525
}
2626

27+
setup_url_pushremote () {
28+
rm -rf fork.git client &&
29+
git clone --bare one fork.git &&
30+
git clone one client &&
31+
fork_url="$TRASH_DIRECTORY/fork.git" &&
32+
(
33+
cd client &&
34+
git checkout -b topic --track origin/main &&
35+
git commit --allow-empty -m topic-change &&
36+
git config push.default current &&
37+
git config status.compareBranches "@{upstream} @{push}" &&
38+
git config branch.topic.pushRemote "$fork_url" &&
39+
git push
40+
)
41+
}
42+
43+
check_status () {
44+
git -C client status >actual &&
45+
cat >expected &&
46+
test_cmp expected actual
47+
}
48+
2749
tokens_match () {
2850
echo "$1" | tr ' ' '\012' | sort | sed -e '/^$/d' >expect &&
2951
echo "$2" | tr ' ' '\012' | sort | sed -e '/^$/d' >actual &&
@@ -1018,6 +1040,88 @@ test_expect_success 'rename a remote renames repo remote.pushDefault but keeps g
10181040
)
10191041
'
10201042

1043+
test_expect_success 'URL-valued pushRemote without matching remote is not trackable' '
1044+
setup_url_pushremote &&
1045+
1046+
check_status <<-EOF
1047+
On branch topic
1048+
Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
1049+
(use "git push" to publish your local commits)
1050+
1051+
nothing to commit, working tree clean
1052+
EOF
1053+
'
1054+
1055+
test_expect_success 'adding fork remote makes URL-valued pushRemote trackable' '
1056+
setup_url_pushremote &&
1057+
1058+
(
1059+
cd client &&
1060+
git remote rename origin upstream &&
1061+
git remote add -f origin "$fork_url"
1062+
) &&
1063+
1064+
check_status <<-EOF
1065+
On branch topic
1066+
Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
1067+
1068+
Your branch is up to date with ${SQ}origin/topic${SQ}.
1069+
1070+
nothing to commit, working tree clean
1071+
EOF
1072+
'
1073+
1074+
test_expect_success 'up-to-date URL push refreshes stale tracking branch' '
1075+
setup_url_pushremote &&
1076+
(
1077+
cd client &&
1078+
git remote rename origin upstream &&
1079+
git remote add -f origin "$fork_url" &&
1080+
git commit --allow-empty -m another-topic-change &&
1081+
git -C ../fork.git fetch ../client topic:topic
1082+
) &&
1083+
1084+
check_status <<-EOF &&
1085+
On branch topic
1086+
Your branch is ahead of ${SQ}upstream/main${SQ} by 2 commits.
1087+
1088+
Your branch is ahead of ${SQ}origin/topic${SQ} by 1 commit.
1089+
(use "git push" to publish your local commits)
1090+
1091+
nothing to commit, working tree clean
1092+
EOF
1093+
1094+
git -C client push >actual 2>&1 &&
1095+
test_grep "Everything up-to-date" actual &&
1096+
1097+
check_status <<-EOF
1098+
On branch topic
1099+
Your branch is ahead of ${SQ}upstream/main${SQ} by 2 commits.
1100+
1101+
Your branch is up to date with ${SQ}origin/topic${SQ}.
1102+
1103+
nothing to commit, working tree clean
1104+
EOF
1105+
'
1106+
1107+
test_expect_success 'duplicate remote URL leaves URL-valued pushRemote ambiguous' '
1108+
setup_url_pushremote &&
1109+
(
1110+
cd client &&
1111+
git remote rename origin upstream &&
1112+
git remote add -f origin "$fork_url" &&
1113+
git remote add duplicate "$fork_url"
1114+
) &&
1115+
1116+
check_status <<-EOF
1117+
On branch topic
1118+
Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
1119+
(use "git push" to publish your local commits)
1120+
1121+
nothing to commit, working tree clean
1122+
EOF
1123+
'
1124+
10211125
test_expect_success 'rename handles remote without fetch refspec' '
10221126
git clone --bare one no-refspec.git &&
10231127
# confirm assumption that bare clone does not create refspec

transport.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1597,8 +1597,11 @@ int transport_push(struct repository *r,
15971597
if (!(flags & (TRANSPORT_PUSH_DRY_RUN |
15981598
TRANSPORT_RECURSE_SUBMODULES_ONLY))) {
15991599
struct ref *ref;
1600+
struct remote *tracking_remote = repo_remote_for_push_tracking(
1601+
r, transport->remote);
1602+
16001603
for (ref = remote_refs; ref; ref = ref->next)
1601-
transport_update_tracking_ref(transport->remote, ref, verbose);
1604+
transport_update_tracking_ref(tracking_remote, ref, verbose);
16021605
}
16031606

16041607
if (porcelain && !push_ret)

0 commit comments

Comments
 (0)