Skip to content

Commit 86737ad

Browse files
committed
keep old links working for renamed default branches
If 'master' or 'main' fail to resolve, try again with 'HEAD' after a delay this keeps old links to renamed default branches working for the big master->main migration
1 parent bd957db commit 86737ad

File tree

4 files changed

+44
-15
lines changed

4 files changed

+44
-15
lines changed

binderhub/builder.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Handlers for working with version control services (i.e. GitHub) for builds.
33
"""
44

5+
import asyncio
56
import hashlib
67
import json
78
import re
@@ -303,16 +304,43 @@ async def get(self, provider_prefix, _unescaped_spec):
303304
'GitHub recently changed default branches from "master" to "main".'
304305
)
305306

306-
if provider.unresolved_ref == "master":
307-
error_message.append('Did you mean the "main" branch?')
308-
elif provider.unresolved_ref == "main":
309-
error_message.append('Did you mean the "master" branch?')
307+
if provider.unresolved_ref in {"master", "main"}:
308+
error_message.append(
309+
"Tip: HEAD will always resolve to a repo's default branch."
310+
)
311+
312+
# keep old links working for default branch names
313+
# by substituting 'master' or 'main' with 'HEAD'
314+
pre_ref_spec, _ = spec.rsplit("/", 1)
315+
spec = f"{pre_ref_spec}/HEAD"
316+
unresolved_ref = provider.unresolved_ref
317+
try:
318+
provider = self.get_provider(provider_prefix, spec=spec)
319+
ref = await provider.get_resolved_ref()
320+
except Exception as e:
321+
# if this fails, leave ref as None, which will fail below
322+
self.log.error(f"Error redirecting {key} to HEAD: {e}")
323+
else:
324+
# delayed redirect for deleted default branches
325+
await self.emit(
326+
{
327+
"phase": "waiting",
328+
"message": (
329+
" ".join(error_message) + "\n"
330+
f"Trying again with HEAD instead of {unresolved_ref}. Please update your links.\n"
331+
),
332+
}
333+
)
334+
# artificial delay for what should be broken links
335+
await asyncio.sleep(10)
310336

311337
else:
312338
error_message.append("Is your repo public?")
313339

314-
await self.fail(" ".join(error_message))
315-
return
340+
if ref is None:
341+
# ref can become non-None if redirected to HEAD
342+
await self.fail(" ".join(error_message))
343+
return
316344

317345
self.ref_url = await provider.get_resolved_ref_url()
318346
resolved_spec = await provider.get_resolved_spec()

binderhub/repoproviders.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ def tokenize_spec(spec):
3838
spec_parts = spec.split("/", 2) # allow ref to contain "/"
3939
if len(spec_parts) != 3:
4040
msg = f'Spec is not of the form "user/repo/ref", provided: "{spec}".'
41-
if len(spec_parts) == 2 and spec_parts[-1] not in ("main", "master"):
42-
msg += f' Did you mean "{spec}/main"?'
41+
if len(spec_parts) == 2 and spec_parts[-1] not in {"main", "master", "HEAD"}:
42+
msg += f' Did you mean "{spec}/HEAD"?'
4343
raise ValueError(msg)
4444

4545
return spec_parts
@@ -564,7 +564,7 @@ class GitLabRepoProvider(RepoProvider):
564564
<url-escaped-namespace>/<unresolved_ref>
565565
566566
eg:
567-
group%2Fproject%2Frepo/master
567+
group%2Fproject%2Frepo/main
568568
"""
569569

570570
name = Unicode("GitLab")
@@ -956,10 +956,9 @@ class GistRepoProvider(GitHubRepoProvider):
956956
957957
The ref is optional, valid values are
958958
- a full sha1 of a ref in the history
959-
- master
960-
- HEAD for the default branch
959+
- HEAD for the latest ref (also allow 'master', 'main' as aliases for HEAD)
961960
962-
If master or no ref is specified the latest revision will be used.
961+
If HEAD or no ref is specified the latest revision will be used.
963962
"""
964963

965964
name = Unicode("Gist")
@@ -1020,7 +1019,7 @@ async def get_resolved_ref(self):
10201019
)
10211020

10221021
all_versions = [e["version"] for e in ref_info["history"]]
1023-
if self.unresolved_ref in {"", "HEAD", "master"}:
1022+
if self.unresolved_ref in {"", "HEAD", "master", "main"}:
10241023
self.resolved_ref = all_versions[0]
10251024
else:
10261025
if self.unresolved_ref not in all_versions:

binderhub/tests/test_build.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
# gh/ GitHub repo provider
4242
"gh/binderhub-ci-repos/cached-minimal-dockerfile/HEAD",
4343
"gh/binderhub-ci-repos/cached-minimal-dockerfile/596b52f10efb0c9befc0c4ae850cc5175297d71c",
44+
# test redirect master->HEAD
45+
"gh/binderhub-ci-repos/cached-minimal-dockerfile/master",
4446
# gl/ GitLab repo provider
4547
"gl/binderhub-ci-repos%2Fcached-minimal-dockerfile/HEAD",
4648
"gl/binderhub-ci-repos%2Fcached-minimal-dockerfile/596b52f10efb0c9befc0c4ae850cc5175297d71c",

binderhub/tests/test_repoproviders.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,14 +360,14 @@ def test_long_spec(self):
360360
assert len(spec_parts) == 3
361361

362362
def test_spec_with_no_suggestion(self):
363-
spec = "short/master"
363+
spec = "short/HEAD"
364364
error = "^((?!Did you mean).)*$" # negative match
365365
with self.assertRaisesRegex(ValueError, error):
366366
user, repo, unresolved_ref = tokenize_spec(spec)
367367

368368
def test_spec_with_suggestion(self):
369369
spec = "short/suggestion"
370-
error = f'Did you mean "{spec}/main"?'
370+
error = f'Did you mean "{spec}/HEAD"?'
371371
with self.assertRaisesRegex(ValueError, error):
372372
user, repo, unresolved_ref = tokenize_spec(spec)
373373

0 commit comments

Comments
 (0)