blob: b43ee0da3961a1e036e2c8d6f2aa2e20330999b7 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001"""
2BitBake 'Fetch' git implementation
3
4git fetcher support the SRC_URI with format of:
5SRC_URI = "git://some.host/somepath;OptionA=xxx;OptionB=xxx;..."
6
7Supported SRC_URI options are:
8
9- branch
10 The git branch to retrieve from. The default is "master"
11
12 This option also supports multiple branch fetching, with branches
13 separated by commas. In multiple branches case, the name option
14 must have the same number of names to match the branches, which is
15 used to specify the SRC_REV for the branch
16 e.g:
17 SRC_URI="git://some.host/somepath;branch=branchX,branchY;name=nameX,nameY"
18 SRCREV_nameX = "xxxxxxxxxxxxxxxxxxxx"
19 SRCREV_nameY = "YYYYYYYYYYYYYYYYYYYY"
20
21- tag
22 The git tag to retrieve. The default is "master"
23
24- protocol
25 The method to use to access the repository. Common options are "git",
26 "http", "https", "file", "ssh" and "rsync". The default is "git".
27
28- rebaseable
29 rebaseable indicates that the upstream git repo may rebase in the future,
30 and current revision may disappear from upstream repo. This option will
31 remind fetcher to preserve local cache carefully for future use.
32 The default value is "0", set rebaseable=1 for rebaseable git repo.
33
34- nocheckout
35 Don't checkout source code when unpacking. set this option for the recipe
36 who has its own routine to checkout code.
37 The default is "0", set nocheckout=1 if needed.
38
39- bareclone
40 Create a bare clone of the source code and don't checkout the source code
41 when unpacking. Set this option for the recipe who has its own routine to
42 checkout code and tracking branch requirements.
43 The default is "0", set bareclone=1 if needed.
44
45- nobranch
46 Don't check the SHA validation for branch. set this option for the recipe
Andrew Geissler517393d2023-01-13 08:55:19 -060047 referring to commit which is valid in any namespace (branch, tag, ...)
48 instead of branch.
Patrick Williamsc124f4f2015-09-15 14:41:29 -050049 The default is "0", set nobranch=1 if needed.
50
Patrick Williamsac13d5f2023-11-24 18:59:46 -060051- subpath
52 Limit the checkout to a specific subpath of the tree.
53 By default, checkout the whole tree, set subpath=<path> if needed
54
55- destsuffix
56 The name of the path in which to place the checkout.
57 By default, the path is git/, set destsuffix=<suffix> if needed
58
Patrick Williamsc0f7c042017-02-23 20:41:17 -060059- usehead
Brad Bishop6e60e8b2018-02-01 10:27:11 -050060 For local git:// urls to use the current branch HEAD as the revision for use with
Patrick Williamsc0f7c042017-02-23 20:41:17 -060061 AUTOREV. Implies nobranch.
62
Patrick Williamsac13d5f2023-11-24 18:59:46 -060063- lfs
64 Enable the checkout to use LFS for large files. This will download all LFS files
65 in the download step, as the unpack step does not have network access.
66 The default is "1", set lfs=0 to skip.
67
Patrick Williamsc124f4f2015-09-15 14:41:29 -050068"""
69
Brad Bishopc342db32019-05-15 21:57:59 -040070# Copyright (C) 2005 Richard Purdie
Patrick Williamsc124f4f2015-09-15 14:41:29 -050071#
Brad Bishopc342db32019-05-15 21:57:59 -040072# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -050073#
Patrick Williamsc124f4f2015-09-15 14:41:29 -050074
Brad Bishopd7bf8c12018-02-25 22:55:05 -050075import collections
Patrick Williamsd7e96312015-09-22 08:09:05 -050076import errno
Brad Bishopd7bf8c12018-02-25 22:55:05 -050077import fnmatch
Patrick Williamsc124f4f2015-09-15 14:41:29 -050078import os
79import re
Andrew Geissler4c19ea12020-10-27 13:52:24 -050080import shlex
Andrew Geissler5082cc72023-09-11 08:41:39 -040081import shutil
Brad Bishopd7bf8c12018-02-25 22:55:05 -050082import subprocess
83import tempfile
Patrick Williamsc124f4f2015-09-15 14:41:29 -050084import bb
Patrick Williamsc0f7c042017-02-23 20:41:17 -060085import bb.progress
Andrew Geissler5199d832021-09-24 16:47:35 -050086from contextlib import contextmanager
Patrick Williamsc124f4f2015-09-15 14:41:29 -050087from bb.fetch2 import FetchMethod
88from bb.fetch2 import runfetchcmd
89from bb.fetch2 import logger
Patrick Williams73bd93f2024-02-20 08:07:48 -060090from bb.fetch2 import trusted_network
Patrick Williamsc124f4f2015-09-15 14:41:29 -050091
Patrick Williamsc0f7c042017-02-23 20:41:17 -060092
Patrick Williams03907ee2022-05-01 06:28:52 -050093sha1_re = re.compile(r'^[0-9a-f]{40}$')
94slash_re = re.compile(r"/+")
95
Patrick Williamsc0f7c042017-02-23 20:41:17 -060096class GitProgressHandler(bb.progress.LineFilterProgressHandler):
97 """Extract progress information from git output"""
98 def __init__(self, d):
99 self._buffer = ''
100 self._count = 0
101 super(GitProgressHandler, self).__init__(d)
102 # Send an initial progress event so the bar gets shown
103 self._fire_progress(-1)
104
105 def write(self, string):
106 self._buffer += string
107 stages = ['Counting objects', 'Compressing objects', 'Receiving objects', 'Resolving deltas']
108 stage_weights = [0.2, 0.05, 0.5, 0.25]
109 stagenum = 0
110 for i, stage in reversed(list(enumerate(stages))):
111 if stage in self._buffer:
112 stagenum = i
113 self._buffer = ''
114 break
115 self._status = stages[stagenum]
116 percs = re.findall(r'(\d+)%', string)
117 if percs:
118 progress = int(round((int(percs[-1]) * stage_weights[stagenum]) + (sum(stage_weights[:stagenum]) * 100)))
119 rates = re.findall(r'([\d.]+ [a-zA-Z]*/s+)', string)
120 if rates:
121 rate = rates[-1]
122 else:
123 rate = None
124 self.update(progress, rate)
125 else:
126 if stagenum == 0:
127 percs = re.findall(r': (\d+)', string)
128 if percs:
129 count = int(percs[-1])
130 if count > self._count:
131 self._count = count
132 self._fire_progress(-count)
133 super(GitProgressHandler, self).write(string)
134
135
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500136class Git(FetchMethod):
Brad Bishop316dfdd2018-06-25 12:45:53 -0400137 bitbake_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.join(os.path.abspath(__file__))), '..', '..', '..'))
138 make_shallow_path = os.path.join(bitbake_dir, 'bin', 'git-make-shallow')
139
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500140 """Class to fetch a module or modules from git repositories"""
141 def init(self, d):
142 pass
143
144 def supports(self, ud, d):
145 """
146 Check to see if a given url can be fetched with git.
147 """
148 return ud.type in ['git']
149
150 def supports_checksum(self, urldata):
151 return False
152
153 def urldata_init(self, ud, d):
154 """
155 init git specific variable within url data
156 so that the git method like latest_revision() can work
157 """
158 if 'protocol' in ud.parm:
159 ud.proto = ud.parm['protocol']
160 elif not ud.host:
161 ud.proto = 'file'
162 else:
163 ud.proto = "git"
Andrew Geissler595f6302022-01-24 19:11:47 +0000164 if ud.host == "github.com" and ud.proto == "git":
165 # github stopped supporting git protocol
166 # https://github.blog/2021-09-01-improving-git-protocol-security-github/#no-more-unauthenticated-git
167 ud.proto = "https"
168 bb.warn("URL: %s uses git protocol which is no longer supported by github. Please change to ;protocol=https in the url." % ud.url)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500169
170 if not ud.proto in ('git', 'file', 'ssh', 'http', 'https', 'rsync'):
171 raise bb.fetch2.ParameterError("Invalid protocol type", ud.url)
172
173 ud.nocheckout = ud.parm.get("nocheckout","0") == "1"
174
175 ud.rebaseable = ud.parm.get("rebaseable","0") == "1"
176
177 ud.nobranch = ud.parm.get("nobranch","0") == "1"
178
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600179 # usehead implies nobranch
180 ud.usehead = ud.parm.get("usehead","0") == "1"
181 if ud.usehead:
182 if ud.proto != "file":
183 raise bb.fetch2.ParameterError("The usehead option is only for use with local ('protocol=file') git repositories", ud.url)
184 ud.nobranch = 1
185
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500186 # bareclone implies nocheckout
187 ud.bareclone = ud.parm.get("bareclone","0") == "1"
188 if ud.bareclone:
189 ud.nocheckout = 1
190
191 ud.unresolvedrev = {}
Andrew Geissler595f6302022-01-24 19:11:47 +0000192 branches = ud.parm.get("branch", "").split(',')
193 if branches == [""] and not ud.nobranch:
194 bb.warn("URL: %s does not set any branch parameter. The future default branch used by tools and repositories is uncertain and we will therefore soon require this is set in all git urls." % ud.url)
195 branches = ["master"]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500196 if len(branches) != len(ud.names):
197 raise bb.fetch2.ParameterError("The number of name and branch parameters is not balanced", ud.url)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500198
Andrew Geisslerc926e172021-05-07 16:11:35 -0500199 ud.noshared = d.getVar("BB_GIT_NOSHARED") == "1"
200
201 ud.cloneflags = "-n"
202 if not ud.noshared:
203 ud.cloneflags += " -s"
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500204 if ud.bareclone:
205 ud.cloneflags += " --mirror"
206
207 ud.shallow = d.getVar("BB_GIT_SHALLOW") == "1"
208 ud.shallow_extra_refs = (d.getVar("BB_GIT_SHALLOW_EXTRA_REFS") or "").split()
209
210 depth_default = d.getVar("BB_GIT_SHALLOW_DEPTH")
211 if depth_default is not None:
212 try:
213 depth_default = int(depth_default or 0)
214 except ValueError:
215 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH: %s" % depth_default)
216 else:
217 if depth_default < 0:
218 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH: %s" % depth_default)
219 else:
220 depth_default = 1
221 ud.shallow_depths = collections.defaultdict(lambda: depth_default)
222
Brad Bishop19323692019-04-05 15:28:33 -0400223 revs_default = d.getVar("BB_GIT_SHALLOW_REVS")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500224 ud.shallow_revs = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500225 ud.branches = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500226 for pos, name in enumerate(ud.names):
227 branch = branches[pos]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500228 ud.branches[name] = branch
229 ud.unresolvedrev[name] = branch
230
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500231 shallow_depth = d.getVar("BB_GIT_SHALLOW_DEPTH_%s" % name)
232 if shallow_depth is not None:
233 try:
234 shallow_depth = int(shallow_depth or 0)
235 except ValueError:
236 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth))
237 else:
238 if shallow_depth < 0:
239 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth))
240 ud.shallow_depths[name] = shallow_depth
241
242 revs = d.getVar("BB_GIT_SHALLOW_REVS_%s" % name)
243 if revs is not None:
244 ud.shallow_revs.extend(revs.split())
245 elif revs_default is not None:
246 ud.shallow_revs.extend(revs_default.split())
247
248 if (ud.shallow and
249 not ud.shallow_revs and
250 all(ud.shallow_depths[n] == 0 for n in ud.names)):
251 # Shallow disabled for this URL
252 ud.shallow = False
253
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600254 if ud.usehead:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600255 # When usehead is set let's associate 'HEAD' with the unresolved
256 # rev of this repository. This will get resolved into a revision
257 # later. If an actual revision happens to have also been provided
258 # then this setting will be overridden.
259 for name in ud.names:
260 ud.unresolvedrev[name] = 'HEAD'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600261
Patrick Williams73bd93f2024-02-20 08:07:48 -0600262 ud.basecmd = d.getVar("FETCHCMD_git") or "git -c gc.autoDetach=false -c core.pager=cat -c safe.bareRepository=all"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500263
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500264 write_tarballs = d.getVar("BB_GENERATE_MIRROR_TARBALLS") or "0"
265 ud.write_tarballs = write_tarballs != "0" or ud.rebaseable
266 ud.write_shallow_tarballs = (d.getVar("BB_GENERATE_SHALLOW_TARBALLS") or write_tarballs) != "0"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500267
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500268 ud.setup_revisions(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500269
270 for name in ud.names:
Patrick Williams03907ee2022-05-01 06:28:52 -0500271 # Ensure any revision that doesn't look like a SHA-1 is translated into one
272 if not sha1_re.match(ud.revisions[name] or ''):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500273 if ud.revisions[name]:
274 ud.unresolvedrev[name] = ud.revisions[name]
275 ud.revisions[name] = self.latest_revision(ud, d, name)
276
Andrew Geisslerc3d88e42020-10-02 09:45:00 -0500277 gitsrcname = '%s%s' % (ud.host.replace(':', '.'), ud.path.replace('/', '.').replace('*', '.').replace(' ','_'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500278 if gitsrcname.startswith('.'):
279 gitsrcname = gitsrcname[1:]
280
Patrick Williams03907ee2022-05-01 06:28:52 -0500281 # For a rebaseable git repo, it is necessary to keep a mirror tar ball
282 # per revision, so that even if the revision disappears from the
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500283 # upstream repo in the future, the mirror will remain intact and still
Patrick Williams03907ee2022-05-01 06:28:52 -0500284 # contain the revision
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500285 if ud.rebaseable:
286 for name in ud.names:
287 gitsrcname = gitsrcname + '_' + ud.revisions[name]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500288
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500289 dl_dir = d.getVar("DL_DIR")
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800290 gitdir = d.getVar("GITDIR") or (dl_dir + "/git2")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500291 ud.clonedir = os.path.join(gitdir, gitsrcname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500292 ud.localfile = ud.clonedir
293
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500294 mirrortarball = 'git2_%s.tar.gz' % gitsrcname
295 ud.fullmirror = os.path.join(dl_dir, mirrortarball)
296 ud.mirrortarballs = [mirrortarball]
297 if ud.shallow:
298 tarballname = gitsrcname
299 if ud.bareclone:
300 tarballname = "%s_bare" % tarballname
301
302 if ud.shallow_revs:
303 tarballname = "%s_%s" % (tarballname, "_".join(sorted(ud.shallow_revs)))
304
305 for name, revision in sorted(ud.revisions.items()):
306 tarballname = "%s_%s" % (tarballname, ud.revisions[name][:7])
307 depth = ud.shallow_depths[name]
308 if depth:
309 tarballname = "%s-%s" % (tarballname, depth)
310
311 shallow_refs = []
312 if not ud.nobranch:
313 shallow_refs.extend(ud.branches.values())
314 if ud.shallow_extra_refs:
315 shallow_refs.extend(r.replace('refs/heads/', '').replace('*', 'ALL') for r in ud.shallow_extra_refs)
316 if shallow_refs:
317 tarballname = "%s_%s" % (tarballname, "_".join(sorted(shallow_refs)).replace('/', '.'))
318
319 fetcher = self.__class__.__name__.lower()
320 ud.shallowtarball = '%sshallow_%s.tar.gz' % (fetcher, tarballname)
321 ud.fullshallow = os.path.join(dl_dir, ud.shallowtarball)
322 ud.mirrortarballs.insert(0, ud.shallowtarball)
323
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500324 def localpath(self, ud, d):
325 return ud.clonedir
326
327 def need_update(self, ud, d):
Patrick Williams39653562024-03-01 08:54:02 -0600328 return self.clonedir_need_update(ud, d) \
329 or self.shallow_tarball_need_update(ud) \
330 or self.tarball_need_update(ud) \
331 or self.lfs_need_update(ud, d)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800332
333 def clonedir_need_update(self, ud, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500334 if not os.path.exists(ud.clonedir):
335 return True
Brad Bishop64c979e2019-11-04 13:55:29 -0500336 if ud.shallow and ud.write_shallow_tarballs and self.clonedir_need_shallow_revs(ud, d):
337 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500338 for name in ud.names:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600339 if not self._contains_ref(ud, d, name, ud.clonedir):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500340 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500341 return False
342
Patrick Williams39653562024-03-01 08:54:02 -0600343 def lfs_need_update(self, ud, d):
344 if self.clonedir_need_update(ud, d):
345 return True
346
347 for name in ud.names:
348 if not self._lfs_objects_downloaded(ud, d, name, ud.clonedir):
349 return True
350 return False
351
Brad Bishop64c979e2019-11-04 13:55:29 -0500352 def clonedir_need_shallow_revs(self, ud, d):
353 for rev in ud.shallow_revs:
354 try:
355 runfetchcmd('%s rev-parse -q --verify %s' % (ud.basecmd, rev), d, quiet=True, workdir=ud.clonedir)
356 except bb.fetch2.FetchError:
357 return rev
358 return None
359
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800360 def shallow_tarball_need_update(self, ud):
361 return ud.shallow and ud.write_shallow_tarballs and not os.path.exists(ud.fullshallow)
362
363 def tarball_need_update(self, ud):
364 return ud.write_tarballs and not os.path.exists(ud.fullmirror)
365
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500366 def try_premirror(self, ud, d):
367 # If we don't do this, updating an existing checkout with only premirrors
368 # is not possible
Brad Bishop19323692019-04-05 15:28:33 -0400369 if bb.utils.to_boolean(d.getVar("BB_FETCH_PREMIRRORONLY")):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500370 return True
Patrick Williams73bd93f2024-02-20 08:07:48 -0600371 # If the url is not in trusted network, that is, BB_NO_NETWORK is set to 0
372 # and BB_ALLOWED_NETWORKS does not contain the host that ud.url uses, then
373 # we need to try premirrors first as using upstream is destined to fail.
374 if not trusted_network(d, ud.url):
375 return True
376 # the following check is to ensure incremental fetch in downloads, this is
377 # because the premirror might be old and does not contain the new rev required,
378 # and this will cause a total removal and new clone. So if we can reach to
379 # network, we prefer upstream over premirror, though the premirror might contain
380 # the new rev.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500381 if os.path.exists(ud.clonedir):
382 return False
383 return True
384
385 def download(self, ud, d):
386 """Fetch url"""
387
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500388 # A current clone is preferred to either tarball, a shallow tarball is
389 # preferred to an out of date clone, and a missing clone will use
390 # either tarball.
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800391 if ud.shallow and os.path.exists(ud.fullshallow) and self.need_update(ud, d):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500392 ud.localpath = ud.fullshallow
393 return
Andrew Geissler78b72792022-06-14 06:47:25 -0500394 elif os.path.exists(ud.fullmirror) and self.need_update(ud, d):
395 if not os.path.exists(ud.clonedir):
396 bb.utils.mkdirhier(ud.clonedir)
397 runfetchcmd("tar -xzf %s" % ud.fullmirror, d, workdir=ud.clonedir)
398 else:
399 tmpdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
400 runfetchcmd("tar -xzf %s" % ud.fullmirror, d, workdir=tmpdir)
Patrick Williams73bd93f2024-02-20 08:07:48 -0600401 output = runfetchcmd("%s remote" % ud.basecmd, d, quiet=True, workdir=ud.clonedir)
402 if 'mirror' in output:
403 runfetchcmd("%s remote rm mirror" % ud.basecmd, d, workdir=ud.clonedir)
404 runfetchcmd("%s remote add --mirror=fetch mirror %s" % (ud.basecmd, tmpdir), d, workdir=ud.clonedir)
405 fetch_cmd = "LANG=C %s fetch -f --update-head-ok --progress mirror " % (ud.basecmd)
Andrew Geissler78b72792022-06-14 06:47:25 -0500406 runfetchcmd(fetch_cmd, d, workdir=ud.clonedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500407 repourl = self._get_repo_url(ud)
408
Andrew Geissler5082cc72023-09-11 08:41:39 -0400409 needs_clone = False
410 if os.path.exists(ud.clonedir):
411 # The directory may exist, but not be the top level of a bare git
412 # repository in which case it needs to be deleted and re-cloned.
413 try:
414 # Since clones can be bare, use --absolute-git-dir instead of --show-toplevel
415 output = runfetchcmd("LANG=C %s rev-parse --absolute-git-dir" % ud.basecmd, d, workdir=ud.clonedir)
Andrew Geissler220dafd2023-10-04 10:18:08 -0500416 toplevel = output.rstrip()
Andrew Geissler5082cc72023-09-11 08:41:39 -0400417
Andrew Geissler220dafd2023-10-04 10:18:08 -0500418 if not bb.utils.path_is_descendant(toplevel, ud.clonedir):
419 logger.warning("Top level directory '%s' is not a descendant of '%s'. Re-cloning", toplevel, ud.clonedir)
Andrew Geissler5082cc72023-09-11 08:41:39 -0400420 needs_clone = True
421 except bb.fetch2.FetchError as e:
422 logger.warning("Unable to get top level for %s (not a git directory?): %s", ud.clonedir, e)
423 needs_clone = True
Andrew Geissler220dafd2023-10-04 10:18:08 -0500424 except FileNotFoundError as e:
425 logger.warning("%s", e)
426 needs_clone = True
Andrew Geissler5082cc72023-09-11 08:41:39 -0400427
428 if needs_clone:
429 shutil.rmtree(ud.clonedir)
430 else:
431 needs_clone = True
432
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500433 # If the repo still doesn't exist, fallback to cloning it
Andrew Geissler5082cc72023-09-11 08:41:39 -0400434 if needs_clone:
Andrew Geisslerfc113ea2023-03-31 09:59:46 -0500435 # We do this since git will use a "-l" option automatically for local urls where possible,
436 # but it doesn't work when git/objects is a symlink, only works when it is a directory.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500437 if repourl.startswith("file://"):
Andrew Geisslerfc113ea2023-03-31 09:59:46 -0500438 repourl_path = repourl[7:]
439 objects = os.path.join(repourl_path, 'objects')
440 if os.path.isdir(objects) and not os.path.islink(objects):
441 repourl = repourl_path
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500442 clone_cmd = "LANG=C %s clone --bare --mirror %s %s --progress" % (ud.basecmd, shlex.quote(repourl), ud.clonedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500443 if ud.proto.lower() != 'file':
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500444 bb.fetch2.check_network_access(d, clone_cmd, ud.url)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600445 progresshandler = GitProgressHandler(d)
446 runfetchcmd(clone_cmd, d, log=progresshandler)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500447
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500448 # Update the checkout if needed
Brad Bishop64c979e2019-11-04 13:55:29 -0500449 if self.clonedir_need_update(ud, d):
Brad Bishop6ef32652018-10-09 18:59:25 +0100450 output = runfetchcmd("%s remote" % ud.basecmd, d, quiet=True, workdir=ud.clonedir)
451 if "origin" in output:
452 runfetchcmd("%s remote rm origin" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500453
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500454 runfetchcmd("%s remote add --mirror=fetch origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=ud.clonedir)
Andrew Geissler517393d2023-01-13 08:55:19 -0600455
456 if ud.nobranch:
457 fetch_cmd = "LANG=C %s fetch -f --progress %s refs/*:refs/*" % (ud.basecmd, shlex.quote(repourl))
458 else:
459 fetch_cmd = "LANG=C %s fetch -f --progress %s refs/heads/*:refs/heads/* refs/tags/*:refs/tags/*" % (ud.basecmd, shlex.quote(repourl))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500460 if ud.proto.lower() != 'file':
461 bb.fetch2.check_network_access(d, fetch_cmd, ud.url)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600462 progresshandler = GitProgressHandler(d)
463 runfetchcmd(fetch_cmd, d, log=progresshandler, workdir=ud.clonedir)
464 runfetchcmd("%s prune-packed" % ud.basecmd, d, workdir=ud.clonedir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400465 runfetchcmd("%s pack-refs --all" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600466 runfetchcmd("%s pack-redundant --all | xargs -r rm" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsd7e96312015-09-22 08:09:05 -0500467 try:
468 os.unlink(ud.fullmirror)
469 except OSError as exc:
470 if exc.errno != errno.ENOENT:
471 raise
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800472
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500473 for name in ud.names:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600474 if not self._contains_ref(ud, d, name, ud.clonedir):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500475 raise bb.fetch2.FetchError("Unable to find revision %s in branch %s even from upstream" % (ud.revisions[name], ud.branches[name]))
476
Brad Bishop64c979e2019-11-04 13:55:29 -0500477 if ud.shallow and ud.write_shallow_tarballs:
478 missing_rev = self.clonedir_need_shallow_revs(ud, d)
479 if missing_rev:
480 raise bb.fetch2.FetchError("Unable to find revision %s even from upstream" % missing_rev)
481
Patrick Williams39653562024-03-01 08:54:02 -0600482 if self.lfs_need_update(ud, d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600483 # Unpack temporary working copy, use it to run 'git checkout' to force pre-fetching
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000484 # of all LFS blobs needed at the srcrev.
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600485 #
486 # It would be nice to just do this inline here by running 'git-lfs fetch'
487 # on the bare clonedir, but that operation requires a working copy on some
488 # releases of Git LFS.
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600489 with tempfile.TemporaryDirectory(dir=d.getVar('DL_DIR')) as tmpdir:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600490 # Do the checkout. This implicitly involves a Git LFS fetch.
Andrew Geisslerc926e172021-05-07 16:11:35 -0500491 Git.unpack(self, ud, tmpdir, d)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600492
493 # Scoop up a copy of any stuff that Git LFS downloaded. Merge them into
494 # the bare clonedir.
495 #
496 # As this procedure is invoked repeatedly on incremental fetches as
497 # a recipe's SRCREV is bumped throughout its lifetime, this will
498 # result in a gradual accumulation of LFS blobs in <ud.clonedir>/lfs
499 # corresponding to all the blobs reachable from the different revs
500 # fetched across time.
501 #
502 # Only do this if the unpack resulted in a .git/lfs directory being
503 # created; this only happens if at least one blob needed to be
504 # downloaded.
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600505 if os.path.exists(os.path.join(ud.destdir, ".git", "lfs")):
506 runfetchcmd("tar -cf - lfs | tar -xf - -C %s" % ud.clonedir, d, workdir="%s/.git" % ud.destdir)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600507
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500508 def build_mirror_data(self, ud, d):
Andrew Geissler5199d832021-09-24 16:47:35 -0500509
510 # Create as a temp file and move atomically into position to avoid races
511 @contextmanager
512 def create_atomic(filename):
513 fd, tfile = tempfile.mkstemp(dir=os.path.dirname(filename))
514 try:
515 yield tfile
516 umask = os.umask(0o666)
517 os.umask(umask)
518 os.chmod(tfile, (0o666 & ~umask))
519 os.rename(tfile, filename)
520 finally:
521 os.close(fd)
522
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500523 if ud.shallow and ud.write_shallow_tarballs:
524 if not os.path.exists(ud.fullshallow):
525 if os.path.islink(ud.fullshallow):
526 os.unlink(ud.fullshallow)
527 tempdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
528 shallowclone = os.path.join(tempdir, 'git')
529 try:
530 self.clone_shallow_local(ud, shallowclone, d)
531
532 logger.info("Creating tarball of git repository")
Andrew Geissler5199d832021-09-24 16:47:35 -0500533 with create_atomic(ud.fullshallow) as tfile:
534 runfetchcmd("tar -czf %s ." % tfile, d, workdir=shallowclone)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500535 runfetchcmd("touch %s.done" % ud.fullshallow, d)
536 finally:
537 bb.utils.remove(tempdir, recurse=True)
538 elif ud.write_tarballs and not os.path.exists(ud.fullmirror):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500539 if os.path.islink(ud.fullmirror):
540 os.unlink(ud.fullmirror)
541
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500542 logger.info("Creating tarball of git repository")
Andrew Geissler5199d832021-09-24 16:47:35 -0500543 with create_atomic(ud.fullmirror) as tfile:
Patrick Williams73bd93f2024-02-20 08:07:48 -0600544 mtime = runfetchcmd("{} log --all -1 --format=%cD".format(ud.basecmd), d,
Andrew Geissler9aee5002022-03-30 16:27:02 +0000545 quiet=True, workdir=ud.clonedir)
Patrick Williams03907ee2022-05-01 06:28:52 -0500546 runfetchcmd("tar -czf %s --owner oe:0 --group oe:0 --mtime \"%s\" ."
Andrew Geissler9aee5002022-03-30 16:27:02 +0000547 % (tfile, mtime), d, workdir=ud.clonedir)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500548 runfetchcmd("touch %s.done" % ud.fullmirror, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500549
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500550 def clone_shallow_local(self, ud, dest, d):
551 """Clone the repo and make it shallow.
552
553 The upstream url of the new clone isn't set at this time, as it'll be
554 set correctly when unpacked."""
555 runfetchcmd("%s clone %s %s %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, dest), d)
556
557 to_parse, shallow_branches = [], []
558 for name in ud.names:
559 revision = ud.revisions[name]
560 depth = ud.shallow_depths[name]
561 if depth:
562 to_parse.append('%s~%d^{}' % (revision, depth - 1))
563
564 # For nobranch, we need a ref, otherwise the commits will be
565 # removed, and for non-nobranch, we truncate the branch to our
566 # srcrev, to avoid keeping unnecessary history beyond that.
567 branch = ud.branches[name]
568 if ud.nobranch:
569 ref = "refs/shallow/%s" % name
570 elif ud.bareclone:
571 ref = "refs/heads/%s" % branch
572 else:
573 ref = "refs/remotes/origin/%s" % branch
574
575 shallow_branches.append(ref)
576 runfetchcmd("%s update-ref %s %s" % (ud.basecmd, ref, revision), d, workdir=dest)
577
578 # Map srcrev+depths to revisions
579 parsed_depths = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join(to_parse)), d, workdir=dest)
580
581 # Resolve specified revisions
582 parsed_revs = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join('"%s^{}"' % r for r in ud.shallow_revs)), d, workdir=dest)
583 shallow_revisions = parsed_depths.splitlines() + parsed_revs.splitlines()
584
585 # Apply extra ref wildcards
586 all_refs = runfetchcmd('%s for-each-ref "--format=%%(refname)"' % ud.basecmd,
587 d, workdir=dest).splitlines()
588 for r in ud.shallow_extra_refs:
589 if not ud.bareclone:
590 r = r.replace('refs/heads/', 'refs/remotes/origin/')
591
592 if '*' in r:
593 matches = filter(lambda a: fnmatch.fnmatchcase(a, r), all_refs)
594 shallow_branches.extend(matches)
595 else:
596 shallow_branches.append(r)
597
598 # Make the repository shallow
Brad Bishop316dfdd2018-06-25 12:45:53 -0400599 shallow_cmd = [self.make_shallow_path, '-s']
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500600 for b in shallow_branches:
601 shallow_cmd.append('-r')
602 shallow_cmd.append(b)
603 shallow_cmd.extend(shallow_revisions)
604 runfetchcmd(subprocess.list2cmdline(shallow_cmd), d, workdir=dest)
605
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500606 def unpack(self, ud, destdir, d):
607 """ unpack the downloaded src to destdir"""
608
Andrew Geissler595f6302022-01-24 19:11:47 +0000609 subdir = ud.parm.get("subdir")
610 subpath = ud.parm.get("subpath")
611 readpathspec = ""
612 def_destsuffix = "git/"
613
614 if subpath:
615 readpathspec = ":%s" % subpath
616 def_destsuffix = "%s/" % os.path.basename(subpath.rstrip('/'))
617
618 if subdir:
619 # If 'subdir' param exists, create a dir and use it as destination for unpack cmd
620 if os.path.isabs(subdir):
621 if not os.path.realpath(subdir).startswith(os.path.realpath(destdir)):
622 raise bb.fetch2.UnpackError("subdir argument isn't a subdirectory of unpack root %s" % destdir, ud.url)
623 destdir = subdir
624 else:
625 destdir = os.path.join(destdir, subdir)
626 def_destsuffix = ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500627
628 destsuffix = ud.parm.get("destsuffix", def_destsuffix)
629 destdir = ud.destdir = os.path.join(destdir, destsuffix)
630 if os.path.exists(destdir):
631 bb.utils.prunedir(destdir)
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600632 if not ud.bareclone:
633 ud.unpack_tracer.unpack("git", destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500634
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600635 need_lfs = self._need_lfs(ud)
Brad Bishopa34c0302019-09-23 22:34:48 -0400636
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500637 if not need_lfs:
638 ud.basecmd = "GIT_LFS_SKIP_SMUDGE=1 " + ud.basecmd
639
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800640 source_found = False
641 source_error = []
642
Andrew Geissler87f5cff2022-09-30 13:13:31 -0500643 clonedir_is_up_to_date = not self.clonedir_need_update(ud, d)
644 if clonedir_is_up_to_date:
645 runfetchcmd("%s clone %s %s/ %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, destdir), d)
646 source_found = True
647 else:
648 source_error.append("clone directory not available or not up to date: " + ud.clonedir)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800649
650 if not source_found:
651 if ud.shallow:
652 if os.path.exists(ud.fullshallow):
653 bb.utils.mkdirhier(destdir)
654 runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=destdir)
655 source_found = True
656 else:
657 source_error.append("shallow clone not available: " + ud.fullshallow)
658 else:
659 source_error.append("shallow clone not enabled")
660
661 if not source_found:
662 raise bb.fetch2.UnpackError("No up to date source found: " + "; ".join(source_error), ud.url)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500663
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500664 repourl = self._get_repo_url(ud)
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500665 runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=destdir)
Brad Bishopc342db32019-05-15 21:57:59 -0400666
667 if self._contains_lfs(ud, d, destdir):
Brad Bishop00e122a2019-10-05 11:10:57 -0400668 if need_lfs and not self._find_git_lfs(d):
669 raise bb.fetch2.FetchError("Repository %s has LFS content, install git-lfs on host to download (or set lfs=0 to ignore it)" % (repourl))
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500670 elif not need_lfs:
Brad Bishopa34c0302019-09-23 22:34:48 -0400671 bb.note("Repository %s has LFS content but it is not being fetched" % (repourl))
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600672 else:
673 runfetchcmd("%s lfs install" % ud.basecmd, d, workdir=destdir)
Brad Bishopc342db32019-05-15 21:57:59 -0400674
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500675 if not ud.nocheckout:
Andrew Geissler595f6302022-01-24 19:11:47 +0000676 if subpath:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600677 runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d,
678 workdir=destdir)
679 runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500680 elif not ud.nobranch:
681 branchname = ud.branches[ud.names[0]]
682 runfetchcmd("%s checkout -B %s %s" % (ud.basecmd, branchname, \
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600683 ud.revisions[ud.names[0]]), d, workdir=destdir)
Andre Rosa49271d42017-09-07 11:15:55 +0200684 runfetchcmd("%s branch %s --set-upstream-to origin/%s" % (ud.basecmd, branchname, \
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600685 branchname), d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500686 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600687 runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revisions[ud.names[0]]), d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500688
689 return True
690
691 def clean(self, ud, d):
692 """ clean the git directory """
693
Brad Bishop19323692019-04-05 15:28:33 -0400694 to_remove = [ud.localpath, ud.fullmirror, ud.fullmirror + ".done"]
695 # The localpath is a symlink to clonedir when it is cloned from a
696 # mirror, so remove both of them.
697 if os.path.islink(ud.localpath):
698 clonedir = os.path.realpath(ud.localpath)
699 to_remove.append(clonedir)
700
701 for r in to_remove:
702 if os.path.exists(r):
703 bb.note('Removing %s' % r)
704 bb.utils.remove(r, True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500705
706 def supports_srcrev(self):
707 return True
708
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600709 def _contains_ref(self, ud, d, name, wd):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500710 cmd = ""
711 if ud.nobranch:
712 cmd = "%s log --pretty=oneline -n 1 %s -- 2> /dev/null | wc -l" % (
713 ud.basecmd, ud.revisions[name])
714 else:
715 cmd = "%s branch --contains %s --list %s 2> /dev/null | wc -l" % (
716 ud.basecmd, ud.revisions[name], ud.branches[name])
717 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600718 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500719 except bb.fetch2.FetchError:
720 return False
721 if len(output.split()) > 1:
722 raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
723 return output.split()[0] != "0"
724
Patrick Williams39653562024-03-01 08:54:02 -0600725 def _lfs_objects_downloaded(self, ud, d, name, wd):
726 """
727 Verifies whether the LFS objects for requested revisions have already been downloaded
728 """
729 # Bail out early if this repository doesn't use LFS
730 if not self._need_lfs(ud) or not self._contains_lfs(ud, d, wd):
731 return True
732
733 # The Git LFS specification specifies ([1]) the LFS folder layout so it should be safe to check for file
734 # existence.
735 # [1] https://github.com/git-lfs/git-lfs/blob/main/docs/spec.md#intercepting-git
736 cmd = "%s lfs ls-files -l %s" \
737 % (ud.basecmd, ud.revisions[name])
738 output = runfetchcmd(cmd, d, quiet=True, workdir=wd).rstrip()
739 # Do not do any further matching if no objects are managed by LFS
740 if not output:
741 return True
742
743 # Match all lines beginning with the hexadecimal OID
744 oid_regex = re.compile("^(([a-fA-F0-9]{2})([a-fA-F0-9]{2})[A-Fa-f0-9]+)")
745 for line in output.split("\n"):
746 oid = re.search(oid_regex, line)
747 if not oid:
748 bb.warn("git lfs ls-files output '%s' did not match expected format." % line)
749 if not os.path.exists(os.path.join(wd, "lfs", "objects", oid.group(2), oid.group(3), oid.group(1))):
750 return False
751
752 return True
753
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600754 def _need_lfs(self, ud):
755 return ud.parm.get("lfs", "1") == "1"
756
Brad Bishopc342db32019-05-15 21:57:59 -0400757 def _contains_lfs(self, ud, d, wd):
758 """
759 Check if the repository has 'lfs' (large file) content
760 """
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500761
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600762 if ud.nobranch:
763 # If no branch is specified, use the current git commit
764 refname = self._build_revision(ud, d, ud.names[0])
765 elif wd == ud.clonedir:
766 # The bare clonedir doesn't use the remote names; it has the branch immediately.
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600767 refname = ud.branches[ud.names[0]]
768 else:
769 refname = "origin/%s" % ud.branches[ud.names[0]]
770
771 cmd = "%s grep lfs %s:.gitattributes | wc -l" % (
772 ud.basecmd, refname)
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500773
Brad Bishopc342db32019-05-15 21:57:59 -0400774 try:
775 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
776 if int(output) > 0:
777 return True
778 except (bb.fetch2.FetchError,ValueError):
779 pass
780 return False
781
Brad Bishop00e122a2019-10-05 11:10:57 -0400782 def _find_git_lfs(self, d):
783 """
784 Return True if git-lfs can be found, False otherwise.
785 """
786 import shutil
787 return shutil.which("git-lfs", path=d.getVar('PATH')) is not None
788
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500789 def _get_repo_url(self, ud):
790 """
791 Return the repository URL
792 """
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600793 # Note that we do not support passwords directly in the git urls. There are several
794 # reasons. SRC_URI can be written out to things like buildhistory and people don't
795 # want to leak passwords like that. Its also all too easy to share metadata without
796 # removing the password. ssh keys, ~/.netrc and ~/.ssh/config files can be used as
797 # alternatives so we will not take patches adding password support here.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500798 if ud.user:
799 username = ud.user + '@'
800 else:
801 username = ""
802 return "%s://%s%s%s" % (ud.proto, username, ud.host, ud.path)
803
804 def _revision_key(self, ud, d, name):
805 """
806 Return a unique key for the url
807 """
Andrew Geissler82c905d2020-04-13 13:39:40 -0500808 # Collapse adjacent slashes
Andrew Geissler82c905d2020-04-13 13:39:40 -0500809 return "git:" + ud.host + slash_re.sub(".", ud.path) + ud.unresolvedrev[name]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500810
811 def _lsremote(self, ud, d, search):
812 """
813 Run git ls-remote with the specified search string
814 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500815 # Prevent recursion e.g. in OE if SRCPV is in PV, PV is in WORKDIR,
816 # and WORKDIR is in PATH (as a result of RSS), our call to
817 # runfetchcmd() exports PATH so this function will get called again (!)
818 # In this scenario the return call of the function isn't actually
819 # important - WORKDIR isn't needed in PATH to call git ls-remote
820 # anyway.
821 if d.getVar('_BB_GIT_IN_LSREMOTE', False):
822 return ''
823 d.setVar('_BB_GIT_IN_LSREMOTE', '1')
824 try:
825 repourl = self._get_repo_url(ud)
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500826 cmd = "%s ls-remote %s %s" % \
827 (ud.basecmd, shlex.quote(repourl), search)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500828 if ud.proto.lower() != 'file':
829 bb.fetch2.check_network_access(d, cmd, repourl)
830 output = runfetchcmd(cmd, d, True)
831 if not output:
832 raise bb.fetch2.FetchError("The command %s gave empty output unexpectedly" % cmd, ud.url)
833 finally:
834 d.delVar('_BB_GIT_IN_LSREMOTE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500835 return output
836
837 def _latest_revision(self, ud, d, name):
838 """
839 Compute the HEAD revision for the url
840 """
Andrew Geisslerfc113ea2023-03-31 09:59:46 -0500841 if not d.getVar("__BBSRCREV_SEEN"):
Andrew Geissler615f2f12022-07-15 14:00:58 -0500842 raise bb.fetch2.FetchError("Recipe uses a floating tag/branch '%s' for repo '%s' without a fixed SRCREV yet doesn't call bb.fetch2.get_srcrev() (use SRCPV in PV for OE)." % (ud.unresolvedrev[name], ud.host+ud.path))
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000843
844 # Ensure we mark as not cached
Andrew Geisslerfc113ea2023-03-31 09:59:46 -0500845 bb.fetch2.mark_recipe_nocache(d)
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000846
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500847 output = self._lsremote(ud, d, "")
848 # Tags of the form ^{} may not work, need to fallback to other form
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600849 if ud.unresolvedrev[name][:5] == "refs/" or ud.usehead:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500850 head = ud.unresolvedrev[name]
851 tag = ud.unresolvedrev[name]
852 else:
853 head = "refs/heads/%s" % ud.unresolvedrev[name]
854 tag = "refs/tags/%s" % ud.unresolvedrev[name]
855 for s in [head, tag + "^{}", tag]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600856 for l in output.strip().split('\n'):
857 sha1, ref = l.split()
858 if s == ref:
859 return sha1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500860 raise bb.fetch2.FetchError("Unable to resolve '%s' in upstream git repository in git ls-remote output for %s" % \
861 (ud.unresolvedrev[name], ud.host+ud.path))
862
863 def latest_versionstring(self, ud, d):
864 """
865 Compute the latest release name like "x.y.x" in "x.y.x+gitHASH"
866 by searching through the tags output of ls-remote, comparing
867 versions and returning the highest match.
868 """
869 pupver = ('', '')
870
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500871 try:
872 output = self._lsremote(ud, d, "refs/tags/*")
Brad Bishop316dfdd2018-06-25 12:45:53 -0400873 except (bb.fetch2.FetchError, bb.fetch2.NetworkAccess) as e:
874 bb.note("Could not list remote: %s" % str(e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500875 return pupver
876
Patrick Williams73bd93f2024-02-20 08:07:48 -0600877 rev_tag_re = re.compile(r"([0-9a-f]{40})\s+refs/tags/(.*)")
878 pver_re = re.compile(d.getVar('UPSTREAM_CHECK_GITTAGREGEX') or r"(?P<pver>([0-9][\.|_]?)+)")
879 nonrel_re = re.compile(r"(alpha|beta|rc|final)+")
880
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500881 verstring = ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500882 for line in output.split("\n"):
883 if not line:
884 break
885
Patrick Williams73bd93f2024-02-20 08:07:48 -0600886 m = rev_tag_re.match(line)
887 if not m:
888 continue
889
890 (revision, tag) = m.groups()
891
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500892 # Ignore non-released branches
Patrick Williams73bd93f2024-02-20 08:07:48 -0600893 if nonrel_re.search(tag):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500894 continue
895
896 # search for version in the line
Patrick Williams73bd93f2024-02-20 08:07:48 -0600897 m = pver_re.search(tag)
898 if not m:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500899 continue
900
Patrick Williams73bd93f2024-02-20 08:07:48 -0600901 pver = m.group('pver').replace("_", ".")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500902
Patrick Williams73bd93f2024-02-20 08:07:48 -0600903 if verstring and bb.utils.vercmp(("0", pver, ""), ("0", verstring, "")) < 0:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500904 continue
905
Patrick Williams73bd93f2024-02-20 08:07:48 -0600906 verstring = pver
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500907 pupver = (verstring, revision)
908
909 return pupver
910
911 def _build_revision(self, ud, d, name):
912 return ud.revisions[name]
913
914 def gitpkgv_revision(self, ud, d, name):
915 """
916 Return a sortable revision number by counting commits in the history
917 Based on gitpkgv.bblass in meta-openembedded
918 """
919 rev = self._build_revision(ud, d, name)
920 localpath = ud.localpath
921 rev_file = os.path.join(localpath, "oe-gitpkgv_" + rev)
922 if not os.path.exists(localpath):
923 commits = None
924 else:
925 if not os.path.exists(rev_file) or not os.path.getsize(rev_file):
926 from pipes import quote
927 commits = bb.fetch2.runfetchcmd(
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500928 "git rev-list %s -- | wc -l" % quote(rev),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500929 d, quiet=True).strip().lstrip('0')
930 if commits:
931 open(rev_file, "w").write("%d\n" % int(commits))
932 else:
933 commits = open(rev_file, "r").readline(128).strip()
934 if commits:
935 return False, "%s+%s" % (commits, rev[:7])
936 else:
937 return True, str(rev)
938
939 def checkstatus(self, fetch, ud, d):
940 try:
941 self._lsremote(ud, d, "")
942 return True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500943 except bb.fetch2.FetchError:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500944 return False