@@ -485,6 +485,10 @@ def uri_replace(ud, uri_find, uri_replace, replacements, d, mirrortarball=None):
# User/password in the replacement is just a straight replacement
result_decoded[loc] = uri_replace_decoded[loc]
elif (re.match(regexp, uri_decoded[loc])):
+ # Snapshot the replacement template before placeholder substitution
+ # so we can later detect whether the user explicitly asked for the
+ # source PATH to be preserved (YOCTO #14156).
+ replace_template = uri_replace_decoded[loc] if loc == 2 else None
if not uri_replace_decoded[loc]:
result_decoded[loc] = ""
else:
@@ -495,7 +499,23 @@ def uri_replace(ud, uri_find, uri_replace, replacements, d, mirrortarball=None):
if loc == 2:
# Handle path manipulations
basename = None
- if uri_decoded[0] != uri_replace_decoded[0] and mirrortarball:
+ # If the replacement template references the PATH placeholder
+ # the user has explicitly asked for the source path to be
+ # preserved in the rewritten URL (e.g. a cross-scheme PREMIRROR
+ # redirect to a checkout location, not a tarball mirror). In
+ # that case, suppress the mirrortarball-basename override and
+ # carry the source URL parameters through (YOCTO #14156).
+ # The substring check is on the pre-substitution template, so a
+ # source URL whose path coincidentally contains "PATH" will not
+ # trigger a false positive. Cross-scheme rewrites that rely on
+ # an absolute literal target path (no PATH placeholder) still
+ # take the mirrortarball-basename path; widening to those is
+ # left for a follow-up if a real-world rule turns up.
+ user_path_explicit = (replace_template is not None
+ and "PATH" in replace_template)
+ if (uri_decoded[0] != uri_replace_decoded[0]
+ and mirrortarball
+ and not user_path_explicit):
# If the source and destination url types differ, must be a mirrortarball mapping
basename = os.path.basename(mirrortarball)
# Kill parameters, they make no sense for mirror tarballs
@@ -566,6 +566,27 @@ class MirrorUriTest(FetcherTest):
'https://bbbb/B/B/B/bitbake/bitbake-1.0.tar.gz',
'http://aaaa/A/A/A/B/B/bitbake/bitbake-1.0.tar.gz'])
+ def test_gitsm_premirror_path_placeholder(self):
+ # YOCTO #14156: when a PREMIRROR rewrites a gitsm:// URL to a
+ # different scheme but uses the HOST/PATH placeholders to redirect
+ # to a checkout location (not a tarball), the rewritten URL must
+ # land on the user-specified checkout path, not on the canonical
+ # mirror-tarball filename. niqingliang2003 reported (2025-07-18) a
+ # case where bitbake produced
+ # 'git:///mnt/datum/repositories/github.com/libjxl/git2_github.com.libjxl.libjxl.git.tar.gz'
+ # for a PREMIRROR rule of
+ # 'gitsm://.*/.* git:///mnt/datum/repositories/HOST/PATH;protocol=file',
+ # which is unfetchable.
+ src = "gitsm://github.com/libjxl/libjxl.git;protocol=https;nobranch=1;rev=" + ("1" * 40)
+ find = "gitsm://.*/.*"
+ replace = "git:///mnt/datum/repositories/HOST/PATH;protocol=file"
+ expected = "git:///mnt/datum/repositories/github.com/libjxl/libjxl.git;protocol=file;nobranch=1;rev=" + ("1" * 40)
+ ud = bb.fetch.FetchData(src, self.d)
+ ud.setup_localpath(self.d)
+ mirrors = bb.fetch2.mirror_from_string("%s %s" % (find, replace))
+ newuris, _ = bb.fetch2.build_mirroruris(ud, mirrors, self.d)
+ self.assertEqual([expected], newuris)
+
class GitDownloadDirectoryNamingTest(FetcherTest):
def setUp(self):
When a PREMIRROR rule rewrites a fetch URL to a different scheme (typically gitsm:// -> git:// for an on-disk checkout), uri_replace() unconditionally treats the rewrite as a mirrortarball mapping: it substitutes the source URL's path basename with the canonical mirror tarball name (git2_<host>.<repo>.tar.gz) and drops the source URL parameters. That is correct for tarball mirrors but breaks the case where the user redirects to a checkout location using HOST/PATH placeholders. niqingliang2003 reported this on YOCTO #14156 (2025-07-18) with a PREMIRROR of: gitsm://.*/.* git:///mnt/datum/repositories/HOST/PATH;protocol=file For the input: gitsm://github.com/libjxl/libjxl.git;protocol=https;nobranch=1 the rewrite produces: git:///mnt/datum/repositories/github.com/libjxl/git2_github.com.libjxl.libjxl.git.tar.gz;protocol=file instead of the user-intended: git:///mnt/datum/repositories/github.com/libjxl/libjxl.git;protocol=file;nobranch=1 The mirror fetch then fails because the resulting URL has no nobranch or branch parameter and no actual tarball exists at the path. When the replacement template references the PATH placeholder, the user has explicitly asked for the source path to appear in the rewritten URL. In that case, skip the mirrortarball-basename override and carry the source URL parameters through. Existing PREMIRROR rules that do not use PATH (the canonical tarball-mirror layout) continue to work unchanged. A new selftest, MirrorUriTest.test_gitsm_premirror_path_placeholder, captures the exact niqingliang2003 repro shape so a regression in this area becomes a unit-level failure rather than a runtime mirror fetch error. The dict-driven test_urireplace and the broader GitDownloadDirectoryNamingTest / TarballNamingTest / GitShallowTarballNamingTest / CleanTarballTest / FetcherLocalTest / FetcherNoNetworkTest suites all stay green with this change (42 of 42 relevant fetch tests pass on master + this patch; network-only tests not run). Note for reviewers: a wider bitbake-selftest run shows 4 pre-existing GitShallowTest errors triggered by local git environment (commit.gpgsign + empty editor config); they reproduce on the parent commit and are not related to this change. Signed-off-by: Jhonata Poma-Hansen <jhonata.poma@gmail.com> --- lib/bb/fetch2/__init__.py | 22 +++++++++++++++++++++- lib/bb/tests/fetch.py | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) --- base-commit: df3295f016a74a1414b8af5adb9f2a3967c365b6 change-id: 20260510-b4-yocto-14156-044749482472 Best regards,