blob: e3ba80a3f52ab5c98aec75cd23c234f4c0c3c6b4 [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
Patrick Williamsc124f4f2015-09-15 14:41:29 -050071from bb.fetch2 import FetchMethod
72from bb.fetch2 import runfetchcmd
73from bb.fetch2 import logger
74
Patrick Williamsc0f7c042017-02-23 20:41:17 -060075
76class GitProgressHandler(bb.progress.LineFilterProgressHandler):
77 """Extract progress information from git output"""
78 def __init__(self, d):
79 self._buffer = ''
80 self._count = 0
81 super(GitProgressHandler, self).__init__(d)
82 # Send an initial progress event so the bar gets shown
83 self._fire_progress(-1)
84
85 def write(self, string):
86 self._buffer += string
87 stages = ['Counting objects', 'Compressing objects', 'Receiving objects', 'Resolving deltas']
88 stage_weights = [0.2, 0.05, 0.5, 0.25]
89 stagenum = 0
90 for i, stage in reversed(list(enumerate(stages))):
91 if stage in self._buffer:
92 stagenum = i
93 self._buffer = ''
94 break
95 self._status = stages[stagenum]
96 percs = re.findall(r'(\d+)%', string)
97 if percs:
98 progress = int(round((int(percs[-1]) * stage_weights[stagenum]) + (sum(stage_weights[:stagenum]) * 100)))
99 rates = re.findall(r'([\d.]+ [a-zA-Z]*/s+)', string)
100 if rates:
101 rate = rates[-1]
102 else:
103 rate = None
104 self.update(progress, rate)
105 else:
106 if stagenum == 0:
107 percs = re.findall(r': (\d+)', string)
108 if percs:
109 count = int(percs[-1])
110 if count > self._count:
111 self._count = count
112 self._fire_progress(-count)
113 super(GitProgressHandler, self).write(string)
114
115
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500116class Git(FetchMethod):
Brad Bishop316dfdd2018-06-25 12:45:53 -0400117 bitbake_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.join(os.path.abspath(__file__))), '..', '..', '..'))
118 make_shallow_path = os.path.join(bitbake_dir, 'bin', 'git-make-shallow')
119
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500120 """Class to fetch a module or modules from git repositories"""
121 def init(self, d):
122 pass
123
124 def supports(self, ud, d):
125 """
126 Check to see if a given url can be fetched with git.
127 """
128 return ud.type in ['git']
129
130 def supports_checksum(self, urldata):
131 return False
132
133 def urldata_init(self, ud, d):
134 """
135 init git specific variable within url data
136 so that the git method like latest_revision() can work
137 """
138 if 'protocol' in ud.parm:
139 ud.proto = ud.parm['protocol']
140 elif not ud.host:
141 ud.proto = 'file'
142 else:
143 ud.proto = "git"
144
145 if not ud.proto in ('git', 'file', 'ssh', 'http', 'https', 'rsync'):
146 raise bb.fetch2.ParameterError("Invalid protocol type", ud.url)
147
148 ud.nocheckout = ud.parm.get("nocheckout","0") == "1"
149
150 ud.rebaseable = ud.parm.get("rebaseable","0") == "1"
151
152 ud.nobranch = ud.parm.get("nobranch","0") == "1"
153
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600154 # usehead implies nobranch
155 ud.usehead = ud.parm.get("usehead","0") == "1"
156 if ud.usehead:
157 if ud.proto != "file":
158 raise bb.fetch2.ParameterError("The usehead option is only for use with local ('protocol=file') git repositories", ud.url)
159 ud.nobranch = 1
160
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500161 # bareclone implies nocheckout
162 ud.bareclone = ud.parm.get("bareclone","0") == "1"
163 if ud.bareclone:
164 ud.nocheckout = 1
165
166 ud.unresolvedrev = {}
167 branches = ud.parm.get("branch", "master").split(',')
168 if len(branches) != len(ud.names):
169 raise bb.fetch2.ParameterError("The number of name and branch parameters is not balanced", ud.url)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500170
171 ud.cloneflags = "-s -n"
172 if ud.bareclone:
173 ud.cloneflags += " --mirror"
174
175 ud.shallow = d.getVar("BB_GIT_SHALLOW") == "1"
176 ud.shallow_extra_refs = (d.getVar("BB_GIT_SHALLOW_EXTRA_REFS") or "").split()
177
178 depth_default = d.getVar("BB_GIT_SHALLOW_DEPTH")
179 if depth_default is not None:
180 try:
181 depth_default = int(depth_default or 0)
182 except ValueError:
183 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH: %s" % depth_default)
184 else:
185 if depth_default < 0:
186 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH: %s" % depth_default)
187 else:
188 depth_default = 1
189 ud.shallow_depths = collections.defaultdict(lambda: depth_default)
190
Brad Bishop19323692019-04-05 15:28:33 -0400191 revs_default = d.getVar("BB_GIT_SHALLOW_REVS")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500192 ud.shallow_revs = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500193 ud.branches = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500194 for pos, name in enumerate(ud.names):
195 branch = branches[pos]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500196 ud.branches[name] = branch
197 ud.unresolvedrev[name] = branch
198
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500199 shallow_depth = d.getVar("BB_GIT_SHALLOW_DEPTH_%s" % name)
200 if shallow_depth is not None:
201 try:
202 shallow_depth = int(shallow_depth or 0)
203 except ValueError:
204 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth))
205 else:
206 if shallow_depth < 0:
207 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth))
208 ud.shallow_depths[name] = shallow_depth
209
210 revs = d.getVar("BB_GIT_SHALLOW_REVS_%s" % name)
211 if revs is not None:
212 ud.shallow_revs.extend(revs.split())
213 elif revs_default is not None:
214 ud.shallow_revs.extend(revs_default.split())
215
216 if (ud.shallow and
217 not ud.shallow_revs and
218 all(ud.shallow_depths[n] == 0 for n in ud.names)):
219 # Shallow disabled for this URL
220 ud.shallow = False
221
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600222 if ud.usehead:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600223 # When usehead is set let's associate 'HEAD' with the unresolved
224 # rev of this repository. This will get resolved into a revision
225 # later. If an actual revision happens to have also been provided
226 # then this setting will be overridden.
227 for name in ud.names:
228 ud.unresolvedrev[name] = 'HEAD'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600229
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500230 ud.basecmd = d.getVar("FETCHCMD_git") or "git -c core.fsyncobjectfiles=0"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500231
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500232 write_tarballs = d.getVar("BB_GENERATE_MIRROR_TARBALLS") or "0"
233 ud.write_tarballs = write_tarballs != "0" or ud.rebaseable
234 ud.write_shallow_tarballs = (d.getVar("BB_GENERATE_SHALLOW_TARBALLS") or write_tarballs) != "0"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500235
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500236 ud.setup_revisions(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500237
238 for name in ud.names:
239 # Ensure anything that doesn't look like a sha256 checksum/revision is translated into one
240 if not ud.revisions[name] or len(ud.revisions[name]) != 40 or (False in [c in "abcdef0123456789" for c in ud.revisions[name]]):
241 if ud.revisions[name]:
242 ud.unresolvedrev[name] = ud.revisions[name]
243 ud.revisions[name] = self.latest_revision(ud, d, name)
244
Andrew Geisslerc3d88e42020-10-02 09:45:00 -0500245 gitsrcname = '%s%s' % (ud.host.replace(':', '.'), ud.path.replace('/', '.').replace('*', '.').replace(' ','_'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500246 if gitsrcname.startswith('.'):
247 gitsrcname = gitsrcname[1:]
248
249 # for rebaseable git repo, it is necessary to keep mirror tar ball
250 # per revision, so that even the revision disappears from the
251 # upstream repo in the future, the mirror will remain intact and still
252 # contains the revision
253 if ud.rebaseable:
254 for name in ud.names:
255 gitsrcname = gitsrcname + '_' + ud.revisions[name]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500256
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500257 dl_dir = d.getVar("DL_DIR")
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800258 gitdir = d.getVar("GITDIR") or (dl_dir + "/git2")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500259 ud.clonedir = os.path.join(gitdir, gitsrcname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500260 ud.localfile = ud.clonedir
261
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500262 mirrortarball = 'git2_%s.tar.gz' % gitsrcname
263 ud.fullmirror = os.path.join(dl_dir, mirrortarball)
264 ud.mirrortarballs = [mirrortarball]
265 if ud.shallow:
266 tarballname = gitsrcname
267 if ud.bareclone:
268 tarballname = "%s_bare" % tarballname
269
270 if ud.shallow_revs:
271 tarballname = "%s_%s" % (tarballname, "_".join(sorted(ud.shallow_revs)))
272
273 for name, revision in sorted(ud.revisions.items()):
274 tarballname = "%s_%s" % (tarballname, ud.revisions[name][:7])
275 depth = ud.shallow_depths[name]
276 if depth:
277 tarballname = "%s-%s" % (tarballname, depth)
278
279 shallow_refs = []
280 if not ud.nobranch:
281 shallow_refs.extend(ud.branches.values())
282 if ud.shallow_extra_refs:
283 shallow_refs.extend(r.replace('refs/heads/', '').replace('*', 'ALL') for r in ud.shallow_extra_refs)
284 if shallow_refs:
285 tarballname = "%s_%s" % (tarballname, "_".join(sorted(shallow_refs)).replace('/', '.'))
286
287 fetcher = self.__class__.__name__.lower()
288 ud.shallowtarball = '%sshallow_%s.tar.gz' % (fetcher, tarballname)
289 ud.fullshallow = os.path.join(dl_dir, ud.shallowtarball)
290 ud.mirrortarballs.insert(0, ud.shallowtarball)
291
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500292 def localpath(self, ud, d):
293 return ud.clonedir
294
295 def need_update(self, ud, d):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800296 return self.clonedir_need_update(ud, d) or self.shallow_tarball_need_update(ud) or self.tarball_need_update(ud)
297
298 def clonedir_need_update(self, ud, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500299 if not os.path.exists(ud.clonedir):
300 return True
Brad Bishop64c979e2019-11-04 13:55:29 -0500301 if ud.shallow and ud.write_shallow_tarballs and self.clonedir_need_shallow_revs(ud, d):
302 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500303 for name in ud.names:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600304 if not self._contains_ref(ud, d, name, ud.clonedir):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500305 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500306 return False
307
Brad Bishop64c979e2019-11-04 13:55:29 -0500308 def clonedir_need_shallow_revs(self, ud, d):
309 for rev in ud.shallow_revs:
310 try:
311 runfetchcmd('%s rev-parse -q --verify %s' % (ud.basecmd, rev), d, quiet=True, workdir=ud.clonedir)
312 except bb.fetch2.FetchError:
313 return rev
314 return None
315
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800316 def shallow_tarball_need_update(self, ud):
317 return ud.shallow and ud.write_shallow_tarballs and not os.path.exists(ud.fullshallow)
318
319 def tarball_need_update(self, ud):
320 return ud.write_tarballs and not os.path.exists(ud.fullmirror)
321
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500322 def try_premirror(self, ud, d):
323 # If we don't do this, updating an existing checkout with only premirrors
324 # is not possible
Brad Bishop19323692019-04-05 15:28:33 -0400325 if bb.utils.to_boolean(d.getVar("BB_FETCH_PREMIRRORONLY")):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500326 return True
327 if os.path.exists(ud.clonedir):
328 return False
329 return True
330
331 def download(self, ud, d):
332 """Fetch url"""
333
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500334 # A current clone is preferred to either tarball, a shallow tarball is
335 # preferred to an out of date clone, and a missing clone will use
336 # either tarball.
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800337 if ud.shallow and os.path.exists(ud.fullshallow) and self.need_update(ud, d):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500338 ud.localpath = ud.fullshallow
339 return
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800340 elif os.path.exists(ud.fullmirror) and not os.path.exists(ud.clonedir):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500341 bb.utils.mkdirhier(ud.clonedir)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500342 runfetchcmd("tar -xzf %s" % ud.fullmirror, d, workdir=ud.clonedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500343
344 repourl = self._get_repo_url(ud)
345
346 # If the repo still doesn't exist, fallback to cloning it
347 if not os.path.exists(ud.clonedir):
348 # We do this since git will use a "-l" option automatically for local urls where possible
349 if repourl.startswith("file://"):
350 repourl = repourl[7:]
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500351 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 -0500352 if ud.proto.lower() != 'file':
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500353 bb.fetch2.check_network_access(d, clone_cmd, ud.url)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600354 progresshandler = GitProgressHandler(d)
355 runfetchcmd(clone_cmd, d, log=progresshandler)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500356
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500357 # Update the checkout if needed
Brad Bishop64c979e2019-11-04 13:55:29 -0500358 if self.clonedir_need_update(ud, d):
Brad Bishop6ef32652018-10-09 18:59:25 +0100359 output = runfetchcmd("%s remote" % ud.basecmd, d, quiet=True, workdir=ud.clonedir)
360 if "origin" in output:
361 runfetchcmd("%s remote rm origin" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500362
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500363 runfetchcmd("%s remote add --mirror=fetch origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=ud.clonedir)
364 fetch_cmd = "LANG=C %s fetch -f --progress %s refs/*:refs/*" % (ud.basecmd, shlex.quote(repourl))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500365 if ud.proto.lower() != 'file':
366 bb.fetch2.check_network_access(d, fetch_cmd, ud.url)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600367 progresshandler = GitProgressHandler(d)
368 runfetchcmd(fetch_cmd, d, log=progresshandler, workdir=ud.clonedir)
369 runfetchcmd("%s prune-packed" % ud.basecmd, d, workdir=ud.clonedir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400370 runfetchcmd("%s pack-refs --all" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600371 runfetchcmd("%s pack-redundant --all | xargs -r rm" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsd7e96312015-09-22 08:09:05 -0500372 try:
373 os.unlink(ud.fullmirror)
374 except OSError as exc:
375 if exc.errno != errno.ENOENT:
376 raise
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800377
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500378 for name in ud.names:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600379 if not self._contains_ref(ud, d, name, ud.clonedir):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500380 raise bb.fetch2.FetchError("Unable to find revision %s in branch %s even from upstream" % (ud.revisions[name], ud.branches[name]))
381
Brad Bishop64c979e2019-11-04 13:55:29 -0500382 if ud.shallow and ud.write_shallow_tarballs:
383 missing_rev = self.clonedir_need_shallow_revs(ud, d)
384 if missing_rev:
385 raise bb.fetch2.FetchError("Unable to find revision %s even from upstream" % missing_rev)
386
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600387 if self._contains_lfs(ud, d, ud.clonedir) and self._need_lfs(ud):
388 # Unpack temporary working copy, use it to run 'git checkout' to force pre-fetching
389 # of all LFS blobs needed at the the srcrev.
390 #
391 # It would be nice to just do this inline here by running 'git-lfs fetch'
392 # on the bare clonedir, but that operation requires a working copy on some
393 # releases of Git LFS.
394 tmpdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
395 try:
396 # Do the checkout. This implicitly involves a Git LFS fetch.
397 self.unpack(ud, tmpdir, d)
398
399 # Scoop up a copy of any stuff that Git LFS downloaded. Merge them into
400 # the bare clonedir.
401 #
402 # As this procedure is invoked repeatedly on incremental fetches as
403 # a recipe's SRCREV is bumped throughout its lifetime, this will
404 # result in a gradual accumulation of LFS blobs in <ud.clonedir>/lfs
405 # corresponding to all the blobs reachable from the different revs
406 # fetched across time.
407 #
408 # Only do this if the unpack resulted in a .git/lfs directory being
409 # created; this only happens if at least one blob needed to be
410 # downloaded.
411 if os.path.exists(os.path.join(tmpdir, "git", ".git", "lfs")):
412 runfetchcmd("tar -cf - lfs | tar -xf - -C %s" % ud.clonedir, d, workdir="%s/git/.git" % tmpdir)
413 finally:
414 bb.utils.remove(tmpdir, recurse=True)
415
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500416 def build_mirror_data(self, ud, d):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500417 if ud.shallow and ud.write_shallow_tarballs:
418 if not os.path.exists(ud.fullshallow):
419 if os.path.islink(ud.fullshallow):
420 os.unlink(ud.fullshallow)
421 tempdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
422 shallowclone = os.path.join(tempdir, 'git')
423 try:
424 self.clone_shallow_local(ud, shallowclone, d)
425
426 logger.info("Creating tarball of git repository")
427 runfetchcmd("tar -czf %s ." % ud.fullshallow, d, workdir=shallowclone)
428 runfetchcmd("touch %s.done" % ud.fullshallow, d)
429 finally:
430 bb.utils.remove(tempdir, recurse=True)
431 elif ud.write_tarballs and not os.path.exists(ud.fullmirror):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500432 if os.path.islink(ud.fullmirror):
433 os.unlink(ud.fullmirror)
434
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500435 logger.info("Creating tarball of git repository")
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500436 runfetchcmd("tar -czf %s ." % ud.fullmirror, d, workdir=ud.clonedir)
437 runfetchcmd("touch %s.done" % ud.fullmirror, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500438
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500439 def clone_shallow_local(self, ud, dest, d):
440 """Clone the repo and make it shallow.
441
442 The upstream url of the new clone isn't set at this time, as it'll be
443 set correctly when unpacked."""
444 runfetchcmd("%s clone %s %s %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, dest), d)
445
446 to_parse, shallow_branches = [], []
447 for name in ud.names:
448 revision = ud.revisions[name]
449 depth = ud.shallow_depths[name]
450 if depth:
451 to_parse.append('%s~%d^{}' % (revision, depth - 1))
452
453 # For nobranch, we need a ref, otherwise the commits will be
454 # removed, and for non-nobranch, we truncate the branch to our
455 # srcrev, to avoid keeping unnecessary history beyond that.
456 branch = ud.branches[name]
457 if ud.nobranch:
458 ref = "refs/shallow/%s" % name
459 elif ud.bareclone:
460 ref = "refs/heads/%s" % branch
461 else:
462 ref = "refs/remotes/origin/%s" % branch
463
464 shallow_branches.append(ref)
465 runfetchcmd("%s update-ref %s %s" % (ud.basecmd, ref, revision), d, workdir=dest)
466
467 # Map srcrev+depths to revisions
468 parsed_depths = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join(to_parse)), d, workdir=dest)
469
470 # Resolve specified revisions
471 parsed_revs = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join('"%s^{}"' % r for r in ud.shallow_revs)), d, workdir=dest)
472 shallow_revisions = parsed_depths.splitlines() + parsed_revs.splitlines()
473
474 # Apply extra ref wildcards
475 all_refs = runfetchcmd('%s for-each-ref "--format=%%(refname)"' % ud.basecmd,
476 d, workdir=dest).splitlines()
477 for r in ud.shallow_extra_refs:
478 if not ud.bareclone:
479 r = r.replace('refs/heads/', 'refs/remotes/origin/')
480
481 if '*' in r:
482 matches = filter(lambda a: fnmatch.fnmatchcase(a, r), all_refs)
483 shallow_branches.extend(matches)
484 else:
485 shallow_branches.append(r)
486
487 # Make the repository shallow
Brad Bishop316dfdd2018-06-25 12:45:53 -0400488 shallow_cmd = [self.make_shallow_path, '-s']
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500489 for b in shallow_branches:
490 shallow_cmd.append('-r')
491 shallow_cmd.append(b)
492 shallow_cmd.extend(shallow_revisions)
493 runfetchcmd(subprocess.list2cmdline(shallow_cmd), d, workdir=dest)
494
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500495 def unpack(self, ud, destdir, d):
496 """ unpack the downloaded src to destdir"""
497
498 subdir = ud.parm.get("subpath", "")
499 if subdir != "":
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500500 readpathspec = ":%s" % subdir
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500501 def_destsuffix = "%s/" % os.path.basename(subdir.rstrip('/'))
502 else:
503 readpathspec = ""
504 def_destsuffix = "git/"
505
506 destsuffix = ud.parm.get("destsuffix", def_destsuffix)
507 destdir = ud.destdir = os.path.join(destdir, destsuffix)
508 if os.path.exists(destdir):
509 bb.utils.prunedir(destdir)
510
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600511 need_lfs = self._need_lfs(ud)
Brad Bishopa34c0302019-09-23 22:34:48 -0400512
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500513 if not need_lfs:
514 ud.basecmd = "GIT_LFS_SKIP_SMUDGE=1 " + ud.basecmd
515
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800516 source_found = False
517 source_error = []
518
519 if not source_found:
520 clonedir_is_up_to_date = not self.clonedir_need_update(ud, d)
521 if clonedir_is_up_to_date:
522 runfetchcmd("%s clone %s %s/ %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, destdir), d)
523 source_found = True
524 else:
525 source_error.append("clone directory not available or not up to date: " + ud.clonedir)
526
527 if not source_found:
528 if ud.shallow:
529 if os.path.exists(ud.fullshallow):
530 bb.utils.mkdirhier(destdir)
531 runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=destdir)
532 source_found = True
533 else:
534 source_error.append("shallow clone not available: " + ud.fullshallow)
535 else:
536 source_error.append("shallow clone not enabled")
537
538 if not source_found:
539 raise bb.fetch2.UnpackError("No up to date source found: " + "; ".join(source_error), ud.url)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500540
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500541 repourl = self._get_repo_url(ud)
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500542 runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=destdir)
Brad Bishopc342db32019-05-15 21:57:59 -0400543
544 if self._contains_lfs(ud, d, destdir):
Brad Bishop00e122a2019-10-05 11:10:57 -0400545 if need_lfs and not self._find_git_lfs(d):
546 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 -0500547 elif not need_lfs:
Brad Bishopa34c0302019-09-23 22:34:48 -0400548 bb.note("Repository %s has LFS content but it is not being fetched" % (repourl))
Brad Bishopc342db32019-05-15 21:57:59 -0400549
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500550 if not ud.nocheckout:
551 if subdir != "":
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600552 runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d,
553 workdir=destdir)
554 runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500555 elif not ud.nobranch:
556 branchname = ud.branches[ud.names[0]]
557 runfetchcmd("%s checkout -B %s %s" % (ud.basecmd, branchname, \
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600558 ud.revisions[ud.names[0]]), d, workdir=destdir)
Andre Rosa49271d42017-09-07 11:15:55 +0200559 runfetchcmd("%s branch %s --set-upstream-to origin/%s" % (ud.basecmd, branchname, \
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600560 branchname), d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500561 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600562 runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revisions[ud.names[0]]), d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500563
564 return True
565
566 def clean(self, ud, d):
567 """ clean the git directory """
568
Brad Bishop19323692019-04-05 15:28:33 -0400569 to_remove = [ud.localpath, ud.fullmirror, ud.fullmirror + ".done"]
570 # The localpath is a symlink to clonedir when it is cloned from a
571 # mirror, so remove both of them.
572 if os.path.islink(ud.localpath):
573 clonedir = os.path.realpath(ud.localpath)
574 to_remove.append(clonedir)
575
576 for r in to_remove:
577 if os.path.exists(r):
578 bb.note('Removing %s' % r)
579 bb.utils.remove(r, True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500580
581 def supports_srcrev(self):
582 return True
583
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600584 def _contains_ref(self, ud, d, name, wd):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500585 cmd = ""
586 if ud.nobranch:
587 cmd = "%s log --pretty=oneline -n 1 %s -- 2> /dev/null | wc -l" % (
588 ud.basecmd, ud.revisions[name])
589 else:
590 cmd = "%s branch --contains %s --list %s 2> /dev/null | wc -l" % (
591 ud.basecmd, ud.revisions[name], ud.branches[name])
592 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600593 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500594 except bb.fetch2.FetchError:
595 return False
596 if len(output.split()) > 1:
597 raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
598 return output.split()[0] != "0"
599
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600600 def _need_lfs(self, ud):
601 return ud.parm.get("lfs", "1") == "1"
602
Brad Bishopc342db32019-05-15 21:57:59 -0400603 def _contains_lfs(self, ud, d, wd):
604 """
605 Check if the repository has 'lfs' (large file) content
606 """
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500607
608 if not ud.nobranch:
609 branchname = ud.branches[ud.names[0]]
610 else:
611 branchname = "master"
612
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600613 # The bare clonedir doesn't use the remote names; it has the branch immediately.
614 if wd == ud.clonedir:
615 refname = ud.branches[ud.names[0]]
616 else:
617 refname = "origin/%s" % ud.branches[ud.names[0]]
618
619 cmd = "%s grep lfs %s:.gitattributes | wc -l" % (
620 ud.basecmd, refname)
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500621
Brad Bishopc342db32019-05-15 21:57:59 -0400622 try:
623 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
624 if int(output) > 0:
625 return True
626 except (bb.fetch2.FetchError,ValueError):
627 pass
628 return False
629
Brad Bishop00e122a2019-10-05 11:10:57 -0400630 def _find_git_lfs(self, d):
631 """
632 Return True if git-lfs can be found, False otherwise.
633 """
634 import shutil
635 return shutil.which("git-lfs", path=d.getVar('PATH')) is not None
636
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500637 def _get_repo_url(self, ud):
638 """
639 Return the repository URL
640 """
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600641 # Note that we do not support passwords directly in the git urls. There are several
642 # reasons. SRC_URI can be written out to things like buildhistory and people don't
643 # want to leak passwords like that. Its also all too easy to share metadata without
644 # removing the password. ssh keys, ~/.netrc and ~/.ssh/config files can be used as
645 # alternatives so we will not take patches adding password support here.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500646 if ud.user:
647 username = ud.user + '@'
648 else:
649 username = ""
650 return "%s://%s%s%s" % (ud.proto, username, ud.host, ud.path)
651
652 def _revision_key(self, ud, d, name):
653 """
654 Return a unique key for the url
655 """
Andrew Geissler82c905d2020-04-13 13:39:40 -0500656 # Collapse adjacent slashes
657 slash_re = re.compile(r"/+")
658 return "git:" + ud.host + slash_re.sub(".", ud.path) + ud.unresolvedrev[name]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500659
660 def _lsremote(self, ud, d, search):
661 """
662 Run git ls-remote with the specified search string
663 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500664 # Prevent recursion e.g. in OE if SRCPV is in PV, PV is in WORKDIR,
665 # and WORKDIR is in PATH (as a result of RSS), our call to
666 # runfetchcmd() exports PATH so this function will get called again (!)
667 # In this scenario the return call of the function isn't actually
668 # important - WORKDIR isn't needed in PATH to call git ls-remote
669 # anyway.
670 if d.getVar('_BB_GIT_IN_LSREMOTE', False):
671 return ''
672 d.setVar('_BB_GIT_IN_LSREMOTE', '1')
673 try:
674 repourl = self._get_repo_url(ud)
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500675 cmd = "%s ls-remote %s %s" % \
676 (ud.basecmd, shlex.quote(repourl), search)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500677 if ud.proto.lower() != 'file':
678 bb.fetch2.check_network_access(d, cmd, repourl)
679 output = runfetchcmd(cmd, d, True)
680 if not output:
681 raise bb.fetch2.FetchError("The command %s gave empty output unexpectedly" % cmd, ud.url)
682 finally:
683 d.delVar('_BB_GIT_IN_LSREMOTE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500684 return output
685
686 def _latest_revision(self, ud, d, name):
687 """
688 Compute the HEAD revision for the url
689 """
690 output = self._lsremote(ud, d, "")
691 # Tags of the form ^{} may not work, need to fallback to other form
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600692 if ud.unresolvedrev[name][:5] == "refs/" or ud.usehead:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500693 head = ud.unresolvedrev[name]
694 tag = ud.unresolvedrev[name]
695 else:
696 head = "refs/heads/%s" % ud.unresolvedrev[name]
697 tag = "refs/tags/%s" % ud.unresolvedrev[name]
698 for s in [head, tag + "^{}", tag]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600699 for l in output.strip().split('\n'):
700 sha1, ref = l.split()
701 if s == ref:
702 return sha1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500703 raise bb.fetch2.FetchError("Unable to resolve '%s' in upstream git repository in git ls-remote output for %s" % \
704 (ud.unresolvedrev[name], ud.host+ud.path))
705
706 def latest_versionstring(self, ud, d):
707 """
708 Compute the latest release name like "x.y.x" in "x.y.x+gitHASH"
709 by searching through the tags output of ls-remote, comparing
710 versions and returning the highest match.
711 """
712 pupver = ('', '')
713
Brad Bishop19323692019-04-05 15:28:33 -0400714 tagregex = re.compile(d.getVar('UPSTREAM_CHECK_GITTAGREGEX') or r"(?P<pver>([0-9][\.|_]?)+)")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500715 try:
716 output = self._lsremote(ud, d, "refs/tags/*")
Brad Bishop316dfdd2018-06-25 12:45:53 -0400717 except (bb.fetch2.FetchError, bb.fetch2.NetworkAccess) as e:
718 bb.note("Could not list remote: %s" % str(e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500719 return pupver
720
721 verstring = ""
722 revision = ""
723 for line in output.split("\n"):
724 if not line:
725 break
726
727 tag_head = line.split("/")[-1]
728 # Ignore non-released branches
Brad Bishop19323692019-04-05 15:28:33 -0400729 m = re.search(r"(alpha|beta|rc|final)+", tag_head)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500730 if m:
731 continue
732
733 # search for version in the line
734 tag = tagregex.search(tag_head)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500735 if tag is None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500736 continue
737
738 tag = tag.group('pver')
739 tag = tag.replace("_", ".")
740
741 if verstring and bb.utils.vercmp(("0", tag, ""), ("0", verstring, "")) < 0:
742 continue
743
744 verstring = tag
745 revision = line.split()[0]
746 pupver = (verstring, revision)
747
748 return pupver
749
750 def _build_revision(self, ud, d, name):
751 return ud.revisions[name]
752
753 def gitpkgv_revision(self, ud, d, name):
754 """
755 Return a sortable revision number by counting commits in the history
756 Based on gitpkgv.bblass in meta-openembedded
757 """
758 rev = self._build_revision(ud, d, name)
759 localpath = ud.localpath
760 rev_file = os.path.join(localpath, "oe-gitpkgv_" + rev)
761 if not os.path.exists(localpath):
762 commits = None
763 else:
764 if not os.path.exists(rev_file) or not os.path.getsize(rev_file):
765 from pipes import quote
766 commits = bb.fetch2.runfetchcmd(
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500767 "git rev-list %s -- | wc -l" % quote(rev),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500768 d, quiet=True).strip().lstrip('0')
769 if commits:
770 open(rev_file, "w").write("%d\n" % int(commits))
771 else:
772 commits = open(rev_file, "r").readline(128).strip()
773 if commits:
774 return False, "%s+%s" % (commits, rev[:7])
775 else:
776 return True, str(rev)
777
778 def checkstatus(self, fetch, ud, d):
779 try:
780 self._lsremote(ud, d, "")
781 return True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500782 except bb.fetch2.FetchError:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500783 return False