feat: follow a fork's base as its upstream head

For a port that is a GitHub fork of its upstream, the upstream code it
carries is the merge base of the two, not upstream's HEAD. The bump moved
upstream_commit to upstream HEAD on snes9x, kronos, np2kai and others
whose pin was that base. The upstream view's head is now the merge base
GitHub's compare names, the way _follow_submodule already reads a
vendored submodule. Outside a fork network the HEAD stays.
This commit is contained in:
Abdessamad Derraz committed 2026-10-23 16:25:56 +02:00
1 parent 01a6daf653
commit 861b89dd44
4 files changed
+116 -1

No files matched your search

+29 -1
View File
@@ -1104,7 +1104,7 @@ def select_views(
continue
seen.add((repo.host, repo.slug))
views.append(RepoView(repo, pin, head, origin, field))
return _follow_submodule(views, cache_dir, offline)
return _follow_fork_base(_follow_submodule(views, cache_dir, offline), cache_dir, offline)
def _follow_submodule(
@@ -1144,6 +1144,34 @@ def _follow_submodule(
return followed
def _follow_fork_base(
views: list[RepoView], cache_dir: str, offline: bool
) -> list[RepoView]:
"""An upstream the port is a fork of moves with what the port merged.
snes9x, kronos, np2kai and six others pinned upstream_commit on the merge
base of upstream with the fork, the upstream code the port carries. Read
against upstream's own HEAD, the bump moved it past anything the port
built from. Outside one fork network the HEAD stays the reference.
"""
source = next((v for v in views if v.field == "source"), None)
if source is None:
return views
followed: list[RepoView] = []
for view in views:
if view.field != "upstream" or view.pin == view.head or " via " in view.origin:
followed.append(view)
continue
base = upstream.fork_base(
view.repo, view.head, source.repo, source.head, cache_dir, offline
)
followed.append(
replace(view, head=base, origin=f"{view.origin}, fork base of {source.repo.slug}")
if base else view
)
return followed
def declared_repositories(profile: dict) -> list[tuple[str, str, str]]:
"""Every repository URL the profile declares, source before upstream.
+20
View File
@@ -840,6 +840,26 @@ def _commits_ahead(payload: object, family: str) -> int | None:
return value if isinstance(value, int) else None
def fork_base(
repo: Repo, sha: str, fork: Repo, fork_sha: str, cache_dir: str,
offline: bool = False,
) -> str | None:
"""The commit of ``repo`` that ``fork`` at ``fork_sha`` has merged up to.
GitHub compares across a fork network and names the merge base. Two
repositories outside one network answer 404, and None is returned.
"""
if repo.family != "github" or fork.family != "github":
return None
payload = _api(
f"{repo.api_base}/repos/{repo.slug}/compare/"
f"{sha}...{fork.owner}:{fork.name}:{fork_sha}",
cache_dir, offline,
)
base = payload.get("merge_base_commit") if isinstance(payload, dict) else None
return base.get("sha") if isinstance(base, dict) else None
def ancestry(
repo: Repo, base: str, head: str, cache_dir: str, offline: bool = False
) -> tuple[int, int] | None:
+40
View File
@@ -1181,6 +1181,12 @@ class TestBuildReport(unittest.TestCase):
profile_sync.upstream._http_text,
profile_sync.upstream.list_gitlinks,
profile_sync.upstream.current_slug,
profile_sync.upstream.fork_base,
)
self.fork_bases: dict[str, str] = {}
profile_sync.upstream.fork_base = (
lambda repo, sha, fork, fork_sha, cache_dir, offline=False:
self.fork_bases.get(fork_sha)
)
self.current_slugs: dict[str, str] = {}
profile_sync.upstream.current_slug = (
@@ -1243,6 +1249,7 @@ class TestBuildReport(unittest.TestCase):
profile_sync.upstream._http_text,
profile_sync.upstream.list_gitlinks,
profile_sync.upstream.current_slug,
profile_sync.upstream.fork_base,
) = self._orig
self.tmp.cleanup()
@@ -4789,6 +4796,39 @@ class UpstreamVendoredAsASubmodule(unittest.TestCase):
self.assertEqual(views[1].head, "nesbox-tip")
class UpstreamForkedByThePort(unittest.TestCase):
"""snes9x, kronos and np2kai pinned upstream_commit on the merge base of
upstream with the fork. Compared to upstream's own HEAD, a bump moved it
past anything the port carries."""
def _views(self, base):
from unittest import mock # noqa: PLC0415
fork = profile_sync.upstream.make_repo("github.com", "f", "port")
self.up = profile_sync.upstream.make_repo("github.com", "o", "up")
self.fork = fork
views = [
profile_sync.RepoView(fork, "src-pin", "src-head", "source_commit", "source"),
profile_sync.RepoView(self.up, "old-base", "up-tip", "upstream_commit", "upstream"),
]
with mock.patch.object(
profile_sync.upstream, "fork_base", return_value=base
) as asked:
followed = profile_sync._follow_fork_base(views, "cache", True)
return followed, asked
def test_the_upstream_head_is_the_fork_base(self):
views, asked = self._views("new-base")
self.assertEqual(views[1].head, "new-base")
self.assertEqual(
asked.call_args.args[:4], (self.up, "up-tip", self.fork, "src-head")
)
def test_a_port_outside_the_network_keeps_the_tip(self):
views, _ = self._views(None)
self.assertEqual(views[1].head, "up-tip")
class OfflineCacheMissIsNotGone(TestBuildReport):
"""Offline, a file the cache never stored came back as None and the
report read "GONE (absent at HEAD, no rename found)" for files that
+27
View File
@@ -957,5 +957,32 @@ class TestNamedTagCommit(unittest.TestCase):
self.assertEqual(upstream.tag_commit(repo, "1.2.35", self.tmp.name), "c74fc36")
self.assertTrue(asked[0].endswith("/repository/tags/1.2.35"))
class TestForkBase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.saved = upstream._http_json
def tearDown(self):
upstream._http_json = self.saved
self.tmp.cleanup()
def test_the_merge_base_github_names(self):
asked = []
def answer(url):
asked.append(url)
return {"merge_base_commit": {"sha": "base"}, "status": "diverged"}
upstream._http_json = answer
up, fork = make_repo("github.com", "o", "up"), make_repo("github.com", "f", "port")
self.assertEqual(upstream.fork_base(up, "tip", fork, "head", self.tmp.name), "base")
self.assertTrue(asked[0].endswith("/repos/o/up/compare/tip...f:port:head"))
def test_no_network_is_none(self):
upstream._http_json = lambda url: None
up, fork = make_repo("github.com", "o", "up"), make_repo("github.com", "f", "port")
self.assertIsNone(upstream.fork_base(up, "tip", fork, "head", self.tmp.name))
if __name__ == "__main__":
unittest.main()