diff mbox series

fetch2/git: replace stale clones when using premirrors

Message ID DM6PR11MB396228CA1F541EEEB9C9F04CCBA02@DM6PR11MB3962.namprd11.prod.outlook.com
State New
Headers show
Series fetch2/git: replace stale clones when using premirrors | expand

Commit Message

Fredrik Svensson (svsvenss) Aug. 24, 2026, 11:14 a.m. UTC
A successful premirror fetch can leave an existing clone at the original
download path even when it lacks SRCREV. The fetch is then stamped
complete before unpack rejects the stale clone.

Pass the datastore to update_mirror_links() and remove the original
clone only when the Git validity check says it needs updating, allowing
the successful mirror to be linked.

The new self-test creates a stale clone at the original download path
and a local premirror containing a newer SRCREV. Without the fix it
reproduces the UnpackError; with the fix it verifies that the mirror is
linked and the requested revision is unpacked.

Tested with bitbake-selftest on Python 3.11, 3.12, 3.13, and 3.14.

AI-Generated: OpenAI Codex
---
 lib/bb/fetch2/__init__.py |  4 ++--
 lib/bb/fetch2/git.py      |  8 ++++++--
 lib/bb/tests/fetch.py     | 42 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 50 insertions(+), 4 deletions(-)
diff mbox series

Patch

diff --git a/lib/bb/fetch2/__init__.py b/lib/bb/fetch2/__init__.py
index 9cb268be..68a0225f 100644
--- a/lib/bb/fetch2/__init__.py
+++ b/lib/bb/fetch2/__init__.py
@@ -1150,7 +1150,7 @@  def try_mirror_url(fetch, origud, ud, ld, check = False):
         # This may also be a link to a shallow archive
         # When using shallow mode, add a symlink to the original fullshallow
         # path to ensure a valid symlink even in the `PREMIRRORS` case
-        origud.method.update_mirror_links(ud, origud)
+        origud.method.update_mirror_links(ud, origud, ld)
         update_stamp(origud, ld)
         return ud.localpath
 
@@ -1692,7 +1692,7 @@  class FetchMethod(object):
             except FileExistsError:
                 pass
 
-    def update_mirror_links(self, ud, origud):
+    def update_mirror_links(self, ud, origud, d):
         # For local file:// results, create a symlink to them
         # This may also be a link to a shallow archive
         self.ensure_symlink(ud.localpath, origud.localpath)
diff --git a/lib/bb/fetch2/git.py b/lib/bb/fetch2/git.py
index 0bb2ea39..a1f9cb33 100644
--- a/lib/bb/fetch2/git.py
+++ b/lib/bb/fetch2/git.py
@@ -353,8 +353,12 @@  class Git(FetchMethod):
     def tarball_need_update(self, ud):
         return ud.write_tarballs and not os.path.exists(ud.fullmirror)
 
-    def update_mirror_links(self, ud, origud):
-        super().update_mirror_links(ud, origud)
+    def update_mirror_links(self, ud, origud, d):
+        # Replace a stale clone which would prevent linking the successful mirror.
+        if self.clonedir_need_update(origud, d) and os.path.exists(origud.localpath) \
+                and not os.path.samefile(ud.localpath, origud.localpath):
+            bb.utils.remove(origud.localpath, recurse=True)
+        super().update_mirror_links(ud, origud, d)
         # When using shallow mode, add a symlink to the original fullshallow
         # path to ensure a valid symlink even in the `PREMIRRORS` case
         if origud.shallow and not os.path.exists(origud.fullshallow):
diff --git a/lib/bb/tests/fetch.py b/lib/bb/tests/fetch.py
index a1e4b45f..65483570 100644
--- a/lib/bb/tests/fetch.py
+++ b/lib/bb/tests/fetch.py
@@ -616,6 +616,48 @@  class GitDownloadDirectoryNamingTest(FetcherTest):
         self.assertIn(self.recipe_dir, dir)
 
 
+class GitPremirrorStaleCloneTest(FetcherTest):
+
+    def test_premirror_replaces_stale_clone(self):
+        source = os.path.join(self.tempdir, "source")
+        os.mkdir(source)
+        self.git_init(cwd=source)
+
+        testfile = os.path.join(source, "testfile")
+        with open(testfile, "w") as f:
+            f.write("first\n")
+        self.git(["add", "testfile"], cwd=source)
+        self.git(["commit", "-m", "first commit"], cwd=source)
+
+        recipe_url = "git://upstream.invalid/project.git;branch=master;protocol=https"
+        self.d.setVar("SRCREV", self.git(["rev-parse", "HEAD"], cwd=source).strip())
+        fetcher = bb.fetch.Fetch([recipe_url], self.d)
+        ud = fetcher.ud[recipe_url]
+        ud.setup_localpath(self.d)
+        os.makedirs(os.path.dirname(ud.clonedir), exist_ok=True)
+        self.git(["clone", "--bare", "--mirror", source, ud.clonedir], cwd=self.tempdir)
+
+        with open(testfile, "a") as f:
+            f.write("second\n")
+        self.git(["commit", "-am", "second commit"], cwd=source)
+        revision = self.git(["rev-parse", "HEAD"], cwd=source).strip()
+
+        self.d.setVar("SRCREV", revision)
+        self.d.setVar("BB_FETCH_PREMIRRORONLY", "1")
+        self.d.setVar("BB_NO_NETWORK", "1")
+        self.d.setVar("PREMIRRORS", "%s git://%s;protocol=file" % (recipe_url, source))
+
+        fetcher = bb.fetch.Fetch([recipe_url], self.d)
+        ud = fetcher.ud[recipe_url]
+        fetcher.download()
+        fetcher.unpack(self.unpackdir)
+
+        self.assertTrue(os.path.islink(ud.clonedir))
+        unpack_revision = self.git(["rev-parse", "HEAD"],
+                                   cwd=os.path.join(self.unpackdir, "git")).strip()
+        self.assertEqual(revision, unpack_revision)
+
+
 class TarballNamingTest(FetcherTest):
     def setUp(self):
         super(TarballNamingTest, self).setUp()