blob: 2e3d32515fa121675e73768865c3593314d47b11 [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 Williamsc0f7c042017-02-23 20:41:17 -060051- usehead
Brad Bishop6e60e8b2018-02-01 10:27:11 -050052 For local git:// urls to use the current branch HEAD as the revision for use with
Patrick Williamsc0f7c042017-02-23 20:41:17 -060053 AUTOREV. Implies nobranch.
54
Patrick Williamsc124f4f2015-09-15 14:41:29 -050055"""
56
Brad Bishopc342db32019-05-15 21:57:59 -040057# Copyright (C) 2005 Richard Purdie
Patrick Williamsc124f4f2015-09-15 14:41:29 -050058#
Brad Bishopc342db32019-05-15 21:57:59 -040059# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -050060#
Patrick Williamsc124f4f2015-09-15 14:41:29 -050061
Brad Bishopd7bf8c12018-02-25 22:55:05 -050062import collections
Patrick Williamsd7e96312015-09-22 08:09:05 -050063import errno
Brad Bishopd7bf8c12018-02-25 22:55:05 -050064import fnmatch
Patrick Williamsc124f4f2015-09-15 14:41:29 -050065import os
66import re
Andrew Geissler4c19ea12020-10-27 13:52:24 -050067import shlex
Brad Bishopd7bf8c12018-02-25 22:55:05 -050068import subprocess
69import tempfile
Patrick Williamsc124f4f2015-09-15 14:41:29 -050070import bb
Patrick Williamsc0f7c042017-02-23 20:41:17 -060071import bb.progress
Andrew Geissler5199d832021-09-24 16:47:35 -050072from contextlib import contextmanager
Patrick Williamsc124f4f2015-09-15 14:41:29 -050073from bb.fetch2 import FetchMethod
74from bb.fetch2 import runfetchcmd
75from bb.fetch2 import logger
76
Patrick Williamsc0f7c042017-02-23 20:41:17 -060077
Patrick Williams03907ee2022-05-01 06:28:52 -050078sha1_re = re.compile(r'^[0-9a-f]{40}$')
79slash_re = re.compile(r"/+")
80
Patrick Williamsc0f7c042017-02-23 20:41:17 -060081class GitProgressHandler(bb.progress.LineFilterProgressHandler):
82 """Extract progress information from git output"""
83 def __init__(self, d):
84 self._buffer = ''
85 self._count = 0
86 super(GitProgressHandler, self).__init__(d)
87 # Send an initial progress event so the bar gets shown
88 self._fire_progress(-1)
89
90 def write(self, string):
91 self._buffer += string
92 stages = ['Counting objects', 'Compressing objects', 'Receiving objects', 'Resolving deltas']
93 stage_weights = [0.2, 0.05, 0.5, 0.25]
94 stagenum = 0
95 for i, stage in reversed(list(enumerate(stages))):
96 if stage in self._buffer:
97 stagenum = i
98 self._buffer = ''
99 break
100 self._status = stages[stagenum]
101 percs = re.findall(r'(\d+)%', string)
102 if percs:
103 progress = int(round((int(percs[-1]) * stage_weights[stagenum]) + (sum(stage_weights[:stagenum]) * 100)))
104 rates = re.findall(r'([\d.]+ [a-zA-Z]*/s+)', string)
105 if rates:
106 rate = rates[-1]
107 else:
108 rate = None
109 self.update(progress, rate)
110 else:
111 if stagenum == 0:
112 percs = re.findall(r': (\d+)', string)
113 if percs:
114 count = int(percs[-1])
115 if count > self._count:
116 self._count = count
117 self._fire_progress(-count)
118 super(GitProgressHandler, self).write(string)
119
120
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500121class Git(FetchMethod):
Brad Bishop316dfdd2018-06-25 12:45:53 -0400122 bitbake_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.join(os.path.abspath(__file__))), '..', '..', '..'))
123 make_shallow_path = os.path.join(bitbake_dir, 'bin', 'git-make-shallow')
124
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500125 """Class to fetch a module or modules from git repositories"""
126 def init(self, d):
127 pass
128
129 def supports(self, ud, d):
130 """
131 Check to see if a given url can be fetched with git.
132 """
133 return ud.type in ['git']
134
135 def supports_checksum(self, urldata):
136 return False
137
138 def urldata_init(self, ud, d):
139 """
140 init git specific variable within url data
141 so that the git method like latest_revision() can work
142 """
143 if 'protocol' in ud.parm:
144 ud.proto = ud.parm['protocol']
145 elif not ud.host:
146 ud.proto = 'file'
147 else:
148 ud.proto = "git"
Andrew Geissler595f6302022-01-24 19:11:47 +0000149 if ud.host == "github.com" and ud.proto == "git":
150 # github stopped supporting git protocol
151 # https://github.blog/2021-09-01-improving-git-protocol-security-github/#no-more-unauthenticated-git
152 ud.proto = "https"
153 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 -0500154
155 if not ud.proto in ('git', 'file', 'ssh', 'http', 'https', 'rsync'):
156 raise bb.fetch2.ParameterError("Invalid protocol type", ud.url)
157
158 ud.nocheckout = ud.parm.get("nocheckout","0") == "1"
159
160 ud.rebaseable = ud.parm.get("rebaseable","0") == "1"
161
162 ud.nobranch = ud.parm.get("nobranch","0") == "1"
163
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600164 # usehead implies nobranch
165 ud.usehead = ud.parm.get("usehead","0") == "1"
166 if ud.usehead:
167 if ud.proto != "file":
168 raise bb.fetch2.ParameterError("The usehead option is only for use with local ('protocol=file') git repositories", ud.url)
169 ud.nobranch = 1
170
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500171 # bareclone implies nocheckout
172 ud.bareclone = ud.parm.get("bareclone","0") == "1"
173 if ud.bareclone:
174 ud.nocheckout = 1
175
176 ud.unresolvedrev = {}
Andrew Geissler595f6302022-01-24 19:11:47 +0000177 branches = ud.parm.get("branch", "").split(',')
178 if branches == [""] and not ud.nobranch:
179 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)
180 branches = ["master"]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500181 if len(branches) != len(ud.names):
182 raise bb.fetch2.ParameterError("The number of name and branch parameters is not balanced", ud.url)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500183
Andrew Geisslerc926e172021-05-07 16:11:35 -0500184 ud.noshared = d.getVar("BB_GIT_NOSHARED") == "1"
185
186 ud.cloneflags = "-n"
187 if not ud.noshared:
188 ud.cloneflags += " -s"
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500189 if ud.bareclone:
190 ud.cloneflags += " --mirror"
191
192 ud.shallow = d.getVar("BB_GIT_SHALLOW") == "1"
193 ud.shallow_extra_refs = (d.getVar("BB_GIT_SHALLOW_EXTRA_REFS") or "").split()
194
195 depth_default = d.getVar("BB_GIT_SHALLOW_DEPTH")
196 if depth_default is not None:
197 try:
198 depth_default = int(depth_default or 0)
199 except ValueError:
200 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH: %s" % depth_default)
201 else:
202 if depth_default < 0:
203 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH: %s" % depth_default)
204 else:
205 depth_default = 1
206 ud.shallow_depths = collections.defaultdict(lambda: depth_default)
207
Brad Bishop19323692019-04-05 15:28:33 -0400208 revs_default = d.getVar("BB_GIT_SHALLOW_REVS")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500209 ud.shallow_revs = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500210 ud.branches = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500211 for pos, name in enumerate(ud.names):
212 branch = branches[pos]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500213 ud.branches[name] = branch
214 ud.unresolvedrev[name] = branch
215
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500216 shallow_depth = d.getVar("BB_GIT_SHALLOW_DEPTH_%s" % name)
217 if shallow_depth is not None:
218 try:
219 shallow_depth = int(shallow_depth or 0)
220 except ValueError:
221 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth))
222 else:
223 if shallow_depth < 0:
224 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth))
225 ud.shallow_depths[name] = shallow_depth
226
227 revs = d.getVar("BB_GIT_SHALLOW_REVS_%s" % name)
228 if revs is not None:
229 ud.shallow_revs.extend(revs.split())
230 elif revs_default is not None:
231 ud.shallow_revs.extend(revs_default.split())
232
233 if (ud.shallow and
234 not ud.shallow_revs and
235 all(ud.shallow_depths[n] == 0 for n in ud.names)):
236 # Shallow disabled for this URL
237 ud.shallow = False
238
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600239 if ud.usehead:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600240 # When usehead is set let's associate 'HEAD' with the unresolved
241 # rev of this repository. This will get resolved into a revision
242 # later. If an actual revision happens to have also been provided
243 # then this setting will be overridden.
244 for name in ud.names:
245 ud.unresolvedrev[name] = 'HEAD'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600246
Patrick Williams2390b1b2022-11-03 13:47:49 -0500247 ud.basecmd = d.getVar("FETCHCMD_git") or "git -c gc.autoDetach=false -c core.pager=cat"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500248
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500249 write_tarballs = d.getVar("BB_GENERATE_MIRROR_TARBALLS") or "0"
250 ud.write_tarballs = write_tarballs != "0" or ud.rebaseable
251 ud.write_shallow_tarballs = (d.getVar("BB_GENERATE_SHALLOW_TARBALLS") or write_tarballs) != "0"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500252
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500253 ud.setup_revisions(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254
255 for name in ud.names:
Patrick Williams03907ee2022-05-01 06:28:52 -0500256 # Ensure any revision that doesn't look like a SHA-1 is translated into one
257 if not sha1_re.match(ud.revisions[name] or ''):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500258 if ud.revisions[name]:
259 ud.unresolvedrev[name] = ud.revisions[name]
260 ud.revisions[name] = self.latest_revision(ud, d, name)
261
Andrew Geisslerc3d88e42020-10-02 09:45:00 -0500262 gitsrcname = '%s%s' % (ud.host.replace(':', '.'), ud.path.replace('/', '.').replace('*', '.').replace(' ','_'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500263 if gitsrcname.startswith('.'):
264 gitsrcname = gitsrcname[1:]
265
Patrick Williams03907ee2022-05-01 06:28:52 -0500266 # For a rebaseable git repo, it is necessary to keep a mirror tar ball
267 # per revision, so that even if the revision disappears from the
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500268 # upstream repo in the future, the mirror will remain intact and still
Patrick Williams03907ee2022-05-01 06:28:52 -0500269 # contain the revision
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500270 if ud.rebaseable:
271 for name in ud.names:
272 gitsrcname = gitsrcname + '_' + ud.revisions[name]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500273
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500274 dl_dir = d.getVar("DL_DIR")
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800275 gitdir = d.getVar("GITDIR") or (dl_dir + "/git2")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500276 ud.clonedir = os.path.join(gitdir, gitsrcname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500277 ud.localfile = ud.clonedir
278
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500279 mirrortarball = 'git2_%s.tar.gz' % gitsrcname
280 ud.fullmirror = os.path.join(dl_dir, mirrortarball)
281 ud.mirrortarballs = [mirrortarball]
282 if ud.shallow:
283 tarballname = gitsrcname
284 if ud.bareclone:
285 tarballname = "%s_bare" % tarballname
286
287 if ud.shallow_revs:
288 tarballname = "%s_%s" % (tarballname, "_".join(sorted(ud.shallow_revs)))
289
290 for name, revision in sorted(ud.revisions.items()):
291 tarballname = "%s_%s" % (tarballname, ud.revisions[name][:7])
292 depth = ud.shallow_depths[name]
293 if depth:
294 tarballname = "%s-%s" % (tarballname, depth)
295
296 shallow_refs = []
297 if not ud.nobranch:
298 shallow_refs.extend(ud.branches.values())
299 if ud.shallow_extra_refs:
300 shallow_refs.extend(r.replace('refs/heads/', '').replace('*', 'ALL') for r in ud.shallow_extra_refs)
301 if shallow_refs:
302 tarballname = "%s_%s" % (tarballname, "_".join(sorted(shallow_refs)).replace('/', '.'))
303
304 fetcher = self.__class__.__name__.lower()
305 ud.shallowtarball = '%sshallow_%s.tar.gz' % (fetcher, tarballname)
306 ud.fullshallow = os.path.join(dl_dir, ud.shallowtarball)
307 ud.mirrortarballs.insert(0, ud.shallowtarball)
308
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500309 def localpath(self, ud, d):
310 return ud.clonedir
311
312 def need_update(self, ud, d):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800313 return self.clonedir_need_update(ud, d) or self.shallow_tarball_need_update(ud) or self.tarball_need_update(ud)
314
315 def clonedir_need_update(self, ud, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500316 if not os.path.exists(ud.clonedir):
317 return True
Brad Bishop64c979e2019-11-04 13:55:29 -0500318 if ud.shallow and ud.write_shallow_tarballs and self.clonedir_need_shallow_revs(ud, d):
319 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500320 for name in ud.names:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600321 if not self._contains_ref(ud, d, name, ud.clonedir):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500322 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500323 return False
324
Brad Bishop64c979e2019-11-04 13:55:29 -0500325 def clonedir_need_shallow_revs(self, ud, d):
326 for rev in ud.shallow_revs:
327 try:
328 runfetchcmd('%s rev-parse -q --verify %s' % (ud.basecmd, rev), d, quiet=True, workdir=ud.clonedir)
329 except bb.fetch2.FetchError:
330 return rev
331 return None
332
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800333 def shallow_tarball_need_update(self, ud):
334 return ud.shallow and ud.write_shallow_tarballs and not os.path.exists(ud.fullshallow)
335
336 def tarball_need_update(self, ud):
337 return ud.write_tarballs and not os.path.exists(ud.fullmirror)
338
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500339 def try_premirror(self, ud, d):
340 # If we don't do this, updating an existing checkout with only premirrors
341 # is not possible
Brad Bishop19323692019-04-05 15:28:33 -0400342 if bb.utils.to_boolean(d.getVar("BB_FETCH_PREMIRRORONLY")):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500343 return True
344 if os.path.exists(ud.clonedir):
345 return False
346 return True
347
348 def download(self, ud, d):
349 """Fetch url"""
350
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500351 # A current clone is preferred to either tarball, a shallow tarball is
352 # preferred to an out of date clone, and a missing clone will use
353 # either tarball.
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800354 if ud.shallow and os.path.exists(ud.fullshallow) and self.need_update(ud, d):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500355 ud.localpath = ud.fullshallow
356 return
Andrew Geissler78b72792022-06-14 06:47:25 -0500357 elif os.path.exists(ud.fullmirror) and self.need_update(ud, d):
358 if not os.path.exists(ud.clonedir):
359 bb.utils.mkdirhier(ud.clonedir)
360 runfetchcmd("tar -xzf %s" % ud.fullmirror, d, workdir=ud.clonedir)
361 else:
362 tmpdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
363 runfetchcmd("tar -xzf %s" % ud.fullmirror, d, workdir=tmpdir)
364 fetch_cmd = "LANG=C %s fetch -f --progress %s " % (ud.basecmd, shlex.quote(tmpdir))
365 runfetchcmd(fetch_cmd, d, workdir=ud.clonedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500366 repourl = self._get_repo_url(ud)
367
368 # If the repo still doesn't exist, fallback to cloning it
369 if not os.path.exists(ud.clonedir):
370 # We do this since git will use a "-l" option automatically for local urls where possible
371 if repourl.startswith("file://"):
372 repourl = repourl[7:]
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500373 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 -0500374 if ud.proto.lower() != 'file':
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500375 bb.fetch2.check_network_access(d, clone_cmd, ud.url)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600376 progresshandler = GitProgressHandler(d)
377 runfetchcmd(clone_cmd, d, log=progresshandler)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500378
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500379 # Update the checkout if needed
Brad Bishop64c979e2019-11-04 13:55:29 -0500380 if self.clonedir_need_update(ud, d):
Brad Bishop6ef32652018-10-09 18:59:25 +0100381 output = runfetchcmd("%s remote" % ud.basecmd, d, quiet=True, workdir=ud.clonedir)
382 if "origin" in output:
383 runfetchcmd("%s remote rm origin" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500384
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500385 runfetchcmd("%s remote add --mirror=fetch origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=ud.clonedir)
Andrew Geissler517393d2023-01-13 08:55:19 -0600386
387 if ud.nobranch:
388 fetch_cmd = "LANG=C %s fetch -f --progress %s refs/*:refs/*" % (ud.basecmd, shlex.quote(repourl))
389 else:
390 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 -0500391 if ud.proto.lower() != 'file':
392 bb.fetch2.check_network_access(d, fetch_cmd, ud.url)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600393 progresshandler = GitProgressHandler(d)
394 runfetchcmd(fetch_cmd, d, log=progresshandler, workdir=ud.clonedir)
395 runfetchcmd("%s prune-packed" % ud.basecmd, d, workdir=ud.clonedir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400396 runfetchcmd("%s pack-refs --all" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600397 runfetchcmd("%s pack-redundant --all | xargs -r rm" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsd7e96312015-09-22 08:09:05 -0500398 try:
399 os.unlink(ud.fullmirror)
400 except OSError as exc:
401 if exc.errno != errno.ENOENT:
402 raise
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800403
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500404 for name in ud.names:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600405 if not self._contains_ref(ud, d, name, ud.clonedir):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500406 raise bb.fetch2.FetchError("Unable to find revision %s in branch %s even from upstream" % (ud.revisions[name], ud.branches[name]))
407
Brad Bishop64c979e2019-11-04 13:55:29 -0500408 if ud.shallow and ud.write_shallow_tarballs:
409 missing_rev = self.clonedir_need_shallow_revs(ud, d)
410 if missing_rev:
411 raise bb.fetch2.FetchError("Unable to find revision %s even from upstream" % missing_rev)
412
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600413 if self._contains_lfs(ud, d, ud.clonedir) and self._need_lfs(ud):
414 # Unpack temporary working copy, use it to run 'git checkout' to force pre-fetching
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000415 # of all LFS blobs needed at the srcrev.
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600416 #
417 # It would be nice to just do this inline here by running 'git-lfs fetch'
418 # on the bare clonedir, but that operation requires a working copy on some
419 # releases of Git LFS.
420 tmpdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
421 try:
422 # Do the checkout. This implicitly involves a Git LFS fetch.
Andrew Geisslerc926e172021-05-07 16:11:35 -0500423 Git.unpack(self, ud, tmpdir, d)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600424
425 # Scoop up a copy of any stuff that Git LFS downloaded. Merge them into
426 # the bare clonedir.
427 #
428 # As this procedure is invoked repeatedly on incremental fetches as
429 # a recipe's SRCREV is bumped throughout its lifetime, this will
430 # result in a gradual accumulation of LFS blobs in <ud.clonedir>/lfs
431 # corresponding to all the blobs reachable from the different revs
432 # fetched across time.
433 #
434 # Only do this if the unpack resulted in a .git/lfs directory being
435 # created; this only happens if at least one blob needed to be
436 # downloaded.
437 if os.path.exists(os.path.join(tmpdir, "git", ".git", "lfs")):
438 runfetchcmd("tar -cf - lfs | tar -xf - -C %s" % ud.clonedir, d, workdir="%s/git/.git" % tmpdir)
439 finally:
440 bb.utils.remove(tmpdir, recurse=True)
441
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500442 def build_mirror_data(self, ud, d):
Andrew Geissler5199d832021-09-24 16:47:35 -0500443
444 # Create as a temp file and move atomically into position to avoid races
445 @contextmanager
446 def create_atomic(filename):
447 fd, tfile = tempfile.mkstemp(dir=os.path.dirname(filename))
448 try:
449 yield tfile
450 umask = os.umask(0o666)
451 os.umask(umask)
452 os.chmod(tfile, (0o666 & ~umask))
453 os.rename(tfile, filename)
454 finally:
455 os.close(fd)
456
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500457 if ud.shallow and ud.write_shallow_tarballs:
458 if not os.path.exists(ud.fullshallow):
459 if os.path.islink(ud.fullshallow):
460 os.unlink(ud.fullshallow)
461 tempdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
462 shallowclone = os.path.join(tempdir, 'git')
463 try:
464 self.clone_shallow_local(ud, shallowclone, d)
465
466 logger.info("Creating tarball of git repository")
Andrew Geissler5199d832021-09-24 16:47:35 -0500467 with create_atomic(ud.fullshallow) as tfile:
468 runfetchcmd("tar -czf %s ." % tfile, d, workdir=shallowclone)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500469 runfetchcmd("touch %s.done" % ud.fullshallow, d)
470 finally:
471 bb.utils.remove(tempdir, recurse=True)
472 elif ud.write_tarballs and not os.path.exists(ud.fullmirror):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500473 if os.path.islink(ud.fullmirror):
474 os.unlink(ud.fullmirror)
475
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500476 logger.info("Creating tarball of git repository")
Andrew Geissler5199d832021-09-24 16:47:35 -0500477 with create_atomic(ud.fullmirror) as tfile:
Andrew Geissler9aee5002022-03-30 16:27:02 +0000478 mtime = runfetchcmd("git log --all -1 --format=%cD", d,
479 quiet=True, workdir=ud.clonedir)
Patrick Williams03907ee2022-05-01 06:28:52 -0500480 runfetchcmd("tar -czf %s --owner oe:0 --group oe:0 --mtime \"%s\" ."
Andrew Geissler9aee5002022-03-30 16:27:02 +0000481 % (tfile, mtime), d, workdir=ud.clonedir)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500482 runfetchcmd("touch %s.done" % ud.fullmirror, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500483
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500484 def clone_shallow_local(self, ud, dest, d):
485 """Clone the repo and make it shallow.
486
487 The upstream url of the new clone isn't set at this time, as it'll be
488 set correctly when unpacked."""
489 runfetchcmd("%s clone %s %s %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, dest), d)
490
491 to_parse, shallow_branches = [], []
492 for name in ud.names:
493 revision = ud.revisions[name]
494 depth = ud.shallow_depths[name]
495 if depth:
496 to_parse.append('%s~%d^{}' % (revision, depth - 1))
497
498 # For nobranch, we need a ref, otherwise the commits will be
499 # removed, and for non-nobranch, we truncate the branch to our
500 # srcrev, to avoid keeping unnecessary history beyond that.
501 branch = ud.branches[name]
502 if ud.nobranch:
503 ref = "refs/shallow/%s" % name
504 elif ud.bareclone:
505 ref = "refs/heads/%s" % branch
506 else:
507 ref = "refs/remotes/origin/%s" % branch
508
509 shallow_branches.append(ref)
510 runfetchcmd("%s update-ref %s %s" % (ud.basecmd, ref, revision), d, workdir=dest)
511
512 # Map srcrev+depths to revisions
513 parsed_depths = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join(to_parse)), d, workdir=dest)
514
515 # Resolve specified revisions
516 parsed_revs = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join('"%s^{}"' % r for r in ud.shallow_revs)), d, workdir=dest)
517 shallow_revisions = parsed_depths.splitlines() + parsed_revs.splitlines()
518
519 # Apply extra ref wildcards
520 all_refs = runfetchcmd('%s for-each-ref "--format=%%(refname)"' % ud.basecmd,
521 d, workdir=dest).splitlines()
522 for r in ud.shallow_extra_refs:
523 if not ud.bareclone:
524 r = r.replace('refs/heads/', 'refs/remotes/origin/')
525
526 if '*' in r:
527 matches = filter(lambda a: fnmatch.fnmatchcase(a, r), all_refs)
528 shallow_branches.extend(matches)
529 else:
530 shallow_branches.append(r)
531
532 # Make the repository shallow
Brad Bishop316dfdd2018-06-25 12:45:53 -0400533 shallow_cmd = [self.make_shallow_path, '-s']
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500534 for b in shallow_branches:
535 shallow_cmd.append('-r')
536 shallow_cmd.append(b)
537 shallow_cmd.extend(shallow_revisions)
538 runfetchcmd(subprocess.list2cmdline(shallow_cmd), d, workdir=dest)
539
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500540 def unpack(self, ud, destdir, d):
541 """ unpack the downloaded src to destdir"""
542
Andrew Geissler595f6302022-01-24 19:11:47 +0000543 subdir = ud.parm.get("subdir")
544 subpath = ud.parm.get("subpath")
545 readpathspec = ""
546 def_destsuffix = "git/"
547
548 if subpath:
549 readpathspec = ":%s" % subpath
550 def_destsuffix = "%s/" % os.path.basename(subpath.rstrip('/'))
551
552 if subdir:
553 # If 'subdir' param exists, create a dir and use it as destination for unpack cmd
554 if os.path.isabs(subdir):
555 if not os.path.realpath(subdir).startswith(os.path.realpath(destdir)):
556 raise bb.fetch2.UnpackError("subdir argument isn't a subdirectory of unpack root %s" % destdir, ud.url)
557 destdir = subdir
558 else:
559 destdir = os.path.join(destdir, subdir)
560 def_destsuffix = ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500561
562 destsuffix = ud.parm.get("destsuffix", def_destsuffix)
563 destdir = ud.destdir = os.path.join(destdir, destsuffix)
564 if os.path.exists(destdir):
565 bb.utils.prunedir(destdir)
566
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600567 need_lfs = self._need_lfs(ud)
Brad Bishopa34c0302019-09-23 22:34:48 -0400568
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500569 if not need_lfs:
570 ud.basecmd = "GIT_LFS_SKIP_SMUDGE=1 " + ud.basecmd
571
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800572 source_found = False
573 source_error = []
574
Andrew Geissler87f5cff2022-09-30 13:13:31 -0500575 clonedir_is_up_to_date = not self.clonedir_need_update(ud, d)
576 if clonedir_is_up_to_date:
577 runfetchcmd("%s clone %s %s/ %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, destdir), d)
578 source_found = True
579 else:
580 source_error.append("clone directory not available or not up to date: " + ud.clonedir)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800581
582 if not source_found:
583 if ud.shallow:
584 if os.path.exists(ud.fullshallow):
585 bb.utils.mkdirhier(destdir)
586 runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=destdir)
587 source_found = True
588 else:
589 source_error.append("shallow clone not available: " + ud.fullshallow)
590 else:
591 source_error.append("shallow clone not enabled")
592
593 if not source_found:
594 raise bb.fetch2.UnpackError("No up to date source found: " + "; ".join(source_error), ud.url)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500595
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500596 repourl = self._get_repo_url(ud)
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500597 runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=destdir)
Brad Bishopc342db32019-05-15 21:57:59 -0400598
599 if self._contains_lfs(ud, d, destdir):
Brad Bishop00e122a2019-10-05 11:10:57 -0400600 if need_lfs and not self._find_git_lfs(d):
601 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 -0500602 elif not need_lfs:
Brad Bishopa34c0302019-09-23 22:34:48 -0400603 bb.note("Repository %s has LFS content but it is not being fetched" % (repourl))
Brad Bishopc342db32019-05-15 21:57:59 -0400604
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500605 if not ud.nocheckout:
Andrew Geissler595f6302022-01-24 19:11:47 +0000606 if subpath:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600607 runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d,
608 workdir=destdir)
609 runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500610 elif not ud.nobranch:
611 branchname = ud.branches[ud.names[0]]
612 runfetchcmd("%s checkout -B %s %s" % (ud.basecmd, branchname, \
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600613 ud.revisions[ud.names[0]]), d, workdir=destdir)
Andre Rosa49271d42017-09-07 11:15:55 +0200614 runfetchcmd("%s branch %s --set-upstream-to origin/%s" % (ud.basecmd, branchname, \
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600615 branchname), d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500616 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600617 runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revisions[ud.names[0]]), d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500618
619 return True
620
621 def clean(self, ud, d):
622 """ clean the git directory """
623
Brad Bishop19323692019-04-05 15:28:33 -0400624 to_remove = [ud.localpath, ud.fullmirror, ud.fullmirror + ".done"]
625 # The localpath is a symlink to clonedir when it is cloned from a
626 # mirror, so remove both of them.
627 if os.path.islink(ud.localpath):
628 clonedir = os.path.realpath(ud.localpath)
629 to_remove.append(clonedir)
630
631 for r in to_remove:
632 if os.path.exists(r):
633 bb.note('Removing %s' % r)
634 bb.utils.remove(r, True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500635
636 def supports_srcrev(self):
637 return True
638
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600639 def _contains_ref(self, ud, d, name, wd):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500640 cmd = ""
641 if ud.nobranch:
642 cmd = "%s log --pretty=oneline -n 1 %s -- 2> /dev/null | wc -l" % (
643 ud.basecmd, ud.revisions[name])
644 else:
645 cmd = "%s branch --contains %s --list %s 2> /dev/null | wc -l" % (
646 ud.basecmd, ud.revisions[name], ud.branches[name])
647 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600648 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500649 except bb.fetch2.FetchError:
650 return False
651 if len(output.split()) > 1:
652 raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
653 return output.split()[0] != "0"
654
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600655 def _need_lfs(self, ud):
656 return ud.parm.get("lfs", "1") == "1"
657
Brad Bishopc342db32019-05-15 21:57:59 -0400658 def _contains_lfs(self, ud, d, wd):
659 """
660 Check if the repository has 'lfs' (large file) content
661 """
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500662
663 if not ud.nobranch:
664 branchname = ud.branches[ud.names[0]]
665 else:
666 branchname = "master"
667
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600668 # The bare clonedir doesn't use the remote names; it has the branch immediately.
669 if wd == ud.clonedir:
670 refname = ud.branches[ud.names[0]]
671 else:
672 refname = "origin/%s" % ud.branches[ud.names[0]]
673
674 cmd = "%s grep lfs %s:.gitattributes | wc -l" % (
675 ud.basecmd, refname)
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500676
Brad Bishopc342db32019-05-15 21:57:59 -0400677 try:
678 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
679 if int(output) > 0:
680 return True
681 except (bb.fetch2.FetchError,ValueError):
682 pass
683 return False
684
Brad Bishop00e122a2019-10-05 11:10:57 -0400685 def _find_git_lfs(self, d):
686 """
687 Return True if git-lfs can be found, False otherwise.
688 """
689 import shutil
690 return shutil.which("git-lfs", path=d.getVar('PATH')) is not None
691
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500692 def _get_repo_url(self, ud):
693 """
694 Return the repository URL
695 """
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600696 # Note that we do not support passwords directly in the git urls. There are several
697 # reasons. SRC_URI can be written out to things like buildhistory and people don't
698 # want to leak passwords like that. Its also all too easy to share metadata without
699 # removing the password. ssh keys, ~/.netrc and ~/.ssh/config files can be used as
700 # alternatives so we will not take patches adding password support here.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500701 if ud.user:
702 username = ud.user + '@'
703 else:
704 username = ""
705 return "%s://%s%s%s" % (ud.proto, username, ud.host, ud.path)
706
707 def _revision_key(self, ud, d, name):
708 """
709 Return a unique key for the url
710 """
Andrew Geissler82c905d2020-04-13 13:39:40 -0500711 # Collapse adjacent slashes
Andrew Geissler82c905d2020-04-13 13:39:40 -0500712 return "git:" + ud.host + slash_re.sub(".", ud.path) + ud.unresolvedrev[name]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500713
714 def _lsremote(self, ud, d, search):
715 """
716 Run git ls-remote with the specified search string
717 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500718 # Prevent recursion e.g. in OE if SRCPV is in PV, PV is in WORKDIR,
719 # and WORKDIR is in PATH (as a result of RSS), our call to
720 # runfetchcmd() exports PATH so this function will get called again (!)
721 # In this scenario the return call of the function isn't actually
722 # important - WORKDIR isn't needed in PATH to call git ls-remote
723 # anyway.
724 if d.getVar('_BB_GIT_IN_LSREMOTE', False):
725 return ''
726 d.setVar('_BB_GIT_IN_LSREMOTE', '1')
727 try:
728 repourl = self._get_repo_url(ud)
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500729 cmd = "%s ls-remote %s %s" % \
730 (ud.basecmd, shlex.quote(repourl), search)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500731 if ud.proto.lower() != 'file':
732 bb.fetch2.check_network_access(d, cmd, repourl)
733 output = runfetchcmd(cmd, d, True)
734 if not output:
735 raise bb.fetch2.FetchError("The command %s gave empty output unexpectedly" % cmd, ud.url)
736 finally:
737 d.delVar('_BB_GIT_IN_LSREMOTE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500738 return output
739
740 def _latest_revision(self, ud, d, name):
741 """
742 Compute the HEAD revision for the url
743 """
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000744 if not d.getVar("__BBSEENSRCREV"):
Andrew Geissler615f2f12022-07-15 14:00:58 -0500745 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 +0000746
747 # Ensure we mark as not cached
748 bb.fetch2.get_autorev(d)
749
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500750 output = self._lsremote(ud, d, "")
751 # Tags of the form ^{} may not work, need to fallback to other form
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600752 if ud.unresolvedrev[name][:5] == "refs/" or ud.usehead:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500753 head = ud.unresolvedrev[name]
754 tag = ud.unresolvedrev[name]
755 else:
756 head = "refs/heads/%s" % ud.unresolvedrev[name]
757 tag = "refs/tags/%s" % ud.unresolvedrev[name]
758 for s in [head, tag + "^{}", tag]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600759 for l in output.strip().split('\n'):
760 sha1, ref = l.split()
761 if s == ref:
762 return sha1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500763 raise bb.fetch2.FetchError("Unable to resolve '%s' in upstream git repository in git ls-remote output for %s" % \
764 (ud.unresolvedrev[name], ud.host+ud.path))
765
766 def latest_versionstring(self, ud, d):
767 """
768 Compute the latest release name like "x.y.x" in "x.y.x+gitHASH"
769 by searching through the tags output of ls-remote, comparing
770 versions and returning the highest match.
771 """
772 pupver = ('', '')
773
Brad Bishop19323692019-04-05 15:28:33 -0400774 tagregex = re.compile(d.getVar('UPSTREAM_CHECK_GITTAGREGEX') or r"(?P<pver>([0-9][\.|_]?)+)")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500775 try:
776 output = self._lsremote(ud, d, "refs/tags/*")
Brad Bishop316dfdd2018-06-25 12:45:53 -0400777 except (bb.fetch2.FetchError, bb.fetch2.NetworkAccess) as e:
778 bb.note("Could not list remote: %s" % str(e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500779 return pupver
780
781 verstring = ""
782 revision = ""
783 for line in output.split("\n"):
784 if not line:
785 break
786
787 tag_head = line.split("/")[-1]
788 # Ignore non-released branches
Brad Bishop19323692019-04-05 15:28:33 -0400789 m = re.search(r"(alpha|beta|rc|final)+", tag_head)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500790 if m:
791 continue
792
793 # search for version in the line
794 tag = tagregex.search(tag_head)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500795 if tag is None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500796 continue
797
798 tag = tag.group('pver')
799 tag = tag.replace("_", ".")
800
801 if verstring and bb.utils.vercmp(("0", tag, ""), ("0", verstring, "")) < 0:
802 continue
803
804 verstring = tag
805 revision = line.split()[0]
806 pupver = (verstring, revision)
807
808 return pupver
809
810 def _build_revision(self, ud, d, name):
811 return ud.revisions[name]
812
813 def gitpkgv_revision(self, ud, d, name):
814 """
815 Return a sortable revision number by counting commits in the history
816 Based on gitpkgv.bblass in meta-openembedded
817 """
818 rev = self._build_revision(ud, d, name)
819 localpath = ud.localpath
820 rev_file = os.path.join(localpath, "oe-gitpkgv_" + rev)
821 if not os.path.exists(localpath):
822 commits = None
823 else:
824 if not os.path.exists(rev_file) or not os.path.getsize(rev_file):
825 from pipes import quote
826 commits = bb.fetch2.runfetchcmd(
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500827 "git rev-list %s -- | wc -l" % quote(rev),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500828 d, quiet=True).strip().lstrip('0')
829 if commits:
830 open(rev_file, "w").write("%d\n" % int(commits))
831 else:
832 commits = open(rev_file, "r").readline(128).strip()
833 if commits:
834 return False, "%s+%s" % (commits, rev[:7])
835 else:
836 return True, str(rev)
837
838 def checkstatus(self, fetch, ud, d):
839 try:
840 self._lsremote(ud, d, "")
841 return True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500842 except bb.fetch2.FetchError:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500843 return False