blob: 17d4904927df5342fe7bf70da2b6f25336e9f1f6 [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
47 referring to commit which is valid in tag instead of branch.
48 The default is "0", set nobranch=1 if needed.
49
Patrick Williamsc0f7c042017-02-23 20:41:17 -060050- usehead
Brad Bishop6e60e8b2018-02-01 10:27:11 -050051 For local git:// urls to use the current branch HEAD as the revision for use with
Patrick Williamsc0f7c042017-02-23 20:41:17 -060052 AUTOREV. Implies nobranch.
53
Patrick Williamsc124f4f2015-09-15 14:41:29 -050054"""
55
Brad Bishopc342db32019-05-15 21:57:59 -040056# Copyright (C) 2005 Richard Purdie
Patrick Williamsc124f4f2015-09-15 14:41:29 -050057#
Brad Bishopc342db32019-05-15 21:57:59 -040058# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -050059#
Patrick Williamsc124f4f2015-09-15 14:41:29 -050060
Brad Bishopd7bf8c12018-02-25 22:55:05 -050061import collections
Patrick Williamsd7e96312015-09-22 08:09:05 -050062import errno
Brad Bishopd7bf8c12018-02-25 22:55:05 -050063import fnmatch
Patrick Williamsc124f4f2015-09-15 14:41:29 -050064import os
65import re
Andrew Geissler4c19ea12020-10-27 13:52:24 -050066import shlex
Brad Bishopd7bf8c12018-02-25 22:55:05 -050067import subprocess
68import tempfile
Patrick Williamsc124f4f2015-09-15 14:41:29 -050069import bb
Patrick Williamsc0f7c042017-02-23 20:41:17 -060070import bb.progress
Andrew Geissler5199d832021-09-24 16:47:35 -050071from contextlib import contextmanager
Patrick Williamsc124f4f2015-09-15 14:41:29 -050072from bb.fetch2 import FetchMethod
73from bb.fetch2 import runfetchcmd
74from bb.fetch2 import logger
75
Patrick Williamsc0f7c042017-02-23 20:41:17 -060076
Patrick Williams03907ee2022-05-01 06:28:52 -050077sha1_re = re.compile(r'^[0-9a-f]{40}$')
78slash_re = re.compile(r"/+")
79
Patrick Williamsc0f7c042017-02-23 20:41:17 -060080class GitProgressHandler(bb.progress.LineFilterProgressHandler):
81 """Extract progress information from git output"""
82 def __init__(self, d):
83 self._buffer = ''
84 self._count = 0
85 super(GitProgressHandler, self).__init__(d)
86 # Send an initial progress event so the bar gets shown
87 self._fire_progress(-1)
88
89 def write(self, string):
90 self._buffer += string
91 stages = ['Counting objects', 'Compressing objects', 'Receiving objects', 'Resolving deltas']
92 stage_weights = [0.2, 0.05, 0.5, 0.25]
93 stagenum = 0
94 for i, stage in reversed(list(enumerate(stages))):
95 if stage in self._buffer:
96 stagenum = i
97 self._buffer = ''
98 break
99 self._status = stages[stagenum]
100 percs = re.findall(r'(\d+)%', string)
101 if percs:
102 progress = int(round((int(percs[-1]) * stage_weights[stagenum]) + (sum(stage_weights[:stagenum]) * 100)))
103 rates = re.findall(r'([\d.]+ [a-zA-Z]*/s+)', string)
104 if rates:
105 rate = rates[-1]
106 else:
107 rate = None
108 self.update(progress, rate)
109 else:
110 if stagenum == 0:
111 percs = re.findall(r': (\d+)', string)
112 if percs:
113 count = int(percs[-1])
114 if count > self._count:
115 self._count = count
116 self._fire_progress(-count)
117 super(GitProgressHandler, self).write(string)
118
119
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500120class Git(FetchMethod):
Brad Bishop316dfdd2018-06-25 12:45:53 -0400121 bitbake_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.join(os.path.abspath(__file__))), '..', '..', '..'))
122 make_shallow_path = os.path.join(bitbake_dir, 'bin', 'git-make-shallow')
123
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500124 """Class to fetch a module or modules from git repositories"""
125 def init(self, d):
126 pass
127
128 def supports(self, ud, d):
129 """
130 Check to see if a given url can be fetched with git.
131 """
132 return ud.type in ['git']
133
134 def supports_checksum(self, urldata):
135 return False
136
137 def urldata_init(self, ud, d):
138 """
139 init git specific variable within url data
140 so that the git method like latest_revision() can work
141 """
142 if 'protocol' in ud.parm:
143 ud.proto = ud.parm['protocol']
144 elif not ud.host:
145 ud.proto = 'file'
146 else:
147 ud.proto = "git"
Andrew Geissler595f6302022-01-24 19:11:47 +0000148 if ud.host == "github.com" and ud.proto == "git":
149 # github stopped supporting git protocol
150 # https://github.blog/2021-09-01-improving-git-protocol-security-github/#no-more-unauthenticated-git
151 ud.proto = "https"
152 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 -0500153
154 if not ud.proto in ('git', 'file', 'ssh', 'http', 'https', 'rsync'):
155 raise bb.fetch2.ParameterError("Invalid protocol type", ud.url)
156
157 ud.nocheckout = ud.parm.get("nocheckout","0") == "1"
158
159 ud.rebaseable = ud.parm.get("rebaseable","0") == "1"
160
161 ud.nobranch = ud.parm.get("nobranch","0") == "1"
162
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600163 # usehead implies nobranch
164 ud.usehead = ud.parm.get("usehead","0") == "1"
165 if ud.usehead:
166 if ud.proto != "file":
167 raise bb.fetch2.ParameterError("The usehead option is only for use with local ('protocol=file') git repositories", ud.url)
168 ud.nobranch = 1
169
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500170 # bareclone implies nocheckout
171 ud.bareclone = ud.parm.get("bareclone","0") == "1"
172 if ud.bareclone:
173 ud.nocheckout = 1
174
175 ud.unresolvedrev = {}
Andrew Geissler595f6302022-01-24 19:11:47 +0000176 branches = ud.parm.get("branch", "").split(',')
177 if branches == [""] and not ud.nobranch:
178 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)
179 branches = ["master"]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500180 if len(branches) != len(ud.names):
181 raise bb.fetch2.ParameterError("The number of name and branch parameters is not balanced", ud.url)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500182
Andrew Geisslerc926e172021-05-07 16:11:35 -0500183 ud.noshared = d.getVar("BB_GIT_NOSHARED") == "1"
184
185 ud.cloneflags = "-n"
186 if not ud.noshared:
187 ud.cloneflags += " -s"
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500188 if ud.bareclone:
189 ud.cloneflags += " --mirror"
190
191 ud.shallow = d.getVar("BB_GIT_SHALLOW") == "1"
192 ud.shallow_extra_refs = (d.getVar("BB_GIT_SHALLOW_EXTRA_REFS") or "").split()
193
194 depth_default = d.getVar("BB_GIT_SHALLOW_DEPTH")
195 if depth_default is not None:
196 try:
197 depth_default = int(depth_default or 0)
198 except ValueError:
199 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH: %s" % depth_default)
200 else:
201 if depth_default < 0:
202 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH: %s" % depth_default)
203 else:
204 depth_default = 1
205 ud.shallow_depths = collections.defaultdict(lambda: depth_default)
206
Brad Bishop19323692019-04-05 15:28:33 -0400207 revs_default = d.getVar("BB_GIT_SHALLOW_REVS")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500208 ud.shallow_revs = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500209 ud.branches = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500210 for pos, name in enumerate(ud.names):
211 branch = branches[pos]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500212 ud.branches[name] = branch
213 ud.unresolvedrev[name] = branch
214
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500215 shallow_depth = d.getVar("BB_GIT_SHALLOW_DEPTH_%s" % name)
216 if shallow_depth is not None:
217 try:
218 shallow_depth = int(shallow_depth or 0)
219 except ValueError:
220 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth))
221 else:
222 if shallow_depth < 0:
223 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth))
224 ud.shallow_depths[name] = shallow_depth
225
226 revs = d.getVar("BB_GIT_SHALLOW_REVS_%s" % name)
227 if revs is not None:
228 ud.shallow_revs.extend(revs.split())
229 elif revs_default is not None:
230 ud.shallow_revs.extend(revs_default.split())
231
232 if (ud.shallow and
233 not ud.shallow_revs and
234 all(ud.shallow_depths[n] == 0 for n in ud.names)):
235 # Shallow disabled for this URL
236 ud.shallow = False
237
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600238 if ud.usehead:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600239 # When usehead is set let's associate 'HEAD' with the unresolved
240 # rev of this repository. This will get resolved into a revision
241 # later. If an actual revision happens to have also been provided
242 # then this setting will be overridden.
243 for name in ud.names:
244 ud.unresolvedrev[name] = 'HEAD'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600245
Andrew Geisslerd5838332022-05-27 11:33:10 -0500246 ud.basecmd = d.getVar("FETCHCMD_git") or "git -c core.fsyncobjectfiles=0 -c gc.autoDetach=false -c core.pager=cat"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500247
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500248 write_tarballs = d.getVar("BB_GENERATE_MIRROR_TARBALLS") or "0"
249 ud.write_tarballs = write_tarballs != "0" or ud.rebaseable
250 ud.write_shallow_tarballs = (d.getVar("BB_GENERATE_SHALLOW_TARBALLS") or write_tarballs) != "0"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500251
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500252 ud.setup_revisions(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500253
254 for name in ud.names:
Patrick Williams03907ee2022-05-01 06:28:52 -0500255 # Ensure any revision that doesn't look like a SHA-1 is translated into one
256 if not sha1_re.match(ud.revisions[name] or ''):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500257 if ud.revisions[name]:
258 ud.unresolvedrev[name] = ud.revisions[name]
259 ud.revisions[name] = self.latest_revision(ud, d, name)
260
Andrew Geisslerc3d88e42020-10-02 09:45:00 -0500261 gitsrcname = '%s%s' % (ud.host.replace(':', '.'), ud.path.replace('/', '.').replace('*', '.').replace(' ','_'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500262 if gitsrcname.startswith('.'):
263 gitsrcname = gitsrcname[1:]
264
Patrick Williams03907ee2022-05-01 06:28:52 -0500265 # For a rebaseable git repo, it is necessary to keep a mirror tar ball
266 # per revision, so that even if the revision disappears from the
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500267 # upstream repo in the future, the mirror will remain intact and still
Patrick Williams03907ee2022-05-01 06:28:52 -0500268 # contain the revision
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500269 if ud.rebaseable:
270 for name in ud.names:
271 gitsrcname = gitsrcname + '_' + ud.revisions[name]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500272
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500273 dl_dir = d.getVar("DL_DIR")
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800274 gitdir = d.getVar("GITDIR") or (dl_dir + "/git2")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500275 ud.clonedir = os.path.join(gitdir, gitsrcname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500276 ud.localfile = ud.clonedir
277
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500278 mirrortarball = 'git2_%s.tar.gz' % gitsrcname
279 ud.fullmirror = os.path.join(dl_dir, mirrortarball)
280 ud.mirrortarballs = [mirrortarball]
281 if ud.shallow:
282 tarballname = gitsrcname
283 if ud.bareclone:
284 tarballname = "%s_bare" % tarballname
285
286 if ud.shallow_revs:
287 tarballname = "%s_%s" % (tarballname, "_".join(sorted(ud.shallow_revs)))
288
289 for name, revision in sorted(ud.revisions.items()):
290 tarballname = "%s_%s" % (tarballname, ud.revisions[name][:7])
291 depth = ud.shallow_depths[name]
292 if depth:
293 tarballname = "%s-%s" % (tarballname, depth)
294
295 shallow_refs = []
296 if not ud.nobranch:
297 shallow_refs.extend(ud.branches.values())
298 if ud.shallow_extra_refs:
299 shallow_refs.extend(r.replace('refs/heads/', '').replace('*', 'ALL') for r in ud.shallow_extra_refs)
300 if shallow_refs:
301 tarballname = "%s_%s" % (tarballname, "_".join(sorted(shallow_refs)).replace('/', '.'))
302
303 fetcher = self.__class__.__name__.lower()
304 ud.shallowtarball = '%sshallow_%s.tar.gz' % (fetcher, tarballname)
305 ud.fullshallow = os.path.join(dl_dir, ud.shallowtarball)
306 ud.mirrortarballs.insert(0, ud.shallowtarball)
307
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500308 def localpath(self, ud, d):
309 return ud.clonedir
310
311 def need_update(self, ud, d):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800312 return self.clonedir_need_update(ud, d) or self.shallow_tarball_need_update(ud) or self.tarball_need_update(ud)
313
314 def clonedir_need_update(self, ud, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500315 if not os.path.exists(ud.clonedir):
316 return True
Brad Bishop64c979e2019-11-04 13:55:29 -0500317 if ud.shallow and ud.write_shallow_tarballs and self.clonedir_need_shallow_revs(ud, d):
318 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319 for name in ud.names:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600320 if not self._contains_ref(ud, d, name, ud.clonedir):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500321 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500322 return False
323
Brad Bishop64c979e2019-11-04 13:55:29 -0500324 def clonedir_need_shallow_revs(self, ud, d):
325 for rev in ud.shallow_revs:
326 try:
327 runfetchcmd('%s rev-parse -q --verify %s' % (ud.basecmd, rev), d, quiet=True, workdir=ud.clonedir)
328 except bb.fetch2.FetchError:
329 return rev
330 return None
331
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800332 def shallow_tarball_need_update(self, ud):
333 return ud.shallow and ud.write_shallow_tarballs and not os.path.exists(ud.fullshallow)
334
335 def tarball_need_update(self, ud):
336 return ud.write_tarballs and not os.path.exists(ud.fullmirror)
337
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500338 def try_premirror(self, ud, d):
339 # If we don't do this, updating an existing checkout with only premirrors
340 # is not possible
Brad Bishop19323692019-04-05 15:28:33 -0400341 if bb.utils.to_boolean(d.getVar("BB_FETCH_PREMIRRORONLY")):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500342 return True
343 if os.path.exists(ud.clonedir):
344 return False
345 return True
346
347 def download(self, ud, d):
348 """Fetch url"""
349
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500350 # A current clone is preferred to either tarball, a shallow tarball is
351 # preferred to an out of date clone, and a missing clone will use
352 # either tarball.
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800353 if ud.shallow and os.path.exists(ud.fullshallow) and self.need_update(ud, d):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500354 ud.localpath = ud.fullshallow
355 return
Andrew Geissler78b72792022-06-14 06:47:25 -0500356 elif os.path.exists(ud.fullmirror) and self.need_update(ud, d):
357 if not os.path.exists(ud.clonedir):
358 bb.utils.mkdirhier(ud.clonedir)
359 runfetchcmd("tar -xzf %s" % ud.fullmirror, d, workdir=ud.clonedir)
360 else:
361 tmpdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
362 runfetchcmd("tar -xzf %s" % ud.fullmirror, d, workdir=tmpdir)
363 fetch_cmd = "LANG=C %s fetch -f --progress %s " % (ud.basecmd, shlex.quote(tmpdir))
364 runfetchcmd(fetch_cmd, d, workdir=ud.clonedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500365 repourl = self._get_repo_url(ud)
366
367 # If the repo still doesn't exist, fallback to cloning it
368 if not os.path.exists(ud.clonedir):
369 # We do this since git will use a "-l" option automatically for local urls where possible
370 if repourl.startswith("file://"):
371 repourl = repourl[7:]
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500372 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 -0500373 if ud.proto.lower() != 'file':
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500374 bb.fetch2.check_network_access(d, clone_cmd, ud.url)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600375 progresshandler = GitProgressHandler(d)
376 runfetchcmd(clone_cmd, d, log=progresshandler)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500377
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500378 # Update the checkout if needed
Brad Bishop64c979e2019-11-04 13:55:29 -0500379 if self.clonedir_need_update(ud, d):
Brad Bishop6ef32652018-10-09 18:59:25 +0100380 output = runfetchcmd("%s remote" % ud.basecmd, d, quiet=True, workdir=ud.clonedir)
381 if "origin" in output:
382 runfetchcmd("%s remote rm origin" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500383
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500384 runfetchcmd("%s remote add --mirror=fetch origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=ud.clonedir)
385 fetch_cmd = "LANG=C %s fetch -f --progress %s refs/*:refs/*" % (ud.basecmd, shlex.quote(repourl))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500386 if ud.proto.lower() != 'file':
387 bb.fetch2.check_network_access(d, fetch_cmd, ud.url)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600388 progresshandler = GitProgressHandler(d)
389 runfetchcmd(fetch_cmd, d, log=progresshandler, workdir=ud.clonedir)
390 runfetchcmd("%s prune-packed" % ud.basecmd, d, workdir=ud.clonedir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400391 runfetchcmd("%s pack-refs --all" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600392 runfetchcmd("%s pack-redundant --all | xargs -r rm" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsd7e96312015-09-22 08:09:05 -0500393 try:
394 os.unlink(ud.fullmirror)
395 except OSError as exc:
396 if exc.errno != errno.ENOENT:
397 raise
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800398
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500399 for name in ud.names:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600400 if not self._contains_ref(ud, d, name, ud.clonedir):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500401 raise bb.fetch2.FetchError("Unable to find revision %s in branch %s even from upstream" % (ud.revisions[name], ud.branches[name]))
402
Brad Bishop64c979e2019-11-04 13:55:29 -0500403 if ud.shallow and ud.write_shallow_tarballs:
404 missing_rev = self.clonedir_need_shallow_revs(ud, d)
405 if missing_rev:
406 raise bb.fetch2.FetchError("Unable to find revision %s even from upstream" % missing_rev)
407
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600408 if self._contains_lfs(ud, d, ud.clonedir) and self._need_lfs(ud):
409 # Unpack temporary working copy, use it to run 'git checkout' to force pre-fetching
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000410 # of all LFS blobs needed at the srcrev.
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600411 #
412 # It would be nice to just do this inline here by running 'git-lfs fetch'
413 # on the bare clonedir, but that operation requires a working copy on some
414 # releases of Git LFS.
415 tmpdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
416 try:
417 # Do the checkout. This implicitly involves a Git LFS fetch.
Andrew Geisslerc926e172021-05-07 16:11:35 -0500418 Git.unpack(self, ud, tmpdir, d)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600419
420 # Scoop up a copy of any stuff that Git LFS downloaded. Merge them into
421 # the bare clonedir.
422 #
423 # As this procedure is invoked repeatedly on incremental fetches as
424 # a recipe's SRCREV is bumped throughout its lifetime, this will
425 # result in a gradual accumulation of LFS blobs in <ud.clonedir>/lfs
426 # corresponding to all the blobs reachable from the different revs
427 # fetched across time.
428 #
429 # Only do this if the unpack resulted in a .git/lfs directory being
430 # created; this only happens if at least one blob needed to be
431 # downloaded.
432 if os.path.exists(os.path.join(tmpdir, "git", ".git", "lfs")):
433 runfetchcmd("tar -cf - lfs | tar -xf - -C %s" % ud.clonedir, d, workdir="%s/git/.git" % tmpdir)
434 finally:
435 bb.utils.remove(tmpdir, recurse=True)
436
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500437 def build_mirror_data(self, ud, d):
Andrew Geissler5199d832021-09-24 16:47:35 -0500438
439 # Create as a temp file and move atomically into position to avoid races
440 @contextmanager
441 def create_atomic(filename):
442 fd, tfile = tempfile.mkstemp(dir=os.path.dirname(filename))
443 try:
444 yield tfile
445 umask = os.umask(0o666)
446 os.umask(umask)
447 os.chmod(tfile, (0o666 & ~umask))
448 os.rename(tfile, filename)
449 finally:
450 os.close(fd)
451
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500452 if ud.shallow and ud.write_shallow_tarballs:
453 if not os.path.exists(ud.fullshallow):
454 if os.path.islink(ud.fullshallow):
455 os.unlink(ud.fullshallow)
456 tempdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
457 shallowclone = os.path.join(tempdir, 'git')
458 try:
459 self.clone_shallow_local(ud, shallowclone, d)
460
461 logger.info("Creating tarball of git repository")
Andrew Geissler5199d832021-09-24 16:47:35 -0500462 with create_atomic(ud.fullshallow) as tfile:
463 runfetchcmd("tar -czf %s ." % tfile, d, workdir=shallowclone)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500464 runfetchcmd("touch %s.done" % ud.fullshallow, d)
465 finally:
466 bb.utils.remove(tempdir, recurse=True)
467 elif ud.write_tarballs and not os.path.exists(ud.fullmirror):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500468 if os.path.islink(ud.fullmirror):
469 os.unlink(ud.fullmirror)
470
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500471 logger.info("Creating tarball of git repository")
Andrew Geissler5199d832021-09-24 16:47:35 -0500472 with create_atomic(ud.fullmirror) as tfile:
Andrew Geissler9aee5002022-03-30 16:27:02 +0000473 mtime = runfetchcmd("git log --all -1 --format=%cD", d,
474 quiet=True, workdir=ud.clonedir)
Patrick Williams03907ee2022-05-01 06:28:52 -0500475 runfetchcmd("tar -czf %s --owner oe:0 --group oe:0 --mtime \"%s\" ."
Andrew Geissler9aee5002022-03-30 16:27:02 +0000476 % (tfile, mtime), d, workdir=ud.clonedir)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500477 runfetchcmd("touch %s.done" % ud.fullmirror, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500478
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500479 def clone_shallow_local(self, ud, dest, d):
480 """Clone the repo and make it shallow.
481
482 The upstream url of the new clone isn't set at this time, as it'll be
483 set correctly when unpacked."""
484 runfetchcmd("%s clone %s %s %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, dest), d)
485
486 to_parse, shallow_branches = [], []
487 for name in ud.names:
488 revision = ud.revisions[name]
489 depth = ud.shallow_depths[name]
490 if depth:
491 to_parse.append('%s~%d^{}' % (revision, depth - 1))
492
493 # For nobranch, we need a ref, otherwise the commits will be
494 # removed, and for non-nobranch, we truncate the branch to our
495 # srcrev, to avoid keeping unnecessary history beyond that.
496 branch = ud.branches[name]
497 if ud.nobranch:
498 ref = "refs/shallow/%s" % name
499 elif ud.bareclone:
500 ref = "refs/heads/%s" % branch
501 else:
502 ref = "refs/remotes/origin/%s" % branch
503
504 shallow_branches.append(ref)
505 runfetchcmd("%s update-ref %s %s" % (ud.basecmd, ref, revision), d, workdir=dest)
506
507 # Map srcrev+depths to revisions
508 parsed_depths = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join(to_parse)), d, workdir=dest)
509
510 # Resolve specified revisions
511 parsed_revs = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join('"%s^{}"' % r for r in ud.shallow_revs)), d, workdir=dest)
512 shallow_revisions = parsed_depths.splitlines() + parsed_revs.splitlines()
513
514 # Apply extra ref wildcards
515 all_refs = runfetchcmd('%s for-each-ref "--format=%%(refname)"' % ud.basecmd,
516 d, workdir=dest).splitlines()
517 for r in ud.shallow_extra_refs:
518 if not ud.bareclone:
519 r = r.replace('refs/heads/', 'refs/remotes/origin/')
520
521 if '*' in r:
522 matches = filter(lambda a: fnmatch.fnmatchcase(a, r), all_refs)
523 shallow_branches.extend(matches)
524 else:
525 shallow_branches.append(r)
526
527 # Make the repository shallow
Brad Bishop316dfdd2018-06-25 12:45:53 -0400528 shallow_cmd = [self.make_shallow_path, '-s']
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500529 for b in shallow_branches:
530 shallow_cmd.append('-r')
531 shallow_cmd.append(b)
532 shallow_cmd.extend(shallow_revisions)
533 runfetchcmd(subprocess.list2cmdline(shallow_cmd), d, workdir=dest)
534
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500535 def unpack(self, ud, destdir, d):
536 """ unpack the downloaded src to destdir"""
537
Andrew Geissler595f6302022-01-24 19:11:47 +0000538 subdir = ud.parm.get("subdir")
539 subpath = ud.parm.get("subpath")
540 readpathspec = ""
541 def_destsuffix = "git/"
542
543 if subpath:
544 readpathspec = ":%s" % subpath
545 def_destsuffix = "%s/" % os.path.basename(subpath.rstrip('/'))
546
547 if subdir:
548 # If 'subdir' param exists, create a dir and use it as destination for unpack cmd
549 if os.path.isabs(subdir):
550 if not os.path.realpath(subdir).startswith(os.path.realpath(destdir)):
551 raise bb.fetch2.UnpackError("subdir argument isn't a subdirectory of unpack root %s" % destdir, ud.url)
552 destdir = subdir
553 else:
554 destdir = os.path.join(destdir, subdir)
555 def_destsuffix = ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500556
557 destsuffix = ud.parm.get("destsuffix", def_destsuffix)
558 destdir = ud.destdir = os.path.join(destdir, destsuffix)
559 if os.path.exists(destdir):
560 bb.utils.prunedir(destdir)
561
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600562 need_lfs = self._need_lfs(ud)
Brad Bishopa34c0302019-09-23 22:34:48 -0400563
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500564 if not need_lfs:
565 ud.basecmd = "GIT_LFS_SKIP_SMUDGE=1 " + ud.basecmd
566
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800567 source_found = False
568 source_error = []
569
Andrew Geissler87f5cff2022-09-30 13:13:31 -0500570 clonedir_is_up_to_date = not self.clonedir_need_update(ud, d)
571 if clonedir_is_up_to_date:
572 runfetchcmd("%s clone %s %s/ %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, destdir), d)
573 source_found = True
574 else:
575 source_error.append("clone directory not available or not up to date: " + ud.clonedir)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800576
577 if not source_found:
578 if ud.shallow:
579 if os.path.exists(ud.fullshallow):
580 bb.utils.mkdirhier(destdir)
581 runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=destdir)
582 source_found = True
583 else:
584 source_error.append("shallow clone not available: " + ud.fullshallow)
585 else:
586 source_error.append("shallow clone not enabled")
587
588 if not source_found:
589 raise bb.fetch2.UnpackError("No up to date source found: " + "; ".join(source_error), ud.url)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500590
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500591 repourl = self._get_repo_url(ud)
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500592 runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=destdir)
Brad Bishopc342db32019-05-15 21:57:59 -0400593
594 if self._contains_lfs(ud, d, destdir):
Brad Bishop00e122a2019-10-05 11:10:57 -0400595 if need_lfs and not self._find_git_lfs(d):
596 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 -0500597 elif not need_lfs:
Brad Bishopa34c0302019-09-23 22:34:48 -0400598 bb.note("Repository %s has LFS content but it is not being fetched" % (repourl))
Brad Bishopc342db32019-05-15 21:57:59 -0400599
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500600 if not ud.nocheckout:
Andrew Geissler595f6302022-01-24 19:11:47 +0000601 if subpath:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600602 runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d,
603 workdir=destdir)
604 runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500605 elif not ud.nobranch:
606 branchname = ud.branches[ud.names[0]]
607 runfetchcmd("%s checkout -B %s %s" % (ud.basecmd, branchname, \
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600608 ud.revisions[ud.names[0]]), d, workdir=destdir)
Andre Rosa49271d42017-09-07 11:15:55 +0200609 runfetchcmd("%s branch %s --set-upstream-to origin/%s" % (ud.basecmd, branchname, \
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600610 branchname), d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500611 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600612 runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revisions[ud.names[0]]), d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500613
614 return True
615
616 def clean(self, ud, d):
617 """ clean the git directory """
618
Brad Bishop19323692019-04-05 15:28:33 -0400619 to_remove = [ud.localpath, ud.fullmirror, ud.fullmirror + ".done"]
620 # The localpath is a symlink to clonedir when it is cloned from a
621 # mirror, so remove both of them.
622 if os.path.islink(ud.localpath):
623 clonedir = os.path.realpath(ud.localpath)
624 to_remove.append(clonedir)
625
626 for r in to_remove:
627 if os.path.exists(r):
628 bb.note('Removing %s' % r)
629 bb.utils.remove(r, True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500630
631 def supports_srcrev(self):
632 return True
633
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600634 def _contains_ref(self, ud, d, name, wd):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500635 cmd = ""
636 if ud.nobranch:
637 cmd = "%s log --pretty=oneline -n 1 %s -- 2> /dev/null | wc -l" % (
638 ud.basecmd, ud.revisions[name])
639 else:
640 cmd = "%s branch --contains %s --list %s 2> /dev/null | wc -l" % (
641 ud.basecmd, ud.revisions[name], ud.branches[name])
642 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600643 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500644 except bb.fetch2.FetchError:
645 return False
646 if len(output.split()) > 1:
647 raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
648 return output.split()[0] != "0"
649
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600650 def _need_lfs(self, ud):
651 return ud.parm.get("lfs", "1") == "1"
652
Brad Bishopc342db32019-05-15 21:57:59 -0400653 def _contains_lfs(self, ud, d, wd):
654 """
655 Check if the repository has 'lfs' (large file) content
656 """
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500657
658 if not ud.nobranch:
659 branchname = ud.branches[ud.names[0]]
660 else:
661 branchname = "master"
662
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600663 # The bare clonedir doesn't use the remote names; it has the branch immediately.
664 if wd == ud.clonedir:
665 refname = ud.branches[ud.names[0]]
666 else:
667 refname = "origin/%s" % ud.branches[ud.names[0]]
668
669 cmd = "%s grep lfs %s:.gitattributes | wc -l" % (
670 ud.basecmd, refname)
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500671
Brad Bishopc342db32019-05-15 21:57:59 -0400672 try:
673 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
674 if int(output) > 0:
675 return True
676 except (bb.fetch2.FetchError,ValueError):
677 pass
678 return False
679
Brad Bishop00e122a2019-10-05 11:10:57 -0400680 def _find_git_lfs(self, d):
681 """
682 Return True if git-lfs can be found, False otherwise.
683 """
684 import shutil
685 return shutil.which("git-lfs", path=d.getVar('PATH')) is not None
686
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500687 def _get_repo_url(self, ud):
688 """
689 Return the repository URL
690 """
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600691 # Note that we do not support passwords directly in the git urls. There are several
692 # reasons. SRC_URI can be written out to things like buildhistory and people don't
693 # want to leak passwords like that. Its also all too easy to share metadata without
694 # removing the password. ssh keys, ~/.netrc and ~/.ssh/config files can be used as
695 # alternatives so we will not take patches adding password support here.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500696 if ud.user:
697 username = ud.user + '@'
698 else:
699 username = ""
700 return "%s://%s%s%s" % (ud.proto, username, ud.host, ud.path)
701
702 def _revision_key(self, ud, d, name):
703 """
704 Return a unique key for the url
705 """
Andrew Geissler82c905d2020-04-13 13:39:40 -0500706 # Collapse adjacent slashes
Andrew Geissler82c905d2020-04-13 13:39:40 -0500707 return "git:" + ud.host + slash_re.sub(".", ud.path) + ud.unresolvedrev[name]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500708
709 def _lsremote(self, ud, d, search):
710 """
711 Run git ls-remote with the specified search string
712 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500713 # Prevent recursion e.g. in OE if SRCPV is in PV, PV is in WORKDIR,
714 # and WORKDIR is in PATH (as a result of RSS), our call to
715 # runfetchcmd() exports PATH so this function will get called again (!)
716 # In this scenario the return call of the function isn't actually
717 # important - WORKDIR isn't needed in PATH to call git ls-remote
718 # anyway.
719 if d.getVar('_BB_GIT_IN_LSREMOTE', False):
720 return ''
721 d.setVar('_BB_GIT_IN_LSREMOTE', '1')
722 try:
723 repourl = self._get_repo_url(ud)
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500724 cmd = "%s ls-remote %s %s" % \
725 (ud.basecmd, shlex.quote(repourl), search)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500726 if ud.proto.lower() != 'file':
727 bb.fetch2.check_network_access(d, cmd, repourl)
728 output = runfetchcmd(cmd, d, True)
729 if not output:
730 raise bb.fetch2.FetchError("The command %s gave empty output unexpectedly" % cmd, ud.url)
731 finally:
732 d.delVar('_BB_GIT_IN_LSREMOTE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500733 return output
734
735 def _latest_revision(self, ud, d, name):
736 """
737 Compute the HEAD revision for the url
738 """
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000739 if not d.getVar("__BBSEENSRCREV"):
Andrew Geissler615f2f12022-07-15 14:00:58 -0500740 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 +0000741
742 # Ensure we mark as not cached
743 bb.fetch2.get_autorev(d)
744
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500745 output = self._lsremote(ud, d, "")
746 # Tags of the form ^{} may not work, need to fallback to other form
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600747 if ud.unresolvedrev[name][:5] == "refs/" or ud.usehead:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500748 head = ud.unresolvedrev[name]
749 tag = ud.unresolvedrev[name]
750 else:
751 head = "refs/heads/%s" % ud.unresolvedrev[name]
752 tag = "refs/tags/%s" % ud.unresolvedrev[name]
753 for s in [head, tag + "^{}", tag]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600754 for l in output.strip().split('\n'):
755 sha1, ref = l.split()
756 if s == ref:
757 return sha1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500758 raise bb.fetch2.FetchError("Unable to resolve '%s' in upstream git repository in git ls-remote output for %s" % \
759 (ud.unresolvedrev[name], ud.host+ud.path))
760
761 def latest_versionstring(self, ud, d):
762 """
763 Compute the latest release name like "x.y.x" in "x.y.x+gitHASH"
764 by searching through the tags output of ls-remote, comparing
765 versions and returning the highest match.
766 """
767 pupver = ('', '')
768
Brad Bishop19323692019-04-05 15:28:33 -0400769 tagregex = re.compile(d.getVar('UPSTREAM_CHECK_GITTAGREGEX') or r"(?P<pver>([0-9][\.|_]?)+)")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500770 try:
771 output = self._lsremote(ud, d, "refs/tags/*")
Brad Bishop316dfdd2018-06-25 12:45:53 -0400772 except (bb.fetch2.FetchError, bb.fetch2.NetworkAccess) as e:
773 bb.note("Could not list remote: %s" % str(e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500774 return pupver
775
776 verstring = ""
777 revision = ""
778 for line in output.split("\n"):
779 if not line:
780 break
781
782 tag_head = line.split("/")[-1]
783 # Ignore non-released branches
Brad Bishop19323692019-04-05 15:28:33 -0400784 m = re.search(r"(alpha|beta|rc|final)+", tag_head)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500785 if m:
786 continue
787
788 # search for version in the line
789 tag = tagregex.search(tag_head)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500790 if tag is None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500791 continue
792
793 tag = tag.group('pver')
794 tag = tag.replace("_", ".")
795
796 if verstring and bb.utils.vercmp(("0", tag, ""), ("0", verstring, "")) < 0:
797 continue
798
799 verstring = tag
800 revision = line.split()[0]
801 pupver = (verstring, revision)
802
803 return pupver
804
805 def _build_revision(self, ud, d, name):
806 return ud.revisions[name]
807
808 def gitpkgv_revision(self, ud, d, name):
809 """
810 Return a sortable revision number by counting commits in the history
811 Based on gitpkgv.bblass in meta-openembedded
812 """
813 rev = self._build_revision(ud, d, name)
814 localpath = ud.localpath
815 rev_file = os.path.join(localpath, "oe-gitpkgv_" + rev)
816 if not os.path.exists(localpath):
817 commits = None
818 else:
819 if not os.path.exists(rev_file) or not os.path.getsize(rev_file):
820 from pipes import quote
821 commits = bb.fetch2.runfetchcmd(
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500822 "git rev-list %s -- | wc -l" % quote(rev),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500823 d, quiet=True).strip().lstrip('0')
824 if commits:
825 open(rev_file, "w").write("%d\n" % int(commits))
826 else:
827 commits = open(rev_file, "r").readline(128).strip()
828 if commits:
829 return False, "%s+%s" % (commits, rev[:7])
830 else:
831 return True, str(rev)
832
833 def checkstatus(self, fetch, ud, d):
834 try:
835 self._lsremote(ud, d, "")
836 return True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500837 except bb.fetch2.FetchError:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500838 return False