[PATCH] fetch2/git: replace stale clones when using premirrors

"Fredrik Svensson (svsvenss)" <[email protected]>
Newsgroups org.openembedded.lists.bitbake-devel
Message-ID <DM6PR11MB396228CA1F541EEEB9C9F04CCBA02@DM6PR11MB3962.namprd11.prod.outlook.com>
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 --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()
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.