diff options
| -rw-r--r-- | bitbake/lib/bb/fetch2/git.py | 45 | ||||
| -rw-r--r-- | bitbake/lib/bb/tests/fetch.py | 51 |
2 files changed, 90 insertions, 6 deletions
diff --git a/bitbake/lib/bb/fetch2/git.py b/bitbake/lib/bb/fetch2/git.py index 4d6e57ade7..9ecc855af8 100644 --- a/bitbake/lib/bb/fetch2/git.py +++ b/bitbake/lib/bb/fetch2/git.py | |||
| @@ -307,7 +307,10 @@ class Git(FetchMethod): | |||
| 307 | return ud.clonedir | 307 | return ud.clonedir |
| 308 | 308 | ||
| 309 | def need_update(self, ud, d): | 309 | def need_update(self, ud, d): |
| 310 | return self.clonedir_need_update(ud, d) or self.shallow_tarball_need_update(ud) or self.tarball_need_update(ud) | 310 | return self.clonedir_need_update(ud, d) \ |
| 311 | or self.shallow_tarball_need_update(ud) \ | ||
| 312 | or self.tarball_need_update(ud) \ | ||
| 313 | or self.lfs_need_update(ud, d) | ||
| 311 | 314 | ||
| 312 | def clonedir_need_update(self, ud, d): | 315 | def clonedir_need_update(self, ud, d): |
| 313 | if not os.path.exists(ud.clonedir): | 316 | if not os.path.exists(ud.clonedir): |
| @@ -319,6 +322,15 @@ class Git(FetchMethod): | |||
| 319 | return True | 322 | return True |
| 320 | return False | 323 | return False |
| 321 | 324 | ||
| 325 | def lfs_need_update(self, ud, d): | ||
| 326 | if self.clonedir_need_update(ud, d): | ||
| 327 | return True | ||
| 328 | |||
| 329 | for name in ud.names: | ||
| 330 | if not self._lfs_objects_downloaded(ud, d, name, ud.clonedir): | ||
| 331 | return True | ||
| 332 | return False | ||
| 333 | |||
| 322 | def clonedir_need_shallow_revs(self, ud, d): | 334 | def clonedir_need_shallow_revs(self, ud, d): |
| 323 | for rev in ud.shallow_revs: | 335 | for rev in ud.shallow_revs: |
| 324 | try: | 336 | try: |
| @@ -406,7 +418,7 @@ class Git(FetchMethod): | |||
| 406 | if missing_rev: | 418 | if missing_rev: |
| 407 | raise bb.fetch2.FetchError("Unable to find revision %s even from upstream" % missing_rev) | 419 | raise bb.fetch2.FetchError("Unable to find revision %s even from upstream" % missing_rev) |
| 408 | 420 | ||
| 409 | if self._contains_lfs(ud, d, ud.clonedir) and self._need_lfs(ud): | 421 | if self.lfs_need_update(ud, d): |
| 410 | # Unpack temporary working copy, use it to run 'git checkout' to force pre-fetching | 422 | # Unpack temporary working copy, use it to run 'git checkout' to force pre-fetching |
| 411 | # of all LFS blobs needed at the srcrev. | 423 | # of all LFS blobs needed at the srcrev. |
| 412 | # | 424 | # |
| @@ -649,6 +661,35 @@ class Git(FetchMethod): | |||
| 649 | raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output)) | 661 | raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output)) |
| 650 | return output.split()[0] != "0" | 662 | return output.split()[0] != "0" |
| 651 | 663 | ||
| 664 | def _lfs_objects_downloaded(self, ud, d, name, wd): | ||
| 665 | """ | ||
| 666 | Verifies whether the LFS objects for requested revisions have already been downloaded | ||
| 667 | """ | ||
| 668 | # Bail out early if this repository doesn't use LFS | ||
| 669 | if not self._need_lfs(ud) or not self._contains_lfs(ud, d, wd): | ||
| 670 | return True | ||
| 671 | |||
| 672 | # The Git LFS specification specifies ([1]) the LFS folder layout so it should be safe to check for file | ||
| 673 | # existence. | ||
| 674 | # [1] https://github.com/git-lfs/git-lfs/blob/main/docs/spec.md#intercepting-git | ||
| 675 | cmd = "%s lfs ls-files -l %s" \ | ||
| 676 | % (ud.basecmd, ud.revisions[name]) | ||
| 677 | output = runfetchcmd(cmd, d, quiet=True, workdir=wd).rstrip() | ||
| 678 | # Do not do any further matching if no objects are managed by LFS | ||
| 679 | if not output: | ||
| 680 | return True | ||
| 681 | |||
| 682 | # Match all lines beginning with the hexadecimal OID | ||
| 683 | oid_regex = re.compile("^(([a-fA-F0-9]{2})([a-fA-F0-9]{2})[A-Fa-f0-9]+)") | ||
| 684 | for line in output.split("\n"): | ||
| 685 | oid = re.search(oid_regex, line) | ||
| 686 | if not oid: | ||
| 687 | bb.warn("git lfs ls-files output '%s' did not match expected format." % line) | ||
| 688 | if not os.path.exists(os.path.join(wd, "lfs", "objects", oid.group(2), oid.group(3), oid.group(1))): | ||
| 689 | return False | ||
| 690 | |||
| 691 | return True | ||
| 692 | |||
| 652 | def _need_lfs(self, ud): | 693 | def _need_lfs(self, ud): |
| 653 | return ud.parm.get("lfs", "1") == "1" | 694 | return ud.parm.get("lfs", "1") == "1" |
| 654 | 695 | ||
diff --git a/bitbake/lib/bb/tests/fetch.py b/bitbake/lib/bb/tests/fetch.py index 847a35602d..5aa3e464dd 100644 --- a/bitbake/lib/bb/tests/fetch.py +++ b/bitbake/lib/bb/tests/fetch.py | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | # SPDX-License-Identifier: GPL-2.0-only | 6 | # SPDX-License-Identifier: GPL-2.0-only |
| 7 | # | 7 | # |
| 8 | 8 | ||
| 9 | import contextlib | ||
| 9 | import unittest | 10 | import unittest |
| 10 | import hashlib | 11 | import hashlib |
| 11 | import tempfile | 12 | import tempfile |
| @@ -2182,10 +2183,14 @@ class GitLfsTest(FetcherTest): | |||
| 2182 | 2183 | ||
| 2183 | bb.utils.mkdirhier(self.srcdir) | 2184 | bb.utils.mkdirhier(self.srcdir) |
| 2184 | self.git_init(cwd=self.srcdir) | 2185 | self.git_init(cwd=self.srcdir) |
| 2185 | with open(os.path.join(self.srcdir, '.gitattributes'), 'wt') as attrs: | 2186 | self.commit_file('.gitattributes', '*.mp3 filter=lfs -text') |
| 2186 | attrs.write('*.mp3 filter=lfs -text') | 2187 | |
| 2187 | self.git(['add', '.gitattributes'], cwd=self.srcdir) | 2188 | def commit_file(self, filename, content): |
| 2188 | self.git(['commit', '-m', "attributes", '.gitattributes'], cwd=self.srcdir) | 2189 | with open(os.path.join(self.srcdir, filename), "w") as f: |
| 2190 | f.write(content) | ||
| 2191 | self.git(["add", filename], cwd=self.srcdir) | ||
| 2192 | self.git(["commit", "-m", "Change"], cwd=self.srcdir) | ||
| 2193 | return self.git(["rev-parse", "HEAD"], cwd=self.srcdir).strip() | ||
| 2189 | 2194 | ||
| 2190 | def fetch(self, uri=None, download=True): | 2195 | def fetch(self, uri=None, download=True): |
| 2191 | uris = self.d.getVar('SRC_URI').split() | 2196 | uris = self.d.getVar('SRC_URI').split() |
| @@ -2206,6 +2211,44 @@ class GitLfsTest(FetcherTest): | |||
| 2206 | return unpacked_lfs_file | 2211 | return unpacked_lfs_file |
| 2207 | 2212 | ||
| 2208 | @skipIfNoGitLFS() | 2213 | @skipIfNoGitLFS() |
| 2214 | def test_fetch_lfs_on_srcrev_change(self): | ||
| 2215 | """Test if fetch downloads missing LFS objects when a different revision within an existing repository is requested""" | ||
| 2216 | self.git(["lfs", "install", "--local"], cwd=self.srcdir) | ||
| 2217 | |||
| 2218 | @contextlib.contextmanager | ||
| 2219 | def hide_upstream_repository(): | ||
| 2220 | """Hide the upstream repository to make sure that git lfs cannot pull from it""" | ||
| 2221 | temp_name = self.srcdir + ".bak" | ||
| 2222 | os.rename(self.srcdir, temp_name) | ||
| 2223 | try: | ||
| 2224 | yield | ||
| 2225 | finally: | ||
| 2226 | os.rename(temp_name, self.srcdir) | ||
| 2227 | |||
| 2228 | def fetch_and_verify(revision, filename, content): | ||
| 2229 | self.d.setVar('SRCREV', revision) | ||
| 2230 | fetcher, ud = self.fetch() | ||
| 2231 | |||
| 2232 | with hide_upstream_repository(): | ||
| 2233 | workdir = self.d.getVar('WORKDIR') | ||
| 2234 | fetcher.unpack(workdir) | ||
| 2235 | |||
| 2236 | with open(os.path.join(workdir, "git", filename)) as f: | ||
| 2237 | self.assertEqual(f.read(), content) | ||
| 2238 | |||
| 2239 | commit_1 = self.commit_file("a.mp3", "version 1") | ||
| 2240 | commit_2 = self.commit_file("a.mp3", "version 2") | ||
| 2241 | |||
| 2242 | self.d.setVar('SRC_URI', "git://%s;protocol=file;lfs=1;branch=master" % self.srcdir) | ||
| 2243 | |||
| 2244 | # Seed the local download folder by fetching the latest commit and verifying that the LFS contents are | ||
| 2245 | # available even when the upstream repository disappears. | ||
| 2246 | fetch_and_verify(commit_2, "a.mp3", "version 2") | ||
| 2247 | # Verify that even when an older revision is fetched, the needed LFS objects are fetched into the download | ||
| 2248 | # folder. | ||
| 2249 | fetch_and_verify(commit_1, "a.mp3", "version 1") | ||
| 2250 | |||
| 2251 | @skipIfNoGitLFS() | ||
| 2209 | @skipIfNoNetwork() | 2252 | @skipIfNoNetwork() |
| 2210 | def test_real_git_lfs_repo_succeeds_without_lfs_param(self): | 2253 | def test_real_git_lfs_repo_succeeds_without_lfs_param(self): |
| 2211 | self.d.setVar('SRC_URI', "git://gitlab.com/gitlab-examples/lfs.git;protocol=https;branch=master") | 2254 | self.d.setVar('SRC_URI', "git://gitlab.com/gitlab-examples/lfs.git;protocol=https;branch=master") |
