blob: 5bb83931339593b8330c17baf595dd6c5599cd12 [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.
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600420 with tempfile.TemporaryDirectory(dir=d.getVar('DL_DIR')) as tmpdir:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600421 # Do the checkout. This implicitly involves a Git LFS fetch.
Andrew Geisslerc926e172021-05-07 16:11:35 -0500422 Git.unpack(self, ud, tmpdir, d)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600423
424 # Scoop up a copy of any stuff that Git LFS downloaded. Merge them into
425 # the bare clonedir.
426 #
427 # As this procedure is invoked repeatedly on incremental fetches as
428 # a recipe's SRCREV is bumped throughout its lifetime, this will
429 # result in a gradual accumulation of LFS blobs in <ud.clonedir>/lfs
430 # corresponding to all the blobs reachable from the different revs
431 # fetched across time.
432 #
433 # Only do this if the unpack resulted in a .git/lfs directory being
434 # created; this only happens if at least one blob needed to be
435 # downloaded.
436 if os.path.exists(os.path.join(tmpdir, "git", ".git", "lfs")):
437 runfetchcmd("tar -cf - lfs | tar -xf - -C %s" % ud.clonedir, d, workdir="%s/git/.git" % tmpdir)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600438
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500439 def build_mirror_data(self, ud, d):
Andrew Geissler5199d832021-09-24 16:47:35 -0500440
441 # Create as a temp file and move atomically into position to avoid races
442 @contextmanager
443 def create_atomic(filename):
444 fd, tfile = tempfile.mkstemp(dir=os.path.dirname(filename))
445 try:
446 yield tfile
447 umask = os.umask(0o666)
448 os.umask(umask)
449 os.chmod(tfile, (0o666 & ~umask))
450 os.rename(tfile, filename)
451 finally:
452 os.close(fd)
453
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500454 if ud.shallow and ud.write_shallow_tarballs:
455 if not os.path.exists(ud.fullshallow):
456 if os.path.islink(ud.fullshallow):
457 os.unlink(ud.fullshallow)
458 tempdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
459 shallowclone = os.path.join(tempdir, 'git')
460 try:
461 self.clone_shallow_local(ud, shallowclone, d)
462
463 logger.info("Creating tarball of git repository")
Andrew Geissler5199d832021-09-24 16:47:35 -0500464 with create_atomic(ud.fullshallow) as tfile:
465 runfetchcmd("tar -czf %s ." % tfile, d, workdir=shallowclone)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500466 runfetchcmd("touch %s.done" % ud.fullshallow, d)
467 finally:
468 bb.utils.remove(tempdir, recurse=True)
469 elif ud.write_tarballs and not os.path.exists(ud.fullmirror):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500470 if os.path.islink(ud.fullmirror):
471 os.unlink(ud.fullmirror)
472
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500473 logger.info("Creating tarball of git repository")
Andrew Geissler5199d832021-09-24 16:47:35 -0500474 with create_atomic(ud.fullmirror) as tfile:
Andrew Geissler9aee5002022-03-30 16:27:02 +0000475 mtime = runfetchcmd("git log --all -1 --format=%cD", d,
476 quiet=True, workdir=ud.clonedir)
Patrick Williams03907ee2022-05-01 06:28:52 -0500477 runfetchcmd("tar -czf %s --owner oe:0 --group oe:0 --mtime \"%s\" ."
Andrew Geissler9aee5002022-03-30 16:27:02 +0000478 % (tfile, mtime), d, workdir=ud.clonedir)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500479 runfetchcmd("touch %s.done" % ud.fullmirror, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500480
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500481 def clone_shallow_local(self, ud, dest, d):
482 """Clone the repo and make it shallow.
483
484 The upstream url of the new clone isn't set at this time, as it'll be
485 set correctly when unpacked."""
486 runfetchcmd("%s clone %s %s %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, dest), d)
487
488 to_parse, shallow_branches = [], []
489 for name in ud.names:
490 revision = ud.revisions[name]
491 depth = ud.shallow_depths[name]
492 if depth:
493 to_parse.append('%s~%d^{}' % (revision, depth - 1))
494
495 # For nobranch, we need a ref, otherwise the commits will be
496 # removed, and for non-nobranch, we truncate the branch to our
497 # srcrev, to avoid keeping unnecessary history beyond that.
498 branch = ud.branches[name]
499 if ud.nobranch:
500 ref = "refs/shallow/%s" % name
501 elif ud.bareclone:
502 ref = "refs/heads/%s" % branch
503 else:
504 ref = "refs/remotes/origin/%s" % branch
505
506 shallow_branches.append(ref)
507 runfetchcmd("%s update-ref %s %s" % (ud.basecmd, ref, revision), d, workdir=dest)
508
509 # Map srcrev+depths to revisions
510 parsed_depths = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join(to_parse)), d, workdir=dest)
511
512 # Resolve specified revisions
513 parsed_revs = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join('"%s^{}"' % r for r in ud.shallow_revs)), d, workdir=dest)
514 shallow_revisions = parsed_depths.splitlines() + parsed_revs.splitlines()
515
516 # Apply extra ref wildcards
517 all_refs = runfetchcmd('%s for-each-ref "--format=%%(refname)"' % ud.basecmd,
518 d, workdir=dest).splitlines()
519 for r in ud.shallow_extra_refs:
520 if not ud.bareclone:
521 r = r.replace('refs/heads/', 'refs/remotes/origin/')
522
523 if '*' in r:
524 matches = filter(lambda a: fnmatch.fnmatchcase(a, r), all_refs)
525 shallow_branches.extend(matches)
526 else:
527 shallow_branches.append(r)
528
529 # Make the repository shallow
Brad Bishop316dfdd2018-06-25 12:45:53 -0400530 shallow_cmd = [self.make_shallow_path, '-s']
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500531 for b in shallow_branches:
532 shallow_cmd.append('-r')
533 shallow_cmd.append(b)
534 shallow_cmd.extend(shallow_revisions)
535 runfetchcmd(subprocess.list2cmdline(shallow_cmd), d, workdir=dest)
536
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500537 def unpack(self, ud, destdir, d):
538 """ unpack the downloaded src to destdir"""
539
Andrew Geissler595f6302022-01-24 19:11:47 +0000540 subdir = ud.parm.get("subdir")
541 subpath = ud.parm.get("subpath")
542 readpathspec = ""
543 def_destsuffix = "git/"
544
545 if subpath:
546 readpathspec = ":%s" % subpath
547 def_destsuffix = "%s/" % os.path.basename(subpath.rstrip('/'))
548
549 if subdir:
550 # If 'subdir' param exists, create a dir and use it as destination for unpack cmd
551 if os.path.isabs(subdir):
552 if not os.path.realpath(subdir).startswith(os.path.realpath(destdir)):
553 raise bb.fetch2.UnpackError("subdir argument isn't a subdirectory of unpack root %s" % destdir, ud.url)
554 destdir = subdir
555 else:
556 destdir = os.path.join(destdir, subdir)
557 def_destsuffix = ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500558
559 destsuffix = ud.parm.get("destsuffix", def_destsuffix)
560 destdir = ud.destdir = os.path.join(destdir, destsuffix)
561 if os.path.exists(destdir):
562 bb.utils.prunedir(destdir)
563
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600564 need_lfs = self._need_lfs(ud)
Brad Bishopa34c0302019-09-23 22:34:48 -0400565
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500566 if not need_lfs:
567 ud.basecmd = "GIT_LFS_SKIP_SMUDGE=1 " + ud.basecmd
568
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800569 source_found = False
570 source_error = []
571
Andrew Geissler87f5cff2022-09-30 13:13:31 -0500572 clonedir_is_up_to_date = not self.clonedir_need_update(ud, d)
573 if clonedir_is_up_to_date:
574 runfetchcmd("%s clone %s %s/ %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, destdir), d)
575 source_found = True
576 else:
577 source_error.append("clone directory not available or not up to date: " + ud.clonedir)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800578
579 if not source_found:
580 if ud.shallow:
581 if os.path.exists(ud.fullshallow):
582 bb.utils.mkdirhier(destdir)
583 runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=destdir)
584 source_found = True
585 else:
586 source_error.append("shallow clone not available: " + ud.fullshallow)
587 else:
588 source_error.append("shallow clone not enabled")
589
590 if not source_found:
591 raise bb.fetch2.UnpackError("No up to date source found: " + "; ".join(source_error), ud.url)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500592
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500593 repourl = self._get_repo_url(ud)
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500594 runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=destdir)
Brad Bishopc342db32019-05-15 21:57:59 -0400595
596 if self._contains_lfs(ud, d, destdir):
Brad Bishop00e122a2019-10-05 11:10:57 -0400597 if need_lfs and not self._find_git_lfs(d):
598 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 -0500599 elif not need_lfs:
Brad Bishopa34c0302019-09-23 22:34:48 -0400600 bb.note("Repository %s has LFS content but it is not being fetched" % (repourl))
Brad Bishopc342db32019-05-15 21:57:59 -0400601
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500602 if not ud.nocheckout:
Andrew Geissler595f6302022-01-24 19:11:47 +0000603 if subpath:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600604 runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d,
605 workdir=destdir)
606 runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500607 elif not ud.nobranch:
608 branchname = ud.branches[ud.names[0]]
609 runfetchcmd("%s checkout -B %s %s" % (ud.basecmd, branchname, \
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600610 ud.revisions[ud.names[0]]), d, workdir=destdir)
Andre Rosa49271d42017-09-07 11:15:55 +0200611 runfetchcmd("%s branch %s --set-upstream-to origin/%s" % (ud.basecmd, branchname, \
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600612 branchname), d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500613 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600614 runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revisions[ud.names[0]]), d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500615
616 return True
617
618 def clean(self, ud, d):
619 """ clean the git directory """
620
Brad Bishop19323692019-04-05 15:28:33 -0400621 to_remove = [ud.localpath, ud.fullmirror, ud.fullmirror + ".done"]
622 # The localpath is a symlink to clonedir when it is cloned from a
623 # mirror, so remove both of them.
624 if os.path.islink(ud.localpath):
625 clonedir = os.path.realpath(ud.localpath)
626 to_remove.append(clonedir)
627
628 for r in to_remove:
629 if os.path.exists(r):
630 bb.note('Removing %s' % r)
631 bb.utils.remove(r, True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500632
633 def supports_srcrev(self):
634 return True
635
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600636 def _contains_ref(self, ud, d, name, wd):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500637 cmd = ""
638 if ud.nobranch:
639 cmd = "%s log --pretty=oneline -n 1 %s -- 2> /dev/null | wc -l" % (
640 ud.basecmd, ud.revisions[name])
641 else:
642 cmd = "%s branch --contains %s --list %s 2> /dev/null | wc -l" % (
643 ud.basecmd, ud.revisions[name], ud.branches[name])
644 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600645 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500646 except bb.fetch2.FetchError:
647 return False
648 if len(output.split()) > 1:
649 raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
650 return output.split()[0] != "0"
651
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600652 def _need_lfs(self, ud):
653 return ud.parm.get("lfs", "1") == "1"
654
Brad Bishopc342db32019-05-15 21:57:59 -0400655 def _contains_lfs(self, ud, d, wd):
656 """
657 Check if the repository has 'lfs' (large file) content
658 """
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500659
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600660 # The bare clonedir doesn't use the remote names; it has the branch immediately.
661 if wd == ud.clonedir:
662 refname = ud.branches[ud.names[0]]
663 else:
664 refname = "origin/%s" % ud.branches[ud.names[0]]
665
666 cmd = "%s grep lfs %s:.gitattributes | wc -l" % (
667 ud.basecmd, refname)
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500668
Brad Bishopc342db32019-05-15 21:57:59 -0400669 try:
670 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
671 if int(output) > 0:
672 return True
673 except (bb.fetch2.FetchError,ValueError):
674 pass
675 return False
676
Brad Bishop00e122a2019-10-05 11:10:57 -0400677 def _find_git_lfs(self, d):
678 """
679 Return True if git-lfs can be found, False otherwise.
680 """
681 import shutil
682 return shutil.which("git-lfs", path=d.getVar('PATH')) is not None
683
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500684 def _get_repo_url(self, ud):
685 """
686 Return the repository URL
687 """
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600688 # Note that we do not support passwords directly in the git urls. There are several
689 # reasons. SRC_URI can be written out to things like buildhistory and people don't
690 # want to leak passwords like that. Its also all too easy to share metadata without
691 # removing the password. ssh keys, ~/.netrc and ~/.ssh/config files can be used as
692 # alternatives so we will not take patches adding password support here.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500693 if ud.user:
694 username = ud.user + '@'
695 else:
696 username = ""
697 return "%s://%s%s%s" % (ud.proto, username, ud.host, ud.path)
698
699 def _revision_key(self, ud, d, name):
700 """
701 Return a unique key for the url
702 """
Andrew Geissler82c905d2020-04-13 13:39:40 -0500703 # Collapse adjacent slashes
Andrew Geissler82c905d2020-04-13 13:39:40 -0500704 return "git:" + ud.host + slash_re.sub(".", ud.path) + ud.unresolvedrev[name]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500705
706 def _lsremote(self, ud, d, search):
707 """
708 Run git ls-remote with the specified search string
709 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500710 # Prevent recursion e.g. in OE if SRCPV is in PV, PV is in WORKDIR,
711 # and WORKDIR is in PATH (as a result of RSS), our call to
712 # runfetchcmd() exports PATH so this function will get called again (!)
713 # In this scenario the return call of the function isn't actually
714 # important - WORKDIR isn't needed in PATH to call git ls-remote
715 # anyway.
716 if d.getVar('_BB_GIT_IN_LSREMOTE', False):
717 return ''
718 d.setVar('_BB_GIT_IN_LSREMOTE', '1')
719 try:
720 repourl = self._get_repo_url(ud)
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500721 cmd = "%s ls-remote %s %s" % \
722 (ud.basecmd, shlex.quote(repourl), search)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500723 if ud.proto.lower() != 'file':
724 bb.fetch2.check_network_access(d, cmd, repourl)
725 output = runfetchcmd(cmd, d, True)
726 if not output:
727 raise bb.fetch2.FetchError("The command %s gave empty output unexpectedly" % cmd, ud.url)
728 finally:
729 d.delVar('_BB_GIT_IN_LSREMOTE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500730 return output
731
732 def _latest_revision(self, ud, d, name):
733 """
734 Compute the HEAD revision for the url
735 """
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000736 if not d.getVar("__BBSEENSRCREV"):
Andrew Geissler615f2f12022-07-15 14:00:58 -0500737 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 +0000738
739 # Ensure we mark as not cached
740 bb.fetch2.get_autorev(d)
741
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500742 output = self._lsremote(ud, d, "")
743 # Tags of the form ^{} may not work, need to fallback to other form
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600744 if ud.unresolvedrev[name][:5] == "refs/" or ud.usehead:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500745 head = ud.unresolvedrev[name]
746 tag = ud.unresolvedrev[name]
747 else:
748 head = "refs/heads/%s" % ud.unresolvedrev[name]
749 tag = "refs/tags/%s" % ud.unresolvedrev[name]
750 for s in [head, tag + "^{}", tag]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600751 for l in output.strip().split('\n'):
752 sha1, ref = l.split()
753 if s == ref:
754 return sha1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500755 raise bb.fetch2.FetchError("Unable to resolve '%s' in upstream git repository in git ls-remote output for %s" % \
756 (ud.unresolvedrev[name], ud.host+ud.path))
757
758 def latest_versionstring(self, ud, d):
759 """
760 Compute the latest release name like "x.y.x" in "x.y.x+gitHASH"
761 by searching through the tags output of ls-remote, comparing
762 versions and returning the highest match.
763 """
764 pupver = ('', '')
765
Brad Bishop19323692019-04-05 15:28:33 -0400766 tagregex = re.compile(d.getVar('UPSTREAM_CHECK_GITTAGREGEX') or r"(?P<pver>([0-9][\.|_]?)+)")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500767 try:
768 output = self._lsremote(ud, d, "refs/tags/*")
Brad Bishop316dfdd2018-06-25 12:45:53 -0400769 except (bb.fetch2.FetchError, bb.fetch2.NetworkAccess) as e:
770 bb.note("Could not list remote: %s" % str(e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500771 return pupver
772
773 verstring = ""
774 revision = ""
775 for line in output.split("\n"):
776 if not line:
777 break
778
779 tag_head = line.split("/")[-1]
780 # Ignore non-released branches
Brad Bishop19323692019-04-05 15:28:33 -0400781 m = re.search(r"(alpha|beta|rc|final)+", tag_head)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500782 if m:
783 continue
784
785 # search for version in the line
786 tag = tagregex.search(tag_head)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500787 if tag is None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500788 continue
789
790 tag = tag.group('pver')
791 tag = tag.replace("_", ".")
792
793 if verstring and bb.utils.vercmp(("0", tag, ""), ("0", verstring, "")) < 0:
794 continue
795
796 verstring = tag
797 revision = line.split()[0]
798 pupver = (verstring, revision)
799
800 return pupver
801
802 def _build_revision(self, ud, d, name):
803 return ud.revisions[name]
804
805 def gitpkgv_revision(self, ud, d, name):
806 """
807 Return a sortable revision number by counting commits in the history
808 Based on gitpkgv.bblass in meta-openembedded
809 """
810 rev = self._build_revision(ud, d, name)
811 localpath = ud.localpath
812 rev_file = os.path.join(localpath, "oe-gitpkgv_" + rev)
813 if not os.path.exists(localpath):
814 commits = None
815 else:
816 if not os.path.exists(rev_file) or not os.path.getsize(rev_file):
817 from pipes import quote
818 commits = bb.fetch2.runfetchcmd(
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500819 "git rev-list %s -- | wc -l" % quote(rev),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500820 d, quiet=True).strip().lstrip('0')
821 if commits:
822 open(rev_file, "w").write("%d\n" % int(commits))
823 else:
824 commits = open(rev_file, "r").readline(128).strip()
825 if commits:
826 return False, "%s+%s" % (commits, rev[:7])
827 else:
828 return True, str(rev)
829
830 def checkstatus(self, fetch, ud, d):
831 try:
832 self._lsremote(ud, d, "")
833 return True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500834 except bb.fetch2.FetchError:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500835 return False