blob: e8ddf2c7615e5e84e43a94548fb6a4adea54fbb5 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001"""
2BitBake 'Fetch' git implementation
3
4git fetcher support the SRC_URI with format of:
5SRC_URI = "git://some.host/somepath;OptionA=xxx;OptionB=xxx;..."
6
7Supported SRC_URI options are:
8
9- branch
10 The git branch to retrieve from. The default is "master"
11
12 This option also supports multiple branch fetching, with branches
13 separated by commas. In multiple branches case, the name option
14 must have the same number of names to match the branches, which is
15 used to specify the SRC_REV for the branch
16 e.g:
17 SRC_URI="git://some.host/somepath;branch=branchX,branchY;name=nameX,nameY"
18 SRCREV_nameX = "xxxxxxxxxxxxxxxxxxxx"
19 SRCREV_nameY = "YYYYYYYYYYYYYYYYYYYY"
20
21- tag
22 The git tag to retrieve. The default is "master"
23
24- protocol
25 The method to use to access the repository. Common options are "git",
26 "http", "https", "file", "ssh" and "rsync". The default is "git".
27
28- rebaseable
29 rebaseable indicates that the upstream git repo may rebase in the future,
30 and current revision may disappear from upstream repo. This option will
31 remind fetcher to preserve local cache carefully for future use.
32 The default value is "0", set rebaseable=1 for rebaseable git repo.
33
34- nocheckout
35 Don't checkout source code when unpacking. set this option for the recipe
36 who has its own routine to checkout code.
37 The default is "0", set nocheckout=1 if needed.
38
39- bareclone
40 Create a bare clone of the source code and don't checkout the source code
41 when unpacking. Set this option for the recipe who has its own routine to
42 checkout code and tracking branch requirements.
43 The default is "0", set bareclone=1 if needed.
44
45- nobranch
46 Don't check the SHA validation for branch. set this option for the recipe
47 referring to commit which is valid in tag instead of branch.
48 The default is "0", set nobranch=1 if needed.
49
Patrick Williamsc0f7c042017-02-23 20:41:17 -060050- usehead
Brad Bishop6e60e8b2018-02-01 10:27:11 -050051 For local git:// urls to use the current branch HEAD as the revision for use with
Patrick Williamsc0f7c042017-02-23 20:41:17 -060052 AUTOREV. Implies nobranch.
53
Patrick Williamsc124f4f2015-09-15 14:41:29 -050054"""
55
Brad Bishopc342db32019-05-15 21:57:59 -040056# Copyright (C) 2005 Richard Purdie
Patrick Williamsc124f4f2015-09-15 14:41:29 -050057#
Brad Bishopc342db32019-05-15 21:57:59 -040058# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -050059#
Patrick Williamsc124f4f2015-09-15 14:41:29 -050060
Brad Bishopd7bf8c12018-02-25 22:55:05 -050061import collections
Patrick Williamsd7e96312015-09-22 08:09:05 -050062import errno
Brad Bishopd7bf8c12018-02-25 22:55:05 -050063import fnmatch
Patrick Williamsc124f4f2015-09-15 14:41:29 -050064import os
65import re
Andrew Geissler4c19ea12020-10-27 13:52:24 -050066import shlex
Brad Bishopd7bf8c12018-02-25 22:55:05 -050067import subprocess
68import tempfile
Patrick Williamsc124f4f2015-09-15 14:41:29 -050069import bb
Patrick Williamsc0f7c042017-02-23 20:41:17 -060070import bb.progress
Andrew Geissler5199d832021-09-24 16:47:35 -050071from contextlib import contextmanager
Patrick Williamsc124f4f2015-09-15 14:41:29 -050072from bb.fetch2 import FetchMethod
73from bb.fetch2 import runfetchcmd
74from bb.fetch2 import logger
75
Patrick Williamsc0f7c042017-02-23 20:41:17 -060076
77class GitProgressHandler(bb.progress.LineFilterProgressHandler):
78 """Extract progress information from git output"""
79 def __init__(self, d):
80 self._buffer = ''
81 self._count = 0
82 super(GitProgressHandler, self).__init__(d)
83 # Send an initial progress event so the bar gets shown
84 self._fire_progress(-1)
85
86 def write(self, string):
87 self._buffer += string
88 stages = ['Counting objects', 'Compressing objects', 'Receiving objects', 'Resolving deltas']
89 stage_weights = [0.2, 0.05, 0.5, 0.25]
90 stagenum = 0
91 for i, stage in reversed(list(enumerate(stages))):
92 if stage in self._buffer:
93 stagenum = i
94 self._buffer = ''
95 break
96 self._status = stages[stagenum]
97 percs = re.findall(r'(\d+)%', string)
98 if percs:
99 progress = int(round((int(percs[-1]) * stage_weights[stagenum]) + (sum(stage_weights[:stagenum]) * 100)))
100 rates = re.findall(r'([\d.]+ [a-zA-Z]*/s+)', string)
101 if rates:
102 rate = rates[-1]
103 else:
104 rate = None
105 self.update(progress, rate)
106 else:
107 if stagenum == 0:
108 percs = re.findall(r': (\d+)', string)
109 if percs:
110 count = int(percs[-1])
111 if count > self._count:
112 self._count = count
113 self._fire_progress(-count)
114 super(GitProgressHandler, self).write(string)
115
116
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500117class Git(FetchMethod):
Brad Bishop316dfdd2018-06-25 12:45:53 -0400118 bitbake_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.join(os.path.abspath(__file__))), '..', '..', '..'))
119 make_shallow_path = os.path.join(bitbake_dir, 'bin', 'git-make-shallow')
120
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500121 """Class to fetch a module or modules from git repositories"""
122 def init(self, d):
123 pass
124
125 def supports(self, ud, d):
126 """
127 Check to see if a given url can be fetched with git.
128 """
129 return ud.type in ['git']
130
131 def supports_checksum(self, urldata):
132 return False
133
134 def urldata_init(self, ud, d):
135 """
136 init git specific variable within url data
137 so that the git method like latest_revision() can work
138 """
139 if 'protocol' in ud.parm:
140 ud.proto = ud.parm['protocol']
141 elif not ud.host:
142 ud.proto = 'file'
143 else:
144 ud.proto = "git"
145
146 if not ud.proto in ('git', 'file', 'ssh', 'http', 'https', 'rsync'):
147 raise bb.fetch2.ParameterError("Invalid protocol type", ud.url)
148
149 ud.nocheckout = ud.parm.get("nocheckout","0") == "1"
150
151 ud.rebaseable = ud.parm.get("rebaseable","0") == "1"
152
153 ud.nobranch = ud.parm.get("nobranch","0") == "1"
154
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600155 # usehead implies nobranch
156 ud.usehead = ud.parm.get("usehead","0") == "1"
157 if ud.usehead:
158 if ud.proto != "file":
159 raise bb.fetch2.ParameterError("The usehead option is only for use with local ('protocol=file') git repositories", ud.url)
160 ud.nobranch = 1
161
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500162 # bareclone implies nocheckout
163 ud.bareclone = ud.parm.get("bareclone","0") == "1"
164 if ud.bareclone:
165 ud.nocheckout = 1
166
167 ud.unresolvedrev = {}
168 branches = ud.parm.get("branch", "master").split(',')
169 if len(branches) != len(ud.names):
170 raise bb.fetch2.ParameterError("The number of name and branch parameters is not balanced", ud.url)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500171
Andrew Geisslerc926e172021-05-07 16:11:35 -0500172 ud.noshared = d.getVar("BB_GIT_NOSHARED") == "1"
173
174 ud.cloneflags = "-n"
175 if not ud.noshared:
176 ud.cloneflags += " -s"
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500177 if ud.bareclone:
178 ud.cloneflags += " --mirror"
179
180 ud.shallow = d.getVar("BB_GIT_SHALLOW") == "1"
181 ud.shallow_extra_refs = (d.getVar("BB_GIT_SHALLOW_EXTRA_REFS") or "").split()
182
183 depth_default = d.getVar("BB_GIT_SHALLOW_DEPTH")
184 if depth_default is not None:
185 try:
186 depth_default = int(depth_default or 0)
187 except ValueError:
188 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH: %s" % depth_default)
189 else:
190 if depth_default < 0:
191 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH: %s" % depth_default)
192 else:
193 depth_default = 1
194 ud.shallow_depths = collections.defaultdict(lambda: depth_default)
195
Brad Bishop19323692019-04-05 15:28:33 -0400196 revs_default = d.getVar("BB_GIT_SHALLOW_REVS")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500197 ud.shallow_revs = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500198 ud.branches = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500199 for pos, name in enumerate(ud.names):
200 branch = branches[pos]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500201 ud.branches[name] = branch
202 ud.unresolvedrev[name] = branch
203
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500204 shallow_depth = d.getVar("BB_GIT_SHALLOW_DEPTH_%s" % name)
205 if shallow_depth is not None:
206 try:
207 shallow_depth = int(shallow_depth or 0)
208 except ValueError:
209 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth))
210 else:
211 if shallow_depth < 0:
212 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth))
213 ud.shallow_depths[name] = shallow_depth
214
215 revs = d.getVar("BB_GIT_SHALLOW_REVS_%s" % name)
216 if revs is not None:
217 ud.shallow_revs.extend(revs.split())
218 elif revs_default is not None:
219 ud.shallow_revs.extend(revs_default.split())
220
221 if (ud.shallow and
222 not ud.shallow_revs and
223 all(ud.shallow_depths[n] == 0 for n in ud.names)):
224 # Shallow disabled for this URL
225 ud.shallow = False
226
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600227 if ud.usehead:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600228 # When usehead is set let's associate 'HEAD' with the unresolved
229 # rev of this repository. This will get resolved into a revision
230 # later. If an actual revision happens to have also been provided
231 # then this setting will be overridden.
232 for name in ud.names:
233 ud.unresolvedrev[name] = 'HEAD'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600234
Andrew Geissler5f350902021-07-23 13:09:54 -0400235 ud.basecmd = d.getVar("FETCHCMD_git") or "git -c core.fsyncobjectfiles=0 -c gc.autoDetach=false"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500236
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500237 write_tarballs = d.getVar("BB_GENERATE_MIRROR_TARBALLS") or "0"
238 ud.write_tarballs = write_tarballs != "0" or ud.rebaseable
239 ud.write_shallow_tarballs = (d.getVar("BB_GENERATE_SHALLOW_TARBALLS") or write_tarballs) != "0"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500240
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500241 ud.setup_revisions(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500242
243 for name in ud.names:
244 # Ensure anything that doesn't look like a sha256 checksum/revision is translated into one
245 if not ud.revisions[name] or len(ud.revisions[name]) != 40 or (False in [c in "abcdef0123456789" for c in ud.revisions[name]]):
246 if ud.revisions[name]:
247 ud.unresolvedrev[name] = ud.revisions[name]
248 ud.revisions[name] = self.latest_revision(ud, d, name)
249
Andrew Geisslerc3d88e42020-10-02 09:45:00 -0500250 gitsrcname = '%s%s' % (ud.host.replace(':', '.'), ud.path.replace('/', '.').replace('*', '.').replace(' ','_'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500251 if gitsrcname.startswith('.'):
252 gitsrcname = gitsrcname[1:]
253
254 # for rebaseable git repo, it is necessary to keep mirror tar ball
255 # per revision, so that even the revision disappears from the
256 # upstream repo in the future, the mirror will remain intact and still
257 # contains the revision
258 if ud.rebaseable:
259 for name in ud.names:
260 gitsrcname = gitsrcname + '_' + ud.revisions[name]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500261
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500262 dl_dir = d.getVar("DL_DIR")
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800263 gitdir = d.getVar("GITDIR") or (dl_dir + "/git2")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500264 ud.clonedir = os.path.join(gitdir, gitsrcname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500265 ud.localfile = ud.clonedir
266
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500267 mirrortarball = 'git2_%s.tar.gz' % gitsrcname
268 ud.fullmirror = os.path.join(dl_dir, mirrortarball)
269 ud.mirrortarballs = [mirrortarball]
270 if ud.shallow:
271 tarballname = gitsrcname
272 if ud.bareclone:
273 tarballname = "%s_bare" % tarballname
274
275 if ud.shallow_revs:
276 tarballname = "%s_%s" % (tarballname, "_".join(sorted(ud.shallow_revs)))
277
278 for name, revision in sorted(ud.revisions.items()):
279 tarballname = "%s_%s" % (tarballname, ud.revisions[name][:7])
280 depth = ud.shallow_depths[name]
281 if depth:
282 tarballname = "%s-%s" % (tarballname, depth)
283
284 shallow_refs = []
285 if not ud.nobranch:
286 shallow_refs.extend(ud.branches.values())
287 if ud.shallow_extra_refs:
288 shallow_refs.extend(r.replace('refs/heads/', '').replace('*', 'ALL') for r in ud.shallow_extra_refs)
289 if shallow_refs:
290 tarballname = "%s_%s" % (tarballname, "_".join(sorted(shallow_refs)).replace('/', '.'))
291
292 fetcher = self.__class__.__name__.lower()
293 ud.shallowtarball = '%sshallow_%s.tar.gz' % (fetcher, tarballname)
294 ud.fullshallow = os.path.join(dl_dir, ud.shallowtarball)
295 ud.mirrortarballs.insert(0, ud.shallowtarball)
296
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500297 def localpath(self, ud, d):
298 return ud.clonedir
299
300 def need_update(self, ud, d):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800301 return self.clonedir_need_update(ud, d) or self.shallow_tarball_need_update(ud) or self.tarball_need_update(ud)
302
303 def clonedir_need_update(self, ud, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500304 if not os.path.exists(ud.clonedir):
305 return True
Brad Bishop64c979e2019-11-04 13:55:29 -0500306 if ud.shallow and ud.write_shallow_tarballs and self.clonedir_need_shallow_revs(ud, d):
307 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500308 for name in ud.names:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600309 if not self._contains_ref(ud, d, name, ud.clonedir):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500310 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500311 return False
312
Brad Bishop64c979e2019-11-04 13:55:29 -0500313 def clonedir_need_shallow_revs(self, ud, d):
314 for rev in ud.shallow_revs:
315 try:
316 runfetchcmd('%s rev-parse -q --verify %s' % (ud.basecmd, rev), d, quiet=True, workdir=ud.clonedir)
317 except bb.fetch2.FetchError:
318 return rev
319 return None
320
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800321 def shallow_tarball_need_update(self, ud):
322 return ud.shallow and ud.write_shallow_tarballs and not os.path.exists(ud.fullshallow)
323
324 def tarball_need_update(self, ud):
325 return ud.write_tarballs and not os.path.exists(ud.fullmirror)
326
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500327 def try_premirror(self, ud, d):
328 # If we don't do this, updating an existing checkout with only premirrors
329 # is not possible
Brad Bishop19323692019-04-05 15:28:33 -0400330 if bb.utils.to_boolean(d.getVar("BB_FETCH_PREMIRRORONLY")):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500331 return True
332 if os.path.exists(ud.clonedir):
333 return False
334 return True
335
336 def download(self, ud, d):
337 """Fetch url"""
338
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500339 # A current clone is preferred to either tarball, a shallow tarball is
340 # preferred to an out of date clone, and a missing clone will use
341 # either tarball.
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800342 if ud.shallow and os.path.exists(ud.fullshallow) and self.need_update(ud, d):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500343 ud.localpath = ud.fullshallow
344 return
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800345 elif os.path.exists(ud.fullmirror) and not os.path.exists(ud.clonedir):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500346 bb.utils.mkdirhier(ud.clonedir)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500347 runfetchcmd("tar -xzf %s" % ud.fullmirror, d, workdir=ud.clonedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500348
349 repourl = self._get_repo_url(ud)
350
351 # If the repo still doesn't exist, fallback to cloning it
352 if not os.path.exists(ud.clonedir):
353 # We do this since git will use a "-l" option automatically for local urls where possible
354 if repourl.startswith("file://"):
355 repourl = repourl[7:]
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500356 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 -0500357 if ud.proto.lower() != 'file':
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500358 bb.fetch2.check_network_access(d, clone_cmd, ud.url)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600359 progresshandler = GitProgressHandler(d)
360 runfetchcmd(clone_cmd, d, log=progresshandler)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500361
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500362 # Update the checkout if needed
Brad Bishop64c979e2019-11-04 13:55:29 -0500363 if self.clonedir_need_update(ud, d):
Brad Bishop6ef32652018-10-09 18:59:25 +0100364 output = runfetchcmd("%s remote" % ud.basecmd, d, quiet=True, workdir=ud.clonedir)
365 if "origin" in output:
366 runfetchcmd("%s remote rm origin" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500367
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500368 runfetchcmd("%s remote add --mirror=fetch origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=ud.clonedir)
369 fetch_cmd = "LANG=C %s fetch -f --progress %s refs/*:refs/*" % (ud.basecmd, shlex.quote(repourl))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500370 if ud.proto.lower() != 'file':
371 bb.fetch2.check_network_access(d, fetch_cmd, ud.url)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600372 progresshandler = GitProgressHandler(d)
373 runfetchcmd(fetch_cmd, d, log=progresshandler, workdir=ud.clonedir)
374 runfetchcmd("%s prune-packed" % ud.basecmd, d, workdir=ud.clonedir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400375 runfetchcmd("%s pack-refs --all" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600376 runfetchcmd("%s pack-redundant --all | xargs -r rm" % ud.basecmd, d, workdir=ud.clonedir)
Patrick Williamsd7e96312015-09-22 08:09:05 -0500377 try:
378 os.unlink(ud.fullmirror)
379 except OSError as exc:
380 if exc.errno != errno.ENOENT:
381 raise
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800382
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500383 for name in ud.names:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600384 if not self._contains_ref(ud, d, name, ud.clonedir):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500385 raise bb.fetch2.FetchError("Unable to find revision %s in branch %s even from upstream" % (ud.revisions[name], ud.branches[name]))
386
Brad Bishop64c979e2019-11-04 13:55:29 -0500387 if ud.shallow and ud.write_shallow_tarballs:
388 missing_rev = self.clonedir_need_shallow_revs(ud, d)
389 if missing_rev:
390 raise bb.fetch2.FetchError("Unable to find revision %s even from upstream" % missing_rev)
391
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600392 if self._contains_lfs(ud, d, ud.clonedir) and self._need_lfs(ud):
393 # Unpack temporary working copy, use it to run 'git checkout' to force pre-fetching
394 # of all LFS blobs needed at the the srcrev.
395 #
396 # It would be nice to just do this inline here by running 'git-lfs fetch'
397 # on the bare clonedir, but that operation requires a working copy on some
398 # releases of Git LFS.
399 tmpdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
400 try:
401 # Do the checkout. This implicitly involves a Git LFS fetch.
Andrew Geisslerc926e172021-05-07 16:11:35 -0500402 Git.unpack(self, ud, tmpdir, d)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600403
404 # Scoop up a copy of any stuff that Git LFS downloaded. Merge them into
405 # the bare clonedir.
406 #
407 # As this procedure is invoked repeatedly on incremental fetches as
408 # a recipe's SRCREV is bumped throughout its lifetime, this will
409 # result in a gradual accumulation of LFS blobs in <ud.clonedir>/lfs
410 # corresponding to all the blobs reachable from the different revs
411 # fetched across time.
412 #
413 # Only do this if the unpack resulted in a .git/lfs directory being
414 # created; this only happens if at least one blob needed to be
415 # downloaded.
416 if os.path.exists(os.path.join(tmpdir, "git", ".git", "lfs")):
417 runfetchcmd("tar -cf - lfs | tar -xf - -C %s" % ud.clonedir, d, workdir="%s/git/.git" % tmpdir)
418 finally:
419 bb.utils.remove(tmpdir, recurse=True)
420
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500421 def build_mirror_data(self, ud, d):
Andrew Geissler5199d832021-09-24 16:47:35 -0500422
423 # Create as a temp file and move atomically into position to avoid races
424 @contextmanager
425 def create_atomic(filename):
426 fd, tfile = tempfile.mkstemp(dir=os.path.dirname(filename))
427 try:
428 yield tfile
429 umask = os.umask(0o666)
430 os.umask(umask)
431 os.chmod(tfile, (0o666 & ~umask))
432 os.rename(tfile, filename)
433 finally:
434 os.close(fd)
435
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500436 if ud.shallow and ud.write_shallow_tarballs:
437 if not os.path.exists(ud.fullshallow):
438 if os.path.islink(ud.fullshallow):
439 os.unlink(ud.fullshallow)
440 tempdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
441 shallowclone = os.path.join(tempdir, 'git')
442 try:
443 self.clone_shallow_local(ud, shallowclone, d)
444
445 logger.info("Creating tarball of git repository")
Andrew Geissler5199d832021-09-24 16:47:35 -0500446 with create_atomic(ud.fullshallow) as tfile:
447 runfetchcmd("tar -czf %s ." % tfile, d, workdir=shallowclone)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500448 runfetchcmd("touch %s.done" % ud.fullshallow, d)
449 finally:
450 bb.utils.remove(tempdir, recurse=True)
451 elif ud.write_tarballs and not os.path.exists(ud.fullmirror):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500452 if os.path.islink(ud.fullmirror):
453 os.unlink(ud.fullmirror)
454
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500455 logger.info("Creating tarball of git repository")
Andrew Geissler5199d832021-09-24 16:47:35 -0500456 with create_atomic(ud.fullmirror) as tfile:
457 runfetchcmd("tar -czf %s ." % tfile, d, workdir=ud.clonedir)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500458 runfetchcmd("touch %s.done" % ud.fullmirror, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500459
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500460 def clone_shallow_local(self, ud, dest, d):
461 """Clone the repo and make it shallow.
462
463 The upstream url of the new clone isn't set at this time, as it'll be
464 set correctly when unpacked."""
465 runfetchcmd("%s clone %s %s %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, dest), d)
466
467 to_parse, shallow_branches = [], []
468 for name in ud.names:
469 revision = ud.revisions[name]
470 depth = ud.shallow_depths[name]
471 if depth:
472 to_parse.append('%s~%d^{}' % (revision, depth - 1))
473
474 # For nobranch, we need a ref, otherwise the commits will be
475 # removed, and for non-nobranch, we truncate the branch to our
476 # srcrev, to avoid keeping unnecessary history beyond that.
477 branch = ud.branches[name]
478 if ud.nobranch:
479 ref = "refs/shallow/%s" % name
480 elif ud.bareclone:
481 ref = "refs/heads/%s" % branch
482 else:
483 ref = "refs/remotes/origin/%s" % branch
484
485 shallow_branches.append(ref)
486 runfetchcmd("%s update-ref %s %s" % (ud.basecmd, ref, revision), d, workdir=dest)
487
488 # Map srcrev+depths to revisions
489 parsed_depths = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join(to_parse)), d, workdir=dest)
490
491 # Resolve specified revisions
492 parsed_revs = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join('"%s^{}"' % r for r in ud.shallow_revs)), d, workdir=dest)
493 shallow_revisions = parsed_depths.splitlines() + parsed_revs.splitlines()
494
495 # Apply extra ref wildcards
496 all_refs = runfetchcmd('%s for-each-ref "--format=%%(refname)"' % ud.basecmd,
497 d, workdir=dest).splitlines()
498 for r in ud.shallow_extra_refs:
499 if not ud.bareclone:
500 r = r.replace('refs/heads/', 'refs/remotes/origin/')
501
502 if '*' in r:
503 matches = filter(lambda a: fnmatch.fnmatchcase(a, r), all_refs)
504 shallow_branches.extend(matches)
505 else:
506 shallow_branches.append(r)
507
508 # Make the repository shallow
Brad Bishop316dfdd2018-06-25 12:45:53 -0400509 shallow_cmd = [self.make_shallow_path, '-s']
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500510 for b in shallow_branches:
511 shallow_cmd.append('-r')
512 shallow_cmd.append(b)
513 shallow_cmd.extend(shallow_revisions)
514 runfetchcmd(subprocess.list2cmdline(shallow_cmd), d, workdir=dest)
515
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500516 def unpack(self, ud, destdir, d):
517 """ unpack the downloaded src to destdir"""
518
519 subdir = ud.parm.get("subpath", "")
520 if subdir != "":
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500521 readpathspec = ":%s" % subdir
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500522 def_destsuffix = "%s/" % os.path.basename(subdir.rstrip('/'))
523 else:
524 readpathspec = ""
525 def_destsuffix = "git/"
526
527 destsuffix = ud.parm.get("destsuffix", def_destsuffix)
528 destdir = ud.destdir = os.path.join(destdir, destsuffix)
529 if os.path.exists(destdir):
530 bb.utils.prunedir(destdir)
531
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600532 need_lfs = self._need_lfs(ud)
Brad Bishopa34c0302019-09-23 22:34:48 -0400533
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500534 if not need_lfs:
535 ud.basecmd = "GIT_LFS_SKIP_SMUDGE=1 " + ud.basecmd
536
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800537 source_found = False
538 source_error = []
539
540 if not source_found:
541 clonedir_is_up_to_date = not self.clonedir_need_update(ud, d)
542 if clonedir_is_up_to_date:
543 runfetchcmd("%s clone %s %s/ %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, destdir), d)
544 source_found = True
545 else:
546 source_error.append("clone directory not available or not up to date: " + ud.clonedir)
547
548 if not source_found:
549 if ud.shallow:
550 if os.path.exists(ud.fullshallow):
551 bb.utils.mkdirhier(destdir)
552 runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=destdir)
553 source_found = True
554 else:
555 source_error.append("shallow clone not available: " + ud.fullshallow)
556 else:
557 source_error.append("shallow clone not enabled")
558
559 if not source_found:
560 raise bb.fetch2.UnpackError("No up to date source found: " + "; ".join(source_error), ud.url)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500561
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500562 repourl = self._get_repo_url(ud)
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500563 runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=destdir)
Brad Bishopc342db32019-05-15 21:57:59 -0400564
565 if self._contains_lfs(ud, d, destdir):
Brad Bishop00e122a2019-10-05 11:10:57 -0400566 if need_lfs and not self._find_git_lfs(d):
567 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 -0500568 elif not need_lfs:
Brad Bishopa34c0302019-09-23 22:34:48 -0400569 bb.note("Repository %s has LFS content but it is not being fetched" % (repourl))
Brad Bishopc342db32019-05-15 21:57:59 -0400570
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500571 if not ud.nocheckout:
572 if subdir != "":
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600573 runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d,
574 workdir=destdir)
575 runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500576 elif not ud.nobranch:
577 branchname = ud.branches[ud.names[0]]
578 runfetchcmd("%s checkout -B %s %s" % (ud.basecmd, branchname, \
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600579 ud.revisions[ud.names[0]]), d, workdir=destdir)
Andre Rosa49271d42017-09-07 11:15:55 +0200580 runfetchcmd("%s branch %s --set-upstream-to origin/%s" % (ud.basecmd, branchname, \
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600581 branchname), d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500582 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600583 runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revisions[ud.names[0]]), d, workdir=destdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500584
585 return True
586
587 def clean(self, ud, d):
588 """ clean the git directory """
589
Brad Bishop19323692019-04-05 15:28:33 -0400590 to_remove = [ud.localpath, ud.fullmirror, ud.fullmirror + ".done"]
591 # The localpath is a symlink to clonedir when it is cloned from a
592 # mirror, so remove both of them.
593 if os.path.islink(ud.localpath):
594 clonedir = os.path.realpath(ud.localpath)
595 to_remove.append(clonedir)
596
597 for r in to_remove:
598 if os.path.exists(r):
599 bb.note('Removing %s' % r)
600 bb.utils.remove(r, True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500601
602 def supports_srcrev(self):
603 return True
604
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600605 def _contains_ref(self, ud, d, name, wd):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500606 cmd = ""
607 if ud.nobranch:
608 cmd = "%s log --pretty=oneline -n 1 %s -- 2> /dev/null | wc -l" % (
609 ud.basecmd, ud.revisions[name])
610 else:
611 cmd = "%s branch --contains %s --list %s 2> /dev/null | wc -l" % (
612 ud.basecmd, ud.revisions[name], ud.branches[name])
613 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600614 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500615 except bb.fetch2.FetchError:
616 return False
617 if len(output.split()) > 1:
618 raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
619 return output.split()[0] != "0"
620
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600621 def _need_lfs(self, ud):
622 return ud.parm.get("lfs", "1") == "1"
623
Brad Bishopc342db32019-05-15 21:57:59 -0400624 def _contains_lfs(self, ud, d, wd):
625 """
626 Check if the repository has 'lfs' (large file) content
627 """
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500628
629 if not ud.nobranch:
630 branchname = ud.branches[ud.names[0]]
631 else:
632 branchname = "master"
633
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600634 # The bare clonedir doesn't use the remote names; it has the branch immediately.
635 if wd == ud.clonedir:
636 refname = ud.branches[ud.names[0]]
637 else:
638 refname = "origin/%s" % ud.branches[ud.names[0]]
639
640 cmd = "%s grep lfs %s:.gitattributes | wc -l" % (
641 ud.basecmd, refname)
Andrew Geissler4ed12e12020-06-05 18:00:41 -0500642
Brad Bishopc342db32019-05-15 21:57:59 -0400643 try:
644 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
645 if int(output) > 0:
646 return True
647 except (bb.fetch2.FetchError,ValueError):
648 pass
649 return False
650
Brad Bishop00e122a2019-10-05 11:10:57 -0400651 def _find_git_lfs(self, d):
652 """
653 Return True if git-lfs can be found, False otherwise.
654 """
655 import shutil
656 return shutil.which("git-lfs", path=d.getVar('PATH')) is not None
657
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500658 def _get_repo_url(self, ud):
659 """
660 Return the repository URL
661 """
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600662 # Note that we do not support passwords directly in the git urls. There are several
663 # reasons. SRC_URI can be written out to things like buildhistory and people don't
664 # want to leak passwords like that. Its also all too easy to share metadata without
665 # removing the password. ssh keys, ~/.netrc and ~/.ssh/config files can be used as
666 # alternatives so we will not take patches adding password support here.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500667 if ud.user:
668 username = ud.user + '@'
669 else:
670 username = ""
671 return "%s://%s%s%s" % (ud.proto, username, ud.host, ud.path)
672
673 def _revision_key(self, ud, d, name):
674 """
675 Return a unique key for the url
676 """
Andrew Geissler82c905d2020-04-13 13:39:40 -0500677 # Collapse adjacent slashes
678 slash_re = re.compile(r"/+")
679 return "git:" + ud.host + slash_re.sub(".", ud.path) + ud.unresolvedrev[name]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500680
681 def _lsremote(self, ud, d, search):
682 """
683 Run git ls-remote with the specified search string
684 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500685 # Prevent recursion e.g. in OE if SRCPV is in PV, PV is in WORKDIR,
686 # and WORKDIR is in PATH (as a result of RSS), our call to
687 # runfetchcmd() exports PATH so this function will get called again (!)
688 # In this scenario the return call of the function isn't actually
689 # important - WORKDIR isn't needed in PATH to call git ls-remote
690 # anyway.
691 if d.getVar('_BB_GIT_IN_LSREMOTE', False):
692 return ''
693 d.setVar('_BB_GIT_IN_LSREMOTE', '1')
694 try:
695 repourl = self._get_repo_url(ud)
Andrew Geissler4c19ea12020-10-27 13:52:24 -0500696 cmd = "%s ls-remote %s %s" % \
697 (ud.basecmd, shlex.quote(repourl), search)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500698 if ud.proto.lower() != 'file':
699 bb.fetch2.check_network_access(d, cmd, repourl)
700 output = runfetchcmd(cmd, d, True)
701 if not output:
702 raise bb.fetch2.FetchError("The command %s gave empty output unexpectedly" % cmd, ud.url)
703 finally:
704 d.delVar('_BB_GIT_IN_LSREMOTE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500705 return output
706
707 def _latest_revision(self, ud, d, name):
708 """
709 Compute the HEAD revision for the url
710 """
711 output = self._lsremote(ud, d, "")
712 # Tags of the form ^{} may not work, need to fallback to other form
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600713 if ud.unresolvedrev[name][:5] == "refs/" or ud.usehead:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500714 head = ud.unresolvedrev[name]
715 tag = ud.unresolvedrev[name]
716 else:
717 head = "refs/heads/%s" % ud.unresolvedrev[name]
718 tag = "refs/tags/%s" % ud.unresolvedrev[name]
719 for s in [head, tag + "^{}", tag]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600720 for l in output.strip().split('\n'):
721 sha1, ref = l.split()
722 if s == ref:
723 return sha1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500724 raise bb.fetch2.FetchError("Unable to resolve '%s' in upstream git repository in git ls-remote output for %s" % \
725 (ud.unresolvedrev[name], ud.host+ud.path))
726
727 def latest_versionstring(self, ud, d):
728 """
729 Compute the latest release name like "x.y.x" in "x.y.x+gitHASH"
730 by searching through the tags output of ls-remote, comparing
731 versions and returning the highest match.
732 """
733 pupver = ('', '')
734
Brad Bishop19323692019-04-05 15:28:33 -0400735 tagregex = re.compile(d.getVar('UPSTREAM_CHECK_GITTAGREGEX') or r"(?P<pver>([0-9][\.|_]?)+)")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500736 try:
737 output = self._lsremote(ud, d, "refs/tags/*")
Brad Bishop316dfdd2018-06-25 12:45:53 -0400738 except (bb.fetch2.FetchError, bb.fetch2.NetworkAccess) as e:
739 bb.note("Could not list remote: %s" % str(e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500740 return pupver
741
742 verstring = ""
743 revision = ""
744 for line in output.split("\n"):
745 if not line:
746 break
747
748 tag_head = line.split("/")[-1]
749 # Ignore non-released branches
Brad Bishop19323692019-04-05 15:28:33 -0400750 m = re.search(r"(alpha|beta|rc|final)+", tag_head)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500751 if m:
752 continue
753
754 # search for version in the line
755 tag = tagregex.search(tag_head)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500756 if tag is None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500757 continue
758
759 tag = tag.group('pver')
760 tag = tag.replace("_", ".")
761
762 if verstring and bb.utils.vercmp(("0", tag, ""), ("0", verstring, "")) < 0:
763 continue
764
765 verstring = tag
766 revision = line.split()[0]
767 pupver = (verstring, revision)
768
769 return pupver
770
771 def _build_revision(self, ud, d, name):
772 return ud.revisions[name]
773
774 def gitpkgv_revision(self, ud, d, name):
775 """
776 Return a sortable revision number by counting commits in the history
777 Based on gitpkgv.bblass in meta-openembedded
778 """
779 rev = self._build_revision(ud, d, name)
780 localpath = ud.localpath
781 rev_file = os.path.join(localpath, "oe-gitpkgv_" + rev)
782 if not os.path.exists(localpath):
783 commits = None
784 else:
785 if not os.path.exists(rev_file) or not os.path.getsize(rev_file):
786 from pipes import quote
787 commits = bb.fetch2.runfetchcmd(
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500788 "git rev-list %s -- | wc -l" % quote(rev),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500789 d, quiet=True).strip().lstrip('0')
790 if commits:
791 open(rev_file, "w").write("%d\n" % int(commits))
792 else:
793 commits = open(rev_file, "r").readline(128).strip()
794 if commits:
795 return False, "%s+%s" % (commits, rev[:7])
796 else:
797 return True, str(rev)
798
799 def checkstatus(self, fetch, ud, d):
800 try:
801 self._lsremote(ud, d, "")
802 return True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500803 except bb.fetch2.FetchError:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500804 return False