Skip to content

Commit 24e4e2e

Browse files
committed
Merge branch 'hn/url-push-tracking'
When the push remote is specified as a URL, the fetch refspec of a uniquely matching configured remote is now used to find and update the remote-tracking branch (e.g., '@{push}'). * hn/url-push-tracking: remote: find tracking branches for URL push destinations remote: pass repository to push tracking helper
2 parents a97fcc3 + 93775e3 commit 24e4e2e

6 files changed

Lines changed: 203 additions & 6 deletions

File tree

Documentation/config/branch.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ This option defaults to `never`.
5555
repository), you would want to set `remote.pushDefault` to
5656
specify the remote to push to for all branches, and use this
5757
option to override it for a specific branch.
58+
The value may be the name of a configured remote or a repository URL.
5859
5960
`branch.<name>.merge`::
6061
Defines, together with `branch.<name>.remote`, the upstream branch

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 destination is a URL and exactly one configured remote uses
131+
that URL for pushing, '@\{push}' reports that remote's remote-tracking
132+
branch.
130133
+
131134
Here's an example to make it more clear:
132135
+

remote.c

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,17 @@ struct strvec *push_url_of_remote(struct remote *remote)
954954
return remote->pushurl.nr ? &remote->pushurl : &remote->url;
955955
}
956956

957+
static bool remote_has_push_url(struct remote *remote, const char *url)
958+
{
959+
const struct strvec *push_urls = push_url_of_remote(remote);
960+
961+
for (size_t i = 0; i < push_urls->nr; i++) {
962+
if (!strcmp(push_urls->v[i], url))
963+
return true;
964+
}
965+
return false;
966+
}
967+
957968
void ref_push_report_free(struct ref_push_report *report)
958969
{
959970
while (report) {
@@ -1887,12 +1898,45 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err)
18871898
return branch->merge[0]->dst;
18881899
}
18891900

