blob: 526668bc23c6d78647de86ba8e7a300aac2be9c0 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3"""
4BitBake 'Fetch' git implementation
5
6git fetcher support the SRC_URI with format of:
7SRC_URI = "git://some.host/somepath;OptionA=xxx;OptionB=xxx;..."
8
9Supported SRC_URI options are:
10
11- branch
12 The git branch to retrieve from. The default is "master"
13
14 This option also supports multiple branch fetching, with branches
15 separated by commas. In multiple branches case, the name option
16 must have the same number of names to match the branches, which is
17 used to specify the SRC_REV for the branch
18 e.g:
19 SRC_URI="git://some.host/somepath;branch=branchX,branchY;name=nameX,nameY"
20 SRCREV_nameX = "xxxxxxxxxxxxxxxxxxxx"
21 SRCREV_nameY = "YYYYYYYYYYYYYYYYYYYY"
22
23- tag
24 The git tag to retrieve. The default is "master"
25
26- protocol
27 The method to use to access the repository. Common options are "git",
28 "http", "https", "file", "ssh" and "rsync". The default is "git".
29
30- rebaseable
31 rebaseable indicates that the upstream git repo may rebase in the future,
32 and current revision may disappear from upstream repo. This option will
33 remind fetcher to preserve local cache carefully for future use.
34 The default value is "0", set rebaseable=1 for rebaseable git repo.
35
36- nocheckout
37 Don't checkout source code when unpacking. set this option for the recipe
38 who has its own routine to checkout code.
39 The default is "0", set nocheckout=1 if needed.
40
41- bareclone
42 Create a bare clone of the source code and don't checkout the source code
43 when unpacking. Set this option for the recipe who has its own routine to
44 checkout code and tracking branch requirements.
45 The default is "0", set bareclone=1 if needed.
46
47- nobranch
48 Don't check the SHA validation for branch. set this option for the recipe
49 referring to commit which is valid in tag instead of branch.
50 The default is "0", set nobranch=1 if needed.
51
52"""
53
54#Copyright (C) 2005 Richard Purdie
55#
56# This program is free software; you can redistribute it and/or modify
57# it under the terms of the GNU General Public License version 2 as
58# published by the Free Software Foundation.
59#
60# This program is distributed in the hope that it will be useful,
61# but WITHOUT ANY WARRANTY; without even the implied warranty of
62# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
63# GNU General Public License for more details.
64#
65# You should have received a copy of the GNU General Public License along
66# with this program; if not, write to the Free Software Foundation, Inc.,
67# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
68
Patrick Williamsd7e96312015-09-22 08:09:05 -050069import errno
Patrick Williamsc124f4f2015-09-15 14:41:29 -050070import os
71import re
72import bb
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050073import errno
Patrick Williamsc124f4f2015-09-15 14:41:29 -050074from bb import data
75from bb.fetch2 import FetchMethod
76from bb.fetch2 import runfetchcmd
77from bb.fetch2 import logger
78
79class Git(FetchMethod):
80 """Class to fetch a module or modules from git repositories"""
81 def init(self, d):
82 pass
83
84 def supports(self, ud, d):
85 """
86 Check to see if a given url can be fetched with git.
87 """
88 return ud.type in ['git']
89
90 def supports_checksum(self, urldata):
91 return False
92
93 def urldata_init(self, ud, d):
94 """
95 init git specific variable within url data
96 so that the git method like latest_revision() can work
97 """
98 if 'protocol' in ud.parm:
99 ud.proto = ud.parm['protocol']
100 elif not ud.host:
101 ud.proto = 'file'
102 else:
103 ud.proto = "git"
104
105 if not ud.proto in ('git', 'file', 'ssh', 'http', 'https', 'rsync'):
106 raise bb.fetch2.ParameterError("Invalid protocol type", ud.url)
107
108 ud.nocheckout = ud.parm.get("nocheckout","0") == "1"
109
110 ud.rebaseable = ud.parm.get("rebaseable","0") == "1"
111
112 ud.nobranch = ud.parm.get("nobranch","0") == "1"
113
114 # bareclone implies nocheckout
115 ud.bareclone = ud.parm.get("bareclone","0") == "1"
116 if ud.bareclone:
117 ud.nocheckout = 1
118
119 ud.unresolvedrev = {}
120 branches = ud.parm.get("branch", "master").split(',')
121 if len(branches) != len(ud.names):
122 raise bb.fetch2.ParameterError("The number of name and branch parameters is not balanced", ud.url)
123 ud.branches = {}
124 for name in ud.names:
125 branch = branches[ud.names.index(name)]
126 ud.branches[name] = branch
127 ud.unresolvedrev[name] = branch
128
129 ud.basecmd = data.getVar("FETCHCMD_git", d, True) or "git -c core.fsyncobjectfiles=0"
130
131 ud.write_tarballs = ((data.getVar("BB_GENERATE_MIRROR_TARBALLS", d, True) or "0") != "0") or ud.rebaseable
132
133 ud.setup_revisons(d)
134
135 for name in ud.names:
136 # Ensure anything that doesn't look like a sha256 checksum/revision is translated into one
137 if not ud.revisions[name] or len(ud.revisions[name]) != 40 or (False in [c in "abcdef0123456789" for c in ud.revisions[name]]):
138 if ud.revisions[name]:
139 ud.unresolvedrev[name] = ud.revisions[name]
140 ud.revisions[name] = self.latest_revision(ud, d, name)
141
142 gitsrcname = '%s%s' % (ud.host.replace(':', '.'), ud.path.replace('/', '.').replace('*', '.'))
143 if gitsrcname.startswith('.'):
144 gitsrcname = gitsrcname[1:]
145
146 # for rebaseable git repo, it is necessary to keep mirror tar ball
147 # per revision, so that even the revision disappears from the
148 # upstream repo in the future, the mirror will remain intact and still
149 # contains the revision
150 if ud.rebaseable:
151 for name in ud.names:
152 gitsrcname = gitsrcname + '_' + ud.revisions[name]
153 ud.mirrortarball = 'git2_%s.tar.gz' % (gitsrcname)
154 ud.fullmirror = os.path.join(d.getVar("DL_DIR", True), ud.mirrortarball)
155 gitdir = d.getVar("GITDIR", True) or (d.getVar("DL_DIR", True) + "/git2/")
156 ud.clonedir = os.path.join(gitdir, gitsrcname)
157
158 ud.localfile = ud.clonedir
159
160 def localpath(self, ud, d):
161 return ud.clonedir
162
163 def need_update(self, ud, d):
164 if not os.path.exists(ud.clonedir):
165 return True
166 os.chdir(ud.clonedir)
167 for name in ud.names:
168 if not self._contains_ref(ud, d, name):
169 return True
170 if ud.write_tarballs and not os.path.exists(ud.fullmirror):
171 return True
172 return False
173
174 def try_premirror(self, ud, d):
175 # If we don't do this, updating an existing checkout with only premirrors
176 # is not possible
177 if d.getVar("BB_FETCH_PREMIRRORONLY", True) is not None:
178 return True
179 if os.path.exists(ud.clonedir):
180 return False
181 return True
182
183 def download(self, ud, d):
184 """Fetch url"""
185
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500186 # If the checkout doesn't exist and the mirror tarball does, extract it
187 if not os.path.exists(ud.clonedir) and os.path.exists(ud.fullmirror):
188 bb.utils.mkdirhier(ud.clonedir)
189 os.chdir(ud.clonedir)
190 runfetchcmd("tar -xzf %s" % (ud.fullmirror), d)
191
192 repourl = self._get_repo_url(ud)
193
194 # If the repo still doesn't exist, fallback to cloning it
195 if not os.path.exists(ud.clonedir):
196 # We do this since git will use a "-l" option automatically for local urls where possible
197 if repourl.startswith("file://"):
198 repourl = repourl[7:]
199 clone_cmd = "%s clone --bare --mirror %s %s" % (ud.basecmd, repourl, ud.clonedir)
200 if ud.proto.lower() != 'file':
201 bb.fetch2.check_network_access(d, clone_cmd)
202 runfetchcmd(clone_cmd, d)
203
204 os.chdir(ud.clonedir)
205 # Update the checkout if needed
206 needupdate = False
207 for name in ud.names:
208 if not self._contains_ref(ud, d, name):
209 needupdate = True
210 if needupdate:
211 try:
212 runfetchcmd("%s remote rm origin" % ud.basecmd, d)
213 except bb.fetch2.FetchError:
214 logger.debug(1, "No Origin")
215
216 runfetchcmd("%s remote add --mirror=fetch origin %s" % (ud.basecmd, repourl), d)
217 fetch_cmd = "%s fetch -f --prune %s refs/*:refs/*" % (ud.basecmd, repourl)
218 if ud.proto.lower() != 'file':
219 bb.fetch2.check_network_access(d, fetch_cmd, ud.url)
220 runfetchcmd(fetch_cmd, d)
221 runfetchcmd("%s prune-packed" % ud.basecmd, d)
222 runfetchcmd("%s pack-redundant --all | xargs -r rm" % ud.basecmd, d)
Patrick Williamsd7e96312015-09-22 08:09:05 -0500223 try:
224 os.unlink(ud.fullmirror)
225 except OSError as exc:
226 if exc.errno != errno.ENOENT:
227 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500228 os.chdir(ud.clonedir)
229 for name in ud.names:
230 if not self._contains_ref(ud, d, name):
231 raise bb.fetch2.FetchError("Unable to find revision %s in branch %s even from upstream" % (ud.revisions[name], ud.branches[name]))
232
233 def build_mirror_data(self, ud, d):
234 # Generate a mirror tarball if needed
Patrick Williamsd7e96312015-09-22 08:09:05 -0500235 if ud.write_tarballs and not os.path.exists(ud.fullmirror):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500236 # it's possible that this symlink points to read-only filesystem with PREMIRROR
237 if os.path.islink(ud.fullmirror):
238 os.unlink(ud.fullmirror)
239
240 os.chdir(ud.clonedir)
241 logger.info("Creating tarball of git repository")
242 runfetchcmd("tar -czf %s %s" % (ud.fullmirror, os.path.join(".") ), d)
243 runfetchcmd("touch %s.done" % (ud.fullmirror), d)
244
245 def unpack(self, ud, destdir, d):
246 """ unpack the downloaded src to destdir"""
247
248 subdir = ud.parm.get("subpath", "")
249 if subdir != "":
250 readpathspec = ":%s" % (subdir)
251 def_destsuffix = "%s/" % os.path.basename(subdir.rstrip('/'))
252 else:
253 readpathspec = ""
254 def_destsuffix = "git/"
255
256 destsuffix = ud.parm.get("destsuffix", def_destsuffix)
257 destdir = ud.destdir = os.path.join(destdir, destsuffix)
258 if os.path.exists(destdir):
259 bb.utils.prunedir(destdir)
260
261 cloneflags = "-s -n"
262 if ud.bareclone:
263 cloneflags += " --mirror"
264
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500265 runfetchcmd("%s clone %s %s/ %s" % (ud.basecmd, cloneflags, ud.clonedir, destdir), d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500266 os.chdir(destdir)
267 repourl = self._get_repo_url(ud)
268 runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, repourl), d)
269 if not ud.nocheckout:
270 if subdir != "":
271 runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d)
272 runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d)
273 elif not ud.nobranch:
274 branchname = ud.branches[ud.names[0]]
275 runfetchcmd("%s checkout -B %s %s" % (ud.basecmd, branchname, \
276 ud.revisions[ud.names[0]]), d)
277 runfetchcmd("%s branch --set-upstream %s origin/%s" % (ud.basecmd, branchname, \
278 branchname), d)
279 else:
280 runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revisions[ud.names[0]]), d)
281
282 return True
283
284 def clean(self, ud, d):
285 """ clean the git directory """
286
287 bb.utils.remove(ud.localpath, True)
288 bb.utils.remove(ud.fullmirror)
289 bb.utils.remove(ud.fullmirror + ".done")
290
291 def supports_srcrev(self):
292 return True
293
294 def _contains_ref(self, ud, d, name):
295 cmd = ""
296 if ud.nobranch:
297 cmd = "%s log --pretty=oneline -n 1 %s -- 2> /dev/null | wc -l" % (
298 ud.basecmd, ud.revisions[name])
299 else:
300 cmd = "%s branch --contains %s --list %s 2> /dev/null | wc -l" % (
301 ud.basecmd, ud.revisions[name], ud.branches[name])
302 try:
303 output = runfetchcmd(cmd, d, quiet=True)
304 except bb.fetch2.FetchError:
305 return False
306 if len(output.split()) > 1:
307 raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
308 return output.split()[0] != "0"
309
310 def _get_repo_url(self, ud):
311 """
312 Return the repository URL
313 """
314 if ud.user:
315 username = ud.user + '@'
316 else:
317 username = ""
318 return "%s://%s%s%s" % (ud.proto, username, ud.host, ud.path)
319
320 def _revision_key(self, ud, d, name):
321 """
322 Return a unique key for the url
323 """
324 return "git:" + ud.host + ud.path.replace('/', '.') + ud.unresolvedrev[name]
325
326 def _lsremote(self, ud, d, search):
327 """
328 Run git ls-remote with the specified search string
329 """
330 repourl = self._get_repo_url(ud)
331 cmd = "%s ls-remote %s %s" % \
332 (ud.basecmd, repourl, search)
333 if ud.proto.lower() != 'file':
334 bb.fetch2.check_network_access(d, cmd)
335 output = runfetchcmd(cmd, d, True)
336 if not output:
337 raise bb.fetch2.FetchError("The command %s gave empty output unexpectedly" % cmd, ud.url)
338 return output
339
340 def _latest_revision(self, ud, d, name):
341 """
342 Compute the HEAD revision for the url
343 """
344 output = self._lsremote(ud, d, "")
345 # Tags of the form ^{} may not work, need to fallback to other form
346 if ud.unresolvedrev[name][:5] == "refs/":
347 head = ud.unresolvedrev[name]
348 tag = ud.unresolvedrev[name]
349 else:
350 head = "refs/heads/%s" % ud.unresolvedrev[name]
351 tag = "refs/tags/%s" % ud.unresolvedrev[name]
352 for s in [head, tag + "^{}", tag]:
353 for l in output.split('\n'):
354 if s in l:
355 return l.split()[0]
356 raise bb.fetch2.FetchError("Unable to resolve '%s' in upstream git repository in git ls-remote output for %s" % \
357 (ud.unresolvedrev[name], ud.host+ud.path))
358
359 def latest_versionstring(self, ud, d):
360 """
361 Compute the latest release name like "x.y.x" in "x.y.x+gitHASH"
362 by searching through the tags output of ls-remote, comparing
363 versions and returning the highest match.
364 """
365 pupver = ('', '')
366
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500367 tagregex = re.compile(d.getVar('UPSTREAM_CHECK_GITTAGREGEX', True) or "(?P<pver>([0-9][\.|_]?)+)")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500368 try:
369 output = self._lsremote(ud, d, "refs/tags/*")
370 except bb.fetch2.FetchError or bb.fetch2.NetworkAccess:
371 return pupver
372
373 verstring = ""
374 revision = ""
375 for line in output.split("\n"):
376 if not line:
377 break
378
379 tag_head = line.split("/")[-1]
380 # Ignore non-released branches
381 m = re.search("(alpha|beta|rc|final)+", tag_head)
382 if m:
383 continue
384
385 # search for version in the line
386 tag = tagregex.search(tag_head)
387 if tag == None:
388 continue
389
390 tag = tag.group('pver')
391 tag = tag.replace("_", ".")
392
393 if verstring and bb.utils.vercmp(("0", tag, ""), ("0", verstring, "")) < 0:
394 continue
395
396 verstring = tag
397 revision = line.split()[0]
398 pupver = (verstring, revision)
399
400 return pupver
401
402 def _build_revision(self, ud, d, name):
403 return ud.revisions[name]
404
405 def gitpkgv_revision(self, ud, d, name):
406 """
407 Return a sortable revision number by counting commits in the history
408 Based on gitpkgv.bblass in meta-openembedded
409 """
410 rev = self._build_revision(ud, d, name)
411 localpath = ud.localpath
412 rev_file = os.path.join(localpath, "oe-gitpkgv_" + rev)
413 if not os.path.exists(localpath):
414 commits = None
415 else:
416 if not os.path.exists(rev_file) or not os.path.getsize(rev_file):
417 from pipes import quote
418 commits = bb.fetch2.runfetchcmd(
419 "git rev-list %s -- | wc -l" % (quote(rev)),
420 d, quiet=True).strip().lstrip('0')
421 if commits:
422 open(rev_file, "w").write("%d\n" % int(commits))
423 else:
424 commits = open(rev_file, "r").readline(128).strip()
425 if commits:
426 return False, "%s+%s" % (commits, rev[:7])
427 else:
428 return True, str(rev)
429
430 def checkstatus(self, fetch, ud, d):
431 try:
432 self._lsremote(ud, d, "")
433 return True
434 except FetchError:
435 return False