1890-
static char *tracking_for_push_dest(struct remote *remote,
1901+
struct remote *repo_remote_for_push_tracking(struct repository *repo,
1902+
struct remote *remote)
1903+
{
1904+
const struct strvec *push_urls;
1905+
struct remote *first_match = NULL;
1906+
struct remote_state *remote_state = repo->remote_state;
1907+
const char *check_url;
1908+
1909+
if (remote->origin != REMOTE_UNCONFIGURED)
1910+
return remote;
1911+
1912+
push_urls = push_url_of_remote(remote);
1913+
if (push_urls->nr != 1)
1914+
return remote;
1915+
check_url = push_urls->v[0];
1916+
1917+
for (int i = 0; i < remote_state->remotes_nr; i++) {
1918+
struct remote *candidate = remote_state->remotes[i];
1919+
1920+
if (!candidate || candidate == remote ||
1921+
!remote_is_configured(candidate, 0) ||
1922+
!remote_has_push_url(candidate, check_url))
1923+
continue;
1924+
if (first_match)
1925+
return remote;
1926+
first_match = candidate;
1927+
}
1928+
1929+
return first_match ? first_match : remote;
1930+
}
1931+
1932+
static char *tracking_for_push_dest(struct repository *repo,
1933+
struct remote *remote,
18911934
const char *refname,
18921935
struct strbuf *err)
18931936
{
18941937
char *ret;
18951938

1939+
remote = repo_remote_for_push_tracking(repo, remote);
18961940
ret = apply_refspecs(&remote->fetch, refname);
18971941
if (!ret)
18981942
return error_buf(err,
@@ -1925,21 +1969,21 @@ static char *branch_get_push_1(struct repository *repo,
19251969
_("push refspecs for '%s' do not include '%s'"),
19261970
remote->name, branch->name);
19271971

1928-
ret = tracking_for_push_dest(remote, dst, err);
1972+
ret = tracking_for_push_dest(repo, remote, dst, err);
19291973
free(dst);
19301974
return ret;
19311975
}
19321976

19331977
if (remote->mirror)
1934-
return tracking_for_push_dest(remote, branch->refname, err);
1978+
return tracking_for_push_dest(repo, remote, branch->refname, err);
19351979

19361980
switch (repo_config_values(repo)->push_default) {
19371981
case PUSH_DEFAULT_NOTHING:
19381982
return error_buf(err, _("push has no destination (push.default is 'nothing')"));
19391983

19401984
case PUSH_DEFAULT_MATCHING:
19411985
case PUSH_DEFAULT_CURRENT:
1942-
return tracking_for_push_dest(remote, branch->refname, err);
1986+
return tracking_for_push_dest(repo, remote, branch->refname, err);
19431987

19441988
case PUSH_DEFAULT_UPSTREAM:
19451989
return xstrdup_or_null(branch_get_upstream(branch, err));
@@ -1953,7 +1997,7 @@ static char *branch_get_push_1(struct repository *repo,
19531997
up = branch_get_upstream(branch, err);
19541998
if (!up)
19551999
return NULL;
1956-
cur = tracking_for_push_dest(remote, branch->refname, err);
2000+
cur = tracking_for_push_dest(repo, remote, branch->refname, err);
19572001
if (!cur)
19582002
return NULL;
19592003
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: 144 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="file://$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,128 @@ 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 matching 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 'configured pushurl makes URL-valued pushRemote trackable' '
1075+
setup_url_pushremote &&
1076+
1077+
(
1078+
cd client &&
1079+
git remote rename origin upstream &&
1080+
git remote add -f origin ../fork.git &&
1081+
git remote set-url --push origin "$fork_url"
1082+
) &&
1083+
1084+
check_status <<-EOF
1085+
On branch topic
1086+
Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
1087+
1088+
Your branch is up to date with ${SQ}origin/topic${SQ}.
1089+
1090+
nothing to commit, working tree clean
1091+
EOF
1092+
'
1093+
1094+
test_expect_success 'pushInsteadOf URL pushRemote is trackable' '
1095+
setup_url_pushremote &&
1096+
(
1097+
cd client &&
1098+
git remote rename origin upstream &&
1099+
git remote add -f origin "$fork_url" &&
1100+
git config "url.$fork_url.pushInsteadOf" fork: &&
1101+
git config branch.topic.pushRemote fork:
1102+
) &&
1103+
1104+
check_status <<-EOF
1105+
On branch topic
1106+
Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
1107+
1108+
Your branch is up to date with ${SQ}origin/topic${SQ}.
1109+
1110+
nothing to commit, working tree clean
1111+
EOF
1112+
'
1113+
1114+
test_expect_success 'up-to-date URL push refreshes stale tracking branch' '
1115+
setup_url_pushremote &&
1116+
(
1117+
cd client &&
1118+
git remote rename origin upstream &&
1119+
git remote add -f origin "$fork_url" &&
1120+
git commit --allow-empty -m another-topic-change &&
1121+
git -C ../fork.git fetch ../client topic:topic
1122+
) &&
1123+
1124+
check_status <<-EOF &&
1125+
On branch topic
1126+
Your branch is ahead of ${SQ}upstream/main${SQ} by 2 commits.
1127+
1128+
Your branch is ahead of ${SQ}origin/topic${SQ} by 1 commit.
1129+
(use "git push" to publish your local commits)
1130+
1131+
nothing to commit, working tree clean
1132+
EOF
1133+
1134+
git -C client push >actual 2>&1 &&
1135+
test_grep "Everything up-to-date" actual &&
1136+
1137+
check_status <<-EOF
1138+
On branch topic
1139+
Your branch is ahead of ${SQ}upstream/main${SQ} by 2 commits.
1140+
1141+
Your branch is up to date with ${SQ}origin/topic${SQ}.
1142+
1143+
nothing to commit, working tree clean
1144+
EOF
1145+
'
1146+
1147+
test_expect_success 'duplicate remote URL leaves URL-valued pushRemote ambiguous' '
1148+
setup_url_pushremote &&
1149+
(
1150+
cd client &&
1151+
git remote rename origin upstream &&
1152+
git remote add -f origin "$fork_url" &&
1153+
git remote add duplicate "$fork_url"
1154+
) &&
1155+
1156+
check_status <<-EOF
1157+
On branch topic
1158+
Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
1159+
(use "git push" to publish your local commits)
1160+
1161+
nothing to commit, working tree clean
1162+
EOF
1163+
'
1164+
10211165
test_expect_success 'rename handles remote without fetch refspec' '
10221166
git clone --bare one no-refspec.git &&
10231167
# 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)