1*4882a593Smuzhiyun""" 2*4882a593SmuzhiyunBitBake 'Fetch' git implementation 3*4882a593Smuzhiyun 4*4882a593Smuzhiyungit fetcher support the SRC_URI with format of: 5*4882a593SmuzhiyunSRC_URI = "git://some.host/somepath;OptionA=xxx;OptionB=xxx;..." 6*4882a593Smuzhiyun 7*4882a593SmuzhiyunSupported SRC_URI options are: 8*4882a593Smuzhiyun 9*4882a593Smuzhiyun- branch 10*4882a593Smuzhiyun The git branch to retrieve from. The default is "master" 11*4882a593Smuzhiyun 12*4882a593Smuzhiyun This option also supports multiple branch fetching, with branches 13*4882a593Smuzhiyun separated by commas. In multiple branches case, the name option 14*4882a593Smuzhiyun must have the same number of names to match the branches, which is 15*4882a593Smuzhiyun used to specify the SRC_REV for the branch 16*4882a593Smuzhiyun e.g: 17*4882a593Smuzhiyun SRC_URI="git://some.host/somepath;branch=branchX,branchY;name=nameX,nameY" 18*4882a593Smuzhiyun SRCREV_nameX = "xxxxxxxxxxxxxxxxxxxx" 19*4882a593Smuzhiyun SRCREV_nameY = "YYYYYYYYYYYYYYYYYYYY" 20*4882a593Smuzhiyun 21*4882a593Smuzhiyun- tag 22*4882a593Smuzhiyun The git tag to retrieve. The default is "master" 23*4882a593Smuzhiyun 24*4882a593Smuzhiyun- protocol 25*4882a593Smuzhiyun The method to use to access the repository. Common options are "git", 26*4882a593Smuzhiyun "http", "https", "file", "ssh" and "rsync". The default is "git". 27*4882a593Smuzhiyun 28*4882a593Smuzhiyun- rebaseable 29*4882a593Smuzhiyun rebaseable indicates that the upstream git repo may rebase in the future, 30*4882a593Smuzhiyun and current revision may disappear from upstream repo. This option will 31*4882a593Smuzhiyun remind fetcher to preserve local cache carefully for future use. 32*4882a593Smuzhiyun The default value is "0", set rebaseable=1 for rebaseable git repo. 33*4882a593Smuzhiyun 34*4882a593Smuzhiyun- nocheckout 35*4882a593Smuzhiyun Don't checkout source code when unpacking. set this option for the recipe 36*4882a593Smuzhiyun who has its own routine to checkout code. 37*4882a593Smuzhiyun The default is "0", set nocheckout=1 if needed. 38*4882a593Smuzhiyun 39*4882a593Smuzhiyun- bareclone 40*4882a593Smuzhiyun Create a bare clone of the source code and don't checkout the source code 41*4882a593Smuzhiyun when unpacking. Set this option for the recipe who has its own routine to 42*4882a593Smuzhiyun checkout code and tracking branch requirements. 43*4882a593Smuzhiyun The default is "0", set bareclone=1 if needed. 44*4882a593Smuzhiyun 45*4882a593Smuzhiyun- nobranch 46*4882a593Smuzhiyun Don't check the SHA validation for branch. set this option for the recipe 47*4882a593Smuzhiyun referring to commit which is valid in any namespace (branch, tag, ...) 48*4882a593Smuzhiyun instead of branch. 49*4882a593Smuzhiyun The default is "0", set nobranch=1 if needed. 50*4882a593Smuzhiyun 51*4882a593Smuzhiyun- usehead 52*4882a593Smuzhiyun For local git:// urls to use the current branch HEAD as the revision for use with 53*4882a593Smuzhiyun AUTOREV. Implies nobranch. 54*4882a593Smuzhiyun 55*4882a593Smuzhiyun""" 56*4882a593Smuzhiyun 57*4882a593Smuzhiyun# Copyright (C) 2005 Richard Purdie 58*4882a593Smuzhiyun# 59*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 60*4882a593Smuzhiyun# 61*4882a593Smuzhiyun 62*4882a593Smuzhiyunimport collections 63*4882a593Smuzhiyunimport errno 64*4882a593Smuzhiyunimport fnmatch 65*4882a593Smuzhiyunimport os 66*4882a593Smuzhiyunimport re 67*4882a593Smuzhiyunimport shlex 68*4882a593Smuzhiyunimport subprocess 69*4882a593Smuzhiyunimport tempfile 70*4882a593Smuzhiyunimport bb 71*4882a593Smuzhiyunimport bb.progress 72*4882a593Smuzhiyunfrom contextlib import contextmanager 73*4882a593Smuzhiyunfrom bb.fetch2 import FetchMethod 74*4882a593Smuzhiyunfrom bb.fetch2 import runfetchcmd 75*4882a593Smuzhiyunfrom bb.fetch2 import logger 76*4882a593Smuzhiyun 77*4882a593Smuzhiyun 78*4882a593Smuzhiyunclass GitProgressHandler(bb.progress.LineFilterProgressHandler): 79*4882a593Smuzhiyun """Extract progress information from git output""" 80*4882a593Smuzhiyun def __init__(self, d): 81*4882a593Smuzhiyun self._buffer = '' 82*4882a593Smuzhiyun self._count = 0 83*4882a593Smuzhiyun super(GitProgressHandler, self).__init__(d) 84*4882a593Smuzhiyun # Send an initial progress event so the bar gets shown 85*4882a593Smuzhiyun self._fire_progress(-1) 86*4882a593Smuzhiyun 87*4882a593Smuzhiyun def write(self, string): 88*4882a593Smuzhiyun self._buffer += string 89*4882a593Smuzhiyun stages = ['Counting objects', 'Compressing objects', 'Receiving objects', 'Resolving deltas'] 90*4882a593Smuzhiyun stage_weights = [0.2, 0.05, 0.5, 0.25] 91*4882a593Smuzhiyun stagenum = 0 92*4882a593Smuzhiyun for i, stage in reversed(list(enumerate(stages))): 93*4882a593Smuzhiyun if stage in self._buffer: 94*4882a593Smuzhiyun stagenum = i 95*4882a593Smuzhiyun self._buffer = '' 96*4882a593Smuzhiyun break 97*4882a593Smuzhiyun self._status = stages[stagenum] 98*4882a593Smuzhiyun percs = re.findall(r'(\d+)%', string) 99*4882a593Smuzhiyun if percs: 100*4882a593Smuzhiyun progress = int(round((int(percs[-1]) * stage_weights[stagenum]) + (sum(stage_weights[:stagenum]) * 100))) 101*4882a593Smuzhiyun rates = re.findall(r'([\d.]+ [a-zA-Z]*/s+)', string) 102*4882a593Smuzhiyun if rates: 103*4882a593Smuzhiyun rate = rates[-1] 104*4882a593Smuzhiyun else: 105*4882a593Smuzhiyun rate = None 106*4882a593Smuzhiyun self.update(progress, rate) 107*4882a593Smuzhiyun else: 108*4882a593Smuzhiyun if stagenum == 0: 109*4882a593Smuzhiyun percs = re.findall(r': (\d+)', string) 110*4882a593Smuzhiyun if percs: 111*4882a593Smuzhiyun count = int(percs[-1]) 112*4882a593Smuzhiyun if count > self._count: 113*4882a593Smuzhiyun self._count = count 114*4882a593Smuzhiyun self._fire_progress(-count) 115*4882a593Smuzhiyun super(GitProgressHandler, self).write(string) 116*4882a593Smuzhiyun 117*4882a593Smuzhiyun 118*4882a593Smuzhiyunclass Git(FetchMethod): 119*4882a593Smuzhiyun bitbake_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.join(os.path.abspath(__file__))), '..', '..', '..')) 120*4882a593Smuzhiyun make_shallow_path = os.path.join(bitbake_dir, 'bin', 'git-make-shallow') 121*4882a593Smuzhiyun 122*4882a593Smuzhiyun """Class to fetch a module or modules from git repositories""" 123*4882a593Smuzhiyun def init(self, d): 124*4882a593Smuzhiyun pass 125*4882a593Smuzhiyun 126*4882a593Smuzhiyun def supports(self, ud, d): 127*4882a593Smuzhiyun """ 128*4882a593Smuzhiyun Check to see if a given url can be fetched with git. 129*4882a593Smuzhiyun """ 130*4882a593Smuzhiyun return ud.type in ['git'] 131*4882a593Smuzhiyun 132*4882a593Smuzhiyun def supports_checksum(self, urldata): 133*4882a593Smuzhiyun return False 134*4882a593Smuzhiyun 135*4882a593Smuzhiyun def urldata_init(self, ud, d): 136*4882a593Smuzhiyun """ 137*4882a593Smuzhiyun init git specific variable within url data 138*4882a593Smuzhiyun so that the git method like latest_revision() can work 139*4882a593Smuzhiyun """ 140*4882a593Smuzhiyun if 'protocol' in ud.parm: 141*4882a593Smuzhiyun ud.proto = ud.parm['protocol'] 142*4882a593Smuzhiyun elif not ud.host: 143*4882a593Smuzhiyun ud.proto = 'file' 144*4882a593Smuzhiyun else: 145*4882a593Smuzhiyun ud.proto = "git" 146*4882a593Smuzhiyun if ud.host == "github.com" and ud.proto == "git": 147*4882a593Smuzhiyun # github stopped supporting git protocol 148*4882a593Smuzhiyun # https://github.blog/2021-09-01-improving-git-protocol-security-github/#no-more-unauthenticated-git 149*4882a593Smuzhiyun ud.proto = "https" 150*4882a593Smuzhiyun bb.warn("URL: %s uses git protocol which is no longer supported by github. Please change to ;protocol=https in the url." % ud.url) 151*4882a593Smuzhiyun 152*4882a593Smuzhiyun if not ud.proto in ('git', 'file', 'ssh', 'http', 'https', 'rsync'): 153*4882a593Smuzhiyun raise bb.fetch2.ParameterError("Invalid protocol type", ud.url) 154*4882a593Smuzhiyun 155*4882a593Smuzhiyun ud.nocheckout = ud.parm.get("nocheckout","0") == "1" 156*4882a593Smuzhiyun 157*4882a593Smuzhiyun ud.rebaseable = ud.parm.get("rebaseable","0") == "1" 158*4882a593Smuzhiyun 159*4882a593Smuzhiyun ud.nobranch = ud.parm.get("nobranch","0") == "1" 160*4882a593Smuzhiyun 161*4882a593Smuzhiyun # usehead implies nobranch 162*4882a593Smuzhiyun ud.usehead = ud.parm.get("usehead","0") == "1" 163*4882a593Smuzhiyun if ud.usehead: 164*4882a593Smuzhiyun if ud.proto != "file": 165*4882a593Smuzhiyun raise bb.fetch2.ParameterError("The usehead option is only for use with local ('protocol=file') git repositories", ud.url) 166*4882a593Smuzhiyun ud.nobranch = 1 167*4882a593Smuzhiyun 168*4882a593Smuzhiyun # bareclone implies nocheckout 169*4882a593Smuzhiyun ud.bareclone = ud.parm.get("bareclone","0") == "1" 170*4882a593Smuzhiyun if ud.bareclone: 171*4882a593Smuzhiyun ud.nocheckout = 1 172*4882a593Smuzhiyun 173*4882a593Smuzhiyun ud.unresolvedrev = {} 174*4882a593Smuzhiyun branches = ud.parm.get("branch", "").split(',') 175*4882a593Smuzhiyun if branches == [""] and not ud.nobranch: 176*4882a593Smuzhiyun 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) 177*4882a593Smuzhiyun branches = ["master"] 178*4882a593Smuzhiyun if len(branches) != len(ud.names): 179*4882a593Smuzhiyun raise bb.fetch2.ParameterError("The number of name and branch parameters is not balanced", ud.url) 180*4882a593Smuzhiyun 181*4882a593Smuzhiyun ud.noshared = d.getVar("BB_GIT_NOSHARED") == "1" 182*4882a593Smuzhiyun 183*4882a593Smuzhiyun ud.cloneflags = "-n" 184*4882a593Smuzhiyun if not ud.noshared: 185*4882a593Smuzhiyun ud.cloneflags += " -s" 186*4882a593Smuzhiyun if ud.bareclone: 187*4882a593Smuzhiyun ud.cloneflags += " --mirror" 188*4882a593Smuzhiyun 189*4882a593Smuzhiyun ud.shallow = d.getVar("BB_GIT_SHALLOW") == "1" 190*4882a593Smuzhiyun ud.shallow_extra_refs = (d.getVar("BB_GIT_SHALLOW_EXTRA_REFS") or "").split() 191*4882a593Smuzhiyun 192*4882a593Smuzhiyun depth_default = d.getVar("BB_GIT_SHALLOW_DEPTH") 193*4882a593Smuzhiyun if depth_default is not None: 194*4882a593Smuzhiyun try: 195*4882a593Smuzhiyun depth_default = int(depth_default or 0) 196*4882a593Smuzhiyun except ValueError: 197*4882a593Smuzhiyun raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH: %s" % depth_default) 198*4882a593Smuzhiyun else: 199*4882a593Smuzhiyun if depth_default < 0: 200*4882a593Smuzhiyun raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH: %s" % depth_default) 201*4882a593Smuzhiyun else: 202*4882a593Smuzhiyun depth_default = 1 203*4882a593Smuzhiyun ud.shallow_depths = collections.defaultdict(lambda: depth_default) 204*4882a593Smuzhiyun 205*4882a593Smuzhiyun revs_default = d.getVar("BB_GIT_SHALLOW_REVS") 206*4882a593Smuzhiyun ud.shallow_revs = [] 207*4882a593Smuzhiyun ud.branches = {} 208*4882a593Smuzhiyun for pos, name in enumerate(ud.names): 209*4882a593Smuzhiyun branch = branches[pos] 210*4882a593Smuzhiyun ud.branches[name] = branch 211*4882a593Smuzhiyun ud.unresolvedrev[name] = branch 212*4882a593Smuzhiyun 213*4882a593Smuzhiyun shallow_depth = d.getVar("BB_GIT_SHALLOW_DEPTH_%s" % name) 214*4882a593Smuzhiyun if shallow_depth is not None: 215*4882a593Smuzhiyun try: 216*4882a593Smuzhiyun shallow_depth = int(shallow_depth or 0) 217*4882a593Smuzhiyun except ValueError: 218*4882a593Smuzhiyun raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth)) 219*4882a593Smuzhiyun else: 220*4882a593Smuzhiyun if shallow_depth < 0: 221*4882a593Smuzhiyun raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth)) 222*4882a593Smuzhiyun ud.shallow_depths[name] = shallow_depth 223*4882a593Smuzhiyun 224*4882a593Smuzhiyun revs = d.getVar("BB_GIT_SHALLOW_REVS_%s" % name) 225*4882a593Smuzhiyun if revs is not None: 226*4882a593Smuzhiyun ud.shallow_revs.extend(revs.split()) 227*4882a593Smuzhiyun elif revs_default is not None: 228*4882a593Smuzhiyun ud.shallow_revs.extend(revs_default.split()) 229*4882a593Smuzhiyun 230*4882a593Smuzhiyun if (ud.shallow and 231*4882a593Smuzhiyun not ud.shallow_revs and 232*4882a593Smuzhiyun all(ud.shallow_depths[n] == 0 for n in ud.names)): 233*4882a593Smuzhiyun # Shallow disabled for this URL 234*4882a593Smuzhiyun ud.shallow = False 235*4882a593Smuzhiyun 236*4882a593Smuzhiyun if ud.usehead: 237*4882a593Smuzhiyun # When usehead is set let's associate 'HEAD' with the unresolved 238*4882a593Smuzhiyun # rev of this repository. This will get resolved into a revision 239*4882a593Smuzhiyun # later. If an actual revision happens to have also been provided 240*4882a593Smuzhiyun # then this setting will be overridden. 241*4882a593Smuzhiyun for name in ud.names: 242*4882a593Smuzhiyun ud.unresolvedrev[name] = 'HEAD' 243*4882a593Smuzhiyun 244*4882a593Smuzhiyun ud.basecmd = d.getVar("FETCHCMD_git") or "git -c core.fsyncobjectfiles=0 -c gc.autoDetach=false -c core.pager=cat" 245*4882a593Smuzhiyun 246*4882a593Smuzhiyun write_tarballs = d.getVar("BB_GENERATE_MIRROR_TARBALLS") or "0" 247*4882a593Smuzhiyun ud.write_tarballs = write_tarballs != "0" or ud.rebaseable 248*4882a593Smuzhiyun ud.write_shallow_tarballs = (d.getVar("BB_GENERATE_SHALLOW_TARBALLS") or write_tarballs) != "0" 249*4882a593Smuzhiyun 250*4882a593Smuzhiyun ud.setup_revisions(d) 251*4882a593Smuzhiyun 252*4882a593Smuzhiyun for name in ud.names: 253*4882a593Smuzhiyun # Ensure anything that doesn't look like a sha256 checksum/revision is translated into one 254*4882a593Smuzhiyun if not ud.revisions[name] or len(ud.revisions[name]) != 40 or (False in [c in "abcdef0123456789" for c in ud.revisions[name]]): 255*4882a593Smuzhiyun if ud.revisions[name]: 256*4882a593Smuzhiyun ud.unresolvedrev[name] = ud.revisions[name] 257*4882a593Smuzhiyun ud.revisions[name] = self.latest_revision(ud, d, name) 258*4882a593Smuzhiyun 259*4882a593Smuzhiyun gitsrcname = '%s%s' % (ud.host.replace(':', '.'), ud.path.replace('/', '.').replace('*', '.').replace(' ','_')) 260*4882a593Smuzhiyun if gitsrcname.startswith('.'): 261*4882a593Smuzhiyun gitsrcname = gitsrcname[1:] 262*4882a593Smuzhiyun 263*4882a593Smuzhiyun # for rebaseable git repo, it is necessary to keep mirror tar ball 264*4882a593Smuzhiyun # per revision, so that even the revision disappears from the 265*4882a593Smuzhiyun # upstream repo in the future, the mirror will remain intact and still 266*4882a593Smuzhiyun # contains the revision 267*4882a593Smuzhiyun if ud.rebaseable: 268*4882a593Smuzhiyun for name in ud.names: 269*4882a593Smuzhiyun gitsrcname = gitsrcname + '_' + ud.revisions[name] 270*4882a593Smuzhiyun 271*4882a593Smuzhiyun dl_dir = d.getVar("DL_DIR") 272*4882a593Smuzhiyun gitdir = d.getVar("GITDIR") or (dl_dir + "/git2") 273*4882a593Smuzhiyun ud.clonedir = os.path.join(gitdir, gitsrcname) 274*4882a593Smuzhiyun ud.localfile = ud.clonedir 275*4882a593Smuzhiyun 276*4882a593Smuzhiyun mirrortarball = 'git2_%s.tar.gz' % gitsrcname 277*4882a593Smuzhiyun ud.fullmirror = os.path.join(dl_dir, mirrortarball) 278*4882a593Smuzhiyun ud.mirrortarballs = [mirrortarball] 279*4882a593Smuzhiyun if ud.shallow: 280*4882a593Smuzhiyun tarballname = gitsrcname 281*4882a593Smuzhiyun if ud.bareclone: 282*4882a593Smuzhiyun tarballname = "%s_bare" % tarballname 283*4882a593Smuzhiyun 284*4882a593Smuzhiyun if ud.shallow_revs: 285*4882a593Smuzhiyun tarballname = "%s_%s" % (tarballname, "_".join(sorted(ud.shallow_revs))) 286*4882a593Smuzhiyun 287*4882a593Smuzhiyun for name, revision in sorted(ud.revisions.items()): 288*4882a593Smuzhiyun tarballname = "%s_%s" % (tarballname, ud.revisions[name][:7]) 289*4882a593Smuzhiyun depth = ud.shallow_depths[name] 290*4882a593Smuzhiyun if depth: 291*4882a593Smuzhiyun tarballname = "%s-%s" % (tarballname, depth) 292*4882a593Smuzhiyun 293*4882a593Smuzhiyun shallow_refs = [] 294*4882a593Smuzhiyun if not ud.nobranch: 295*4882a593Smuzhiyun shallow_refs.extend(ud.branches.values()) 296*4882a593Smuzhiyun if ud.shallow_extra_refs: 297*4882a593Smuzhiyun shallow_refs.extend(r.replace('refs/heads/', '').replace('*', 'ALL') for r in ud.shallow_extra_refs) 298*4882a593Smuzhiyun if shallow_refs: 299*4882a593Smuzhiyun tarballname = "%s_%s" % (tarballname, "_".join(sorted(shallow_refs)).replace('/', '.')) 300*4882a593Smuzhiyun 301*4882a593Smuzhiyun fetcher = self.__class__.__name__.lower() 302*4882a593Smuzhiyun ud.shallowtarball = '%sshallow_%s.tar.gz' % (fetcher, tarballname) 303*4882a593Smuzhiyun ud.fullshallow = os.path.join(dl_dir, ud.shallowtarball) 304*4882a593Smuzhiyun ud.mirrortarballs.insert(0, ud.shallowtarball) 305*4882a593Smuzhiyun 306*4882a593Smuzhiyun def localpath(self, ud, d): 307*4882a593Smuzhiyun return ud.clonedir 308*4882a593Smuzhiyun 309*4882a593Smuzhiyun def need_update(self, ud, d): 310*4882a593Smuzhiyun return self.clonedir_need_update(ud, d) or self.shallow_tarball_need_update(ud) or self.tarball_need_update(ud) 311*4882a593Smuzhiyun 312*4882a593Smuzhiyun def clonedir_need_update(self, ud, d): 313*4882a593Smuzhiyun if not os.path.exists(ud.clonedir): 314*4882a593Smuzhiyun return True 315*4882a593Smuzhiyun if ud.shallow and ud.write_shallow_tarballs and self.clonedir_need_shallow_revs(ud, d): 316*4882a593Smuzhiyun return True 317*4882a593Smuzhiyun for name in ud.names: 318*4882a593Smuzhiyun if not self._contains_ref(ud, d, name, ud.clonedir): 319*4882a593Smuzhiyun return True 320*4882a593Smuzhiyun return False 321*4882a593Smuzhiyun 322*4882a593Smuzhiyun def clonedir_need_shallow_revs(self, ud, d): 323*4882a593Smuzhiyun for rev in ud.shallow_revs: 324*4882a593Smuzhiyun try: 325*4882a593Smuzhiyun runfetchcmd('%s rev-parse -q --verify %s' % (ud.basecmd, rev), d, quiet=True, workdir=ud.clonedir) 326*4882a593Smuzhiyun except bb.fetch2.FetchError: 327*4882a593Smuzhiyun return rev 328*4882a593Smuzhiyun return None 329*4882a593Smuzhiyun 330*4882a593Smuzhiyun def shallow_tarball_need_update(self, ud): 331*4882a593Smuzhiyun return ud.shallow and ud.write_shallow_tarballs and not os.path.exists(ud.fullshallow) 332*4882a593Smuzhiyun 333*4882a593Smuzhiyun def tarball_need_update(self, ud): 334*4882a593Smuzhiyun return ud.write_tarballs and not os.path.exists(ud.fullmirror) 335*4882a593Smuzhiyun 336*4882a593Smuzhiyun def try_premirror(self, ud, d): 337*4882a593Smuzhiyun # If we don't do this, updating an existing checkout with only premirrors 338*4882a593Smuzhiyun # is not possible 339*4882a593Smuzhiyun if bb.utils.to_boolean(d.getVar("BB_FETCH_PREMIRRORONLY")): 340*4882a593Smuzhiyun return True 341*4882a593Smuzhiyun if os.path.exists(ud.clonedir): 342*4882a593Smuzhiyun return False 343*4882a593Smuzhiyun return True 344*4882a593Smuzhiyun 345*4882a593Smuzhiyun def download(self, ud, d): 346*4882a593Smuzhiyun """Fetch url""" 347*4882a593Smuzhiyun 348*4882a593Smuzhiyun # A current clone is preferred to either tarball, a shallow tarball is 349*4882a593Smuzhiyun # preferred to an out of date clone, and a missing clone will use 350*4882a593Smuzhiyun # either tarball. 351*4882a593Smuzhiyun if ud.shallow and os.path.exists(ud.fullshallow) and self.need_update(ud, d): 352*4882a593Smuzhiyun ud.localpath = ud.fullshallow 353*4882a593Smuzhiyun return 354*4882a593Smuzhiyun elif os.path.exists(ud.fullmirror) and not os.path.exists(ud.clonedir): 355*4882a593Smuzhiyun bb.utils.mkdirhier(ud.clonedir) 356*4882a593Smuzhiyun runfetchcmd("tar -xzf %s" % ud.fullmirror, d, workdir=ud.clonedir) 357*4882a593Smuzhiyun 358*4882a593Smuzhiyun repourl = self._get_repo_url(ud) 359*4882a593Smuzhiyun 360*4882a593Smuzhiyun # If the repo still doesn't exist, fallback to cloning it 361*4882a593Smuzhiyun if not os.path.exists(ud.clonedir): 362*4882a593Smuzhiyun # We do this since git will use a "-l" option automatically for local urls where possible, 363*4882a593Smuzhiyun # but it doesn't work when git/objects is a symlink, only works when it is a directory. 364*4882a593Smuzhiyun if repourl.startswith("file://"): 365*4882a593Smuzhiyun repourl_path = repourl[7:] 366*4882a593Smuzhiyun objects = os.path.join(repourl_path, 'objects') 367*4882a593Smuzhiyun if os.path.isdir(objects) and not os.path.islink(objects): 368*4882a593Smuzhiyun repourl = repourl_path 369*4882a593Smuzhiyun clone_cmd = "LANG=C %s clone --bare --mirror %s %s --progress" % (ud.basecmd, shlex.quote(repourl), ud.clonedir) 370*4882a593Smuzhiyun if ud.proto.lower() != 'file': 371*4882a593Smuzhiyun bb.fetch2.check_network_access(d, clone_cmd, ud.url) 372*4882a593Smuzhiyun progresshandler = GitProgressHandler(d) 373*4882a593Smuzhiyun runfetchcmd(clone_cmd, d, log=progresshandler) 374*4882a593Smuzhiyun 375*4882a593Smuzhiyun # Update the checkout if needed 376*4882a593Smuzhiyun if self.clonedir_need_update(ud, d): 377*4882a593Smuzhiyun output = runfetchcmd("%s remote" % ud.basecmd, d, quiet=True, workdir=ud.clonedir) 378*4882a593Smuzhiyun if "origin" in output: 379*4882a593Smuzhiyun runfetchcmd("%s remote rm origin" % ud.basecmd, d, workdir=ud.clonedir) 380*4882a593Smuzhiyun 381*4882a593Smuzhiyun runfetchcmd("%s remote add --mirror=fetch origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=ud.clonedir) 382*4882a593Smuzhiyun 383*4882a593Smuzhiyun if ud.nobranch: 384*4882a593Smuzhiyun fetch_cmd = "LANG=C %s fetch -f --progress %s refs/*:refs/*" % (ud.basecmd, shlex.quote(repourl)) 385*4882a593Smuzhiyun else: 386*4882a593Smuzhiyun fetch_cmd = "LANG=C %s fetch -f --progress %s refs/heads/*:refs/heads/* refs/tags/*:refs/tags/*" % (ud.basecmd, shlex.quote(repourl)) 387*4882a593Smuzhiyun if ud.proto.lower() != 'file': 388*4882a593Smuzhiyun bb.fetch2.check_network_access(d, fetch_cmd, ud.url) 389*4882a593Smuzhiyun progresshandler = GitProgressHandler(d) 390*4882a593Smuzhiyun runfetchcmd(fetch_cmd, d, log=progresshandler, workdir=ud.clonedir) 391*4882a593Smuzhiyun runfetchcmd("%s prune-packed" % ud.basecmd, d, workdir=ud.clonedir) 392*4882a593Smuzhiyun runfetchcmd("%s pack-refs --all" % ud.basecmd, d, workdir=ud.clonedir) 393*4882a593Smuzhiyun runfetchcmd("%s pack-redundant --all | xargs -r rm" % ud.basecmd, d, workdir=ud.clonedir) 394*4882a593Smuzhiyun try: 395*4882a593Smuzhiyun os.unlink(ud.fullmirror) 396*4882a593Smuzhiyun except OSError as exc: 397*4882a593Smuzhiyun if exc.errno != errno.ENOENT: 398*4882a593Smuzhiyun raise 399*4882a593Smuzhiyun 400*4882a593Smuzhiyun for name in ud.names: 401*4882a593Smuzhiyun if not self._contains_ref(ud, d, name, ud.clonedir): 402*4882a593Smuzhiyun raise bb.fetch2.FetchError("Unable to find revision %s in branch %s even from upstream" % (ud.revisions[name], ud.branches[name])) 403*4882a593Smuzhiyun 404*4882a593Smuzhiyun if ud.shallow and ud.write_shallow_tarballs: 405*4882a593Smuzhiyun missing_rev = self.clonedir_need_shallow_revs(ud, d) 406*4882a593Smuzhiyun if missing_rev: 407*4882a593Smuzhiyun raise bb.fetch2.FetchError("Unable to find revision %s even from upstream" % missing_rev) 408*4882a593Smuzhiyun 409*4882a593Smuzhiyun if self._contains_lfs(ud, d, ud.clonedir) and self._need_lfs(ud): 410*4882a593Smuzhiyun # Unpack temporary working copy, use it to run 'git checkout' to force pre-fetching 411*4882a593Smuzhiyun # of all LFS blobs needed at the srcrev. 412*4882a593Smuzhiyun # 413*4882a593Smuzhiyun # It would be nice to just do this inline here by running 'git-lfs fetch' 414*4882a593Smuzhiyun # on the bare clonedir, but that operation requires a working copy on some 415*4882a593Smuzhiyun # releases of Git LFS. 416*4882a593Smuzhiyun tmpdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR')) 417*4882a593Smuzhiyun try: 418*4882a593Smuzhiyun # Do the checkout. This implicitly involves a Git LFS fetch. 419*4882a593Smuzhiyun Git.unpack(self, ud, tmpdir, d) 420*4882a593Smuzhiyun 421*4882a593Smuzhiyun # Scoop up a copy of any stuff that Git LFS downloaded. Merge them into 422*4882a593Smuzhiyun # the bare clonedir. 423*4882a593Smuzhiyun # 424*4882a593Smuzhiyun # As this procedure is invoked repeatedly on incremental fetches as 425*4882a593Smuzhiyun # a recipe's SRCREV is bumped throughout its lifetime, this will 426*4882a593Smuzhiyun # result in a gradual accumulation of LFS blobs in <ud.clonedir>/lfs 427*4882a593Smuzhiyun # corresponding to all the blobs reachable from the different revs 428*4882a593Smuzhiyun # fetched across time. 429*4882a593Smuzhiyun # 430*4882a593Smuzhiyun # Only do this if the unpack resulted in a .git/lfs directory being 431*4882a593Smuzhiyun # created; this only happens if at least one blob needed to be 432*4882a593Smuzhiyun # downloaded. 433*4882a593Smuzhiyun if os.path.exists(os.path.join(tmpdir, "git", ".git", "lfs")): 434*4882a593Smuzhiyun runfetchcmd("tar -cf - lfs | tar -xf - -C %s" % ud.clonedir, d, workdir="%s/git/.git" % tmpdir) 435*4882a593Smuzhiyun finally: 436*4882a593Smuzhiyun bb.utils.remove(tmpdir, recurse=True) 437*4882a593Smuzhiyun 438*4882a593Smuzhiyun def build_mirror_data(self, ud, d): 439*4882a593Smuzhiyun 440*4882a593Smuzhiyun # Create as a temp file and move atomically into position to avoid races 441*4882a593Smuzhiyun @contextmanager 442*4882a593Smuzhiyun def create_atomic(filename): 443*4882a593Smuzhiyun fd, tfile = tempfile.mkstemp(dir=os.path.dirname(filename)) 444*4882a593Smuzhiyun try: 445*4882a593Smuzhiyun yield tfile 446*4882a593Smuzhiyun umask = os.umask(0o666) 447*4882a593Smuzhiyun os.umask(umask) 448*4882a593Smuzhiyun os.chmod(tfile, (0o666 & ~umask)) 449*4882a593Smuzhiyun os.rename(tfile, filename) 450*4882a593Smuzhiyun finally: 451*4882a593Smuzhiyun os.close(fd) 452*4882a593Smuzhiyun 453*4882a593Smuzhiyun if ud.shallow and ud.write_shallow_tarballs: 454*4882a593Smuzhiyun if not os.path.exists(ud.fullshallow): 455*4882a593Smuzhiyun if os.path.islink(ud.fullshallow): 456*4882a593Smuzhiyun os.unlink(ud.fullshallow) 457*4882a593Smuzhiyun tempdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR')) 458*4882a593Smuzhiyun shallowclone = os.path.join(tempdir, 'git') 459*4882a593Smuzhiyun try: 460*4882a593Smuzhiyun self.clone_shallow_local(ud, shallowclone, d) 461*4882a593Smuzhiyun 462*4882a593Smuzhiyun logger.info("Creating tarball of git repository") 463*4882a593Smuzhiyun with create_atomic(ud.fullshallow) as tfile: 464*4882a593Smuzhiyun runfetchcmd("tar -czf %s ." % tfile, d, workdir=shallowclone) 465*4882a593Smuzhiyun runfetchcmd("touch %s.done" % ud.fullshallow, d) 466*4882a593Smuzhiyun finally: 467*4882a593Smuzhiyun bb.utils.remove(tempdir, recurse=True) 468*4882a593Smuzhiyun elif ud.write_tarballs and not os.path.exists(ud.fullmirror): 469*4882a593Smuzhiyun if os.path.islink(ud.fullmirror): 470*4882a593Smuzhiyun os.unlink(ud.fullmirror) 471*4882a593Smuzhiyun 472*4882a593Smuzhiyun logger.info("Creating tarball of git repository") 473*4882a593Smuzhiyun with create_atomic(ud.fullmirror) as tfile: 474*4882a593Smuzhiyun mtime = runfetchcmd("git log --all -1 --format=%cD", d, 475*4882a593Smuzhiyun quiet=True, workdir=ud.clonedir) 476*4882a593Smuzhiyun runfetchcmd("tar -czf %s --owner oe:0 --group oe:0 --mtime \"%s\" ." 477*4882a593Smuzhiyun % (tfile, mtime), d, workdir=ud.clonedir) 478*4882a593Smuzhiyun runfetchcmd("touch %s.done" % ud.fullmirror, d) 479*4882a593Smuzhiyun 480*4882a593Smuzhiyun def clone_shallow_local(self, ud, dest, d): 481*4882a593Smuzhiyun """Clone the repo and make it shallow. 482*4882a593Smuzhiyun 483*4882a593Smuzhiyun The upstream url of the new clone isn't set at this time, as it'll be 484*4882a593Smuzhiyun set correctly when unpacked.""" 485*4882a593Smuzhiyun runfetchcmd("%s clone %s %s %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, dest), d) 486*4882a593Smuzhiyun 487*4882a593Smuzhiyun to_parse, shallow_branches = [], [] 488*4882a593Smuzhiyun for name in ud.names: 489*4882a593Smuzhiyun revision = ud.revisions[name] 490*4882a593Smuzhiyun depth = ud.shallow_depths[name] 491*4882a593Smuzhiyun if depth: 492*4882a593Smuzhiyun to_parse.append('%s~%d^{}' % (revision, depth - 1)) 493*4882a593Smuzhiyun 494*4882a593Smuzhiyun # For nobranch, we need a ref, otherwise the commits will be 495*4882a593Smuzhiyun # removed, and for non-nobranch, we truncate the branch to our 496*4882a593Smuzhiyun # srcrev, to avoid keeping unnecessary history beyond that. 497*4882a593Smuzhiyun branch = ud.branches[name] 498*4882a593Smuzhiyun if ud.nobranch: 499*4882a593Smuzhiyun ref = "refs/shallow/%s" % name 500*4882a593Smuzhiyun elif ud.bareclone: 501*4882a593Smuzhiyun ref = "refs/heads/%s" % branch 502*4882a593Smuzhiyun else: 503*4882a593Smuzhiyun ref = "refs/remotes/origin/%s" % branch 504*4882a593Smuzhiyun 505*4882a593Smuzhiyun shallow_branches.append(ref) 506*4882a593Smuzhiyun runfetchcmd("%s update-ref %s %s" % (ud.basecmd, ref, revision), d, workdir=dest) 507*4882a593Smuzhiyun 508*4882a593Smuzhiyun # Map srcrev+depths to revisions 509*4882a593Smuzhiyun parsed_depths = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join(to_parse)), d, workdir=dest) 510*4882a593Smuzhiyun 511*4882a593Smuzhiyun # Resolve specified revisions 512*4882a593Smuzhiyun parsed_revs = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join('"%s^{}"' % r for r in ud.shallow_revs)), d, workdir=dest) 513*4882a593Smuzhiyun shallow_revisions = parsed_depths.splitlines() + parsed_revs.splitlines() 514*4882a593Smuzhiyun 515*4882a593Smuzhiyun # Apply extra ref wildcards 516*4882a593Smuzhiyun all_refs = runfetchcmd('%s for-each-ref "--format=%%(refname)"' % ud.basecmd, 517*4882a593Smuzhiyun d, workdir=dest).splitlines() 518*4882a593Smuzhiyun for r in ud.shallow_extra_refs: 519*4882a593Smuzhiyun if not ud.bareclone: 520*4882a593Smuzhiyun r = r.replace('refs/heads/', 'refs/remotes/origin/') 521*4882a593Smuzhiyun 522*4882a593Smuzhiyun if '*' in r: 523*4882a593Smuzhiyun matches = filter(lambda a: fnmatch.fnmatchcase(a, r), all_refs) 524*4882a593Smuzhiyun shallow_branches.extend(matches) 525*4882a593Smuzhiyun else: 526*4882a593Smuzhiyun shallow_branches.append(r) 527*4882a593Smuzhiyun 528*4882a593Smuzhiyun # Make the repository shallow 529*4882a593Smuzhiyun shallow_cmd = [self.make_shallow_path, '-s'] 530*4882a593Smuzhiyun for b in shallow_branches: 531*4882a593Smuzhiyun shallow_cmd.append('-r') 532*4882a593Smuzhiyun shallow_cmd.append(b) 533*4882a593Smuzhiyun shallow_cmd.extend(shallow_revisions) 534*4882a593Smuzhiyun runfetchcmd(subprocess.list2cmdline(shallow_cmd), d, workdir=dest) 535*4882a593Smuzhiyun 536*4882a593Smuzhiyun def unpack(self, ud, destdir, d): 537*4882a593Smuzhiyun """ unpack the downloaded src to destdir""" 538*4882a593Smuzhiyun 539*4882a593Smuzhiyun subdir = ud.parm.get("subdir") 540*4882a593Smuzhiyun subpath = ud.parm.get("subpath") 541*4882a593Smuzhiyun readpathspec = "" 542*4882a593Smuzhiyun def_destsuffix = "git/" 543*4882a593Smuzhiyun 544*4882a593Smuzhiyun if subpath: 545*4882a593Smuzhiyun readpathspec = ":%s" % subpath 546*4882a593Smuzhiyun def_destsuffix = "%s/" % os.path.basename(subpath.rstrip('/')) 547*4882a593Smuzhiyun 548*4882a593Smuzhiyun if subdir: 549*4882a593Smuzhiyun # If 'subdir' param exists, create a dir and use it as destination for unpack cmd 550*4882a593Smuzhiyun if os.path.isabs(subdir): 551*4882a593Smuzhiyun if not os.path.realpath(subdir).startswith(os.path.realpath(destdir)): 552*4882a593Smuzhiyun raise bb.fetch2.UnpackError("subdir argument isn't a subdirectory of unpack root %s" % destdir, ud.url) 553*4882a593Smuzhiyun destdir = subdir 554*4882a593Smuzhiyun else: 555*4882a593Smuzhiyun destdir = os.path.join(destdir, subdir) 556*4882a593Smuzhiyun def_destsuffix = "" 557*4882a593Smuzhiyun 558*4882a593Smuzhiyun destsuffix = ud.parm.get("destsuffix", def_destsuffix) 559*4882a593Smuzhiyun destdir = ud.destdir = os.path.join(destdir, destsuffix) 560*4882a593Smuzhiyun if os.path.exists(destdir): 561*4882a593Smuzhiyun bb.utils.prunedir(destdir) 562*4882a593Smuzhiyun 563*4882a593Smuzhiyun need_lfs = self._need_lfs(ud) 564*4882a593Smuzhiyun 565*4882a593Smuzhiyun if not need_lfs: 566*4882a593Smuzhiyun ud.basecmd = "GIT_LFS_SKIP_SMUDGE=1 " + ud.basecmd 567*4882a593Smuzhiyun 568*4882a593Smuzhiyun source_found = False 569*4882a593Smuzhiyun source_error = [] 570*4882a593Smuzhiyun 571*4882a593Smuzhiyun if not source_found: 572*4882a593Smuzhiyun clonedir_is_up_to_date = not self.clonedir_need_update(ud, d) 573*4882a593Smuzhiyun if clonedir_is_up_to_date: 574*4882a593Smuzhiyun runfetchcmd("%s clone %s %s/ %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, destdir), d) 575*4882a593Smuzhiyun source_found = True 576*4882a593Smuzhiyun else: 577*4882a593Smuzhiyun source_error.append("clone directory not available or not up to date: " + ud.clonedir) 578*4882a593Smuzhiyun 579*4882a593Smuzhiyun if not source_found: 580*4882a593Smuzhiyun if ud.shallow: 581*4882a593Smuzhiyun if os.path.exists(ud.fullshallow): 582*4882a593Smuzhiyun bb.utils.mkdirhier(destdir) 583*4882a593Smuzhiyun runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=destdir) 584*4882a593Smuzhiyun source_found = True 585*4882a593Smuzhiyun else: 586*4882a593Smuzhiyun source_error.append("shallow clone not available: " + ud.fullshallow) 587*4882a593Smuzhiyun else: 588*4882a593Smuzhiyun source_error.append("shallow clone not enabled") 589*4882a593Smuzhiyun 590*4882a593Smuzhiyun if not source_found: 591*4882a593Smuzhiyun raise bb.fetch2.UnpackError("No up to date source found: " + "; ".join(source_error), ud.url) 592*4882a593Smuzhiyun 593*4882a593Smuzhiyun repourl = self._get_repo_url(ud) 594*4882a593Smuzhiyun runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=destdir) 595*4882a593Smuzhiyun 596*4882a593Smuzhiyun if self._contains_lfs(ud, d, destdir): 597*4882a593Smuzhiyun if need_lfs and not self._find_git_lfs(d): 598*4882a593Smuzhiyun raise bb.fetch2.FetchError("Repository %s has LFS content, install git-lfs on host to download (or set lfs=0 to ignore it)" % (repourl)) 599*4882a593Smuzhiyun elif not need_lfs: 600*4882a593Smuzhiyun bb.note("Repository %s has LFS content but it is not being fetched" % (repourl)) 601*4882a593Smuzhiyun 602*4882a593Smuzhiyun if not ud.nocheckout: 603*4882a593Smuzhiyun if subpath: 604*4882a593Smuzhiyun runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d, 605*4882a593Smuzhiyun workdir=destdir) 606*4882a593Smuzhiyun runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d, workdir=destdir) 607*4882a593Smuzhiyun elif not ud.nobranch: 608*4882a593Smuzhiyun branchname = ud.branches[ud.names[0]] 609*4882a593Smuzhiyun runfetchcmd("%s checkout -B %s %s" % (ud.basecmd, branchname, \ 610*4882a593Smuzhiyun ud.revisions[ud.names[0]]), d, workdir=destdir) 611*4882a593Smuzhiyun runfetchcmd("%s branch %s --set-upstream-to origin/%s" % (ud.basecmd, branchname, \ 612*4882a593Smuzhiyun branchname), d, workdir=destdir) 613*4882a593Smuzhiyun else: 614*4882a593Smuzhiyun runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revisions[ud.names[0]]), d, workdir=destdir) 615*4882a593Smuzhiyun 616*4882a593Smuzhiyun return True 617*4882a593Smuzhiyun 618*4882a593Smuzhiyun def clean(self, ud, d): 619*4882a593Smuzhiyun """ clean the git directory """ 620*4882a593Smuzhiyun 621*4882a593Smuzhiyun to_remove = [ud.localpath, ud.fullmirror, ud.fullmirror + ".done"] 622*4882a593Smuzhiyun # The localpath is a symlink to clonedir when it is cloned from a 623*4882a593Smuzhiyun # mirror, so remove both of them. 624*4882a593Smuzhiyun if os.path.islink(ud.localpath): 625*4882a593Smuzhiyun clonedir = os.path.realpath(ud.localpath) 626*4882a593Smuzhiyun to_remove.append(clonedir) 627*4882a593Smuzhiyun 628*4882a593Smuzhiyun for r in to_remove: 629*4882a593Smuzhiyun if os.path.exists(r): 630*4882a593Smuzhiyun bb.note('Removing %s' % r) 631*4882a593Smuzhiyun bb.utils.remove(r, True) 632*4882a593Smuzhiyun 633*4882a593Smuzhiyun def supports_srcrev(self): 634*4882a593Smuzhiyun return True 635*4882a593Smuzhiyun 636*4882a593Smuzhiyun def _contains_ref(self, ud, d, name, wd): 637*4882a593Smuzhiyun cmd = "" 638*4882a593Smuzhiyun if ud.nobranch: 639*4882a593Smuzhiyun cmd = "%s log --pretty=oneline -n 1 %s -- 2> /dev/null | wc -l" % ( 640*4882a593Smuzhiyun ud.basecmd, ud.revisions[name]) 641*4882a593Smuzhiyun else: 642*4882a593Smuzhiyun cmd = "%s branch --contains %s --list %s 2> /dev/null | wc -l" % ( 643*4882a593Smuzhiyun ud.basecmd, ud.revisions[name], ud.branches[name]) 644*4882a593Smuzhiyun try: 645*4882a593Smuzhiyun output = runfetchcmd(cmd, d, quiet=True, workdir=wd) 646*4882a593Smuzhiyun except bb.fetch2.FetchError: 647*4882a593Smuzhiyun return False 648*4882a593Smuzhiyun if len(output.split()) > 1: 649*4882a593Smuzhiyun raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output)) 650*4882a593Smuzhiyun return output.split()[0] != "0" 651*4882a593Smuzhiyun 652*4882a593Smuzhiyun def _need_lfs(self, ud): 653*4882a593Smuzhiyun return ud.parm.get("lfs", "1") == "1" 654*4882a593Smuzhiyun 655*4882a593Smuzhiyun def _contains_lfs(self, ud, d, wd): 656*4882a593Smuzhiyun """ 657*4882a593Smuzhiyun Check if the repository has 'lfs' (large file) content 658*4882a593Smuzhiyun """ 659*4882a593Smuzhiyun 660*4882a593Smuzhiyun if not ud.nobranch: 661*4882a593Smuzhiyun branchname = ud.branches[ud.names[0]] 662*4882a593Smuzhiyun else: 663*4882a593Smuzhiyun branchname = "master" 664*4882a593Smuzhiyun 665*4882a593Smuzhiyun # The bare clonedir doesn't use the remote names; it has the branch immediately. 666*4882a593Smuzhiyun if wd == ud.clonedir: 667*4882a593Smuzhiyun refname = ud.branches[ud.names[0]] 668*4882a593Smuzhiyun else: 669*4882a593Smuzhiyun refname = "origin/%s" % ud.branches[ud.names[0]] 670*4882a593Smuzhiyun 671*4882a593Smuzhiyun cmd = "%s grep lfs %s:.gitattributes | wc -l" % ( 672*4882a593Smuzhiyun ud.basecmd, refname) 673*4882a593Smuzhiyun 674*4882a593Smuzhiyun try: 675*4882a593Smuzhiyun output = runfetchcmd(cmd, d, quiet=True, workdir=wd) 676*4882a593Smuzhiyun if int(output) > 0: 677*4882a593Smuzhiyun return True 678*4882a593Smuzhiyun except (bb.fetch2.FetchError,ValueError): 679*4882a593Smuzhiyun pass 680*4882a593Smuzhiyun return False 681*4882a593Smuzhiyun 682*4882a593Smuzhiyun def _find_git_lfs(self, d): 683*4882a593Smuzhiyun """ 684*4882a593Smuzhiyun Return True if git-lfs can be found, False otherwise. 685*4882a593Smuzhiyun """ 686*4882a593Smuzhiyun import shutil 687*4882a593Smuzhiyun return shutil.which("git-lfs", path=d.getVar('PATH')) is not None 688*4882a593Smuzhiyun 689*4882a593Smuzhiyun def _get_repo_url(self, ud): 690*4882a593Smuzhiyun """ 691*4882a593Smuzhiyun Return the repository URL 692*4882a593Smuzhiyun """ 693*4882a593Smuzhiyun # Note that we do not support passwords directly in the git urls. There are several 694*4882a593Smuzhiyun # reasons. SRC_URI can be written out to things like buildhistory and people don't 695*4882a593Smuzhiyun # want to leak passwords like that. Its also all too easy to share metadata without 696*4882a593Smuzhiyun # removing the password. ssh keys, ~/.netrc and ~/.ssh/config files can be used as 697*4882a593Smuzhiyun # alternatives so we will not take patches adding password support here. 698*4882a593Smuzhiyun if ud.user: 699*4882a593Smuzhiyun username = ud.user + '@' 700*4882a593Smuzhiyun else: 701*4882a593Smuzhiyun username = "" 702*4882a593Smuzhiyun return "%s://%s%s%s" % (ud.proto, username, ud.host, ud.path) 703*4882a593Smuzhiyun 704*4882a593Smuzhiyun def _revision_key(self, ud, d, name): 705*4882a593Smuzhiyun """ 706*4882a593Smuzhiyun Return a unique key for the url 707*4882a593Smuzhiyun """ 708*4882a593Smuzhiyun # Collapse adjacent slashes 709*4882a593Smuzhiyun slash_re = re.compile(r"/+") 710*4882a593Smuzhiyun return "git:" + ud.host + slash_re.sub(".", ud.path) + ud.unresolvedrev[name] 711*4882a593Smuzhiyun 712*4882a593Smuzhiyun def _lsremote(self, ud, d, search): 713*4882a593Smuzhiyun """ 714*4882a593Smuzhiyun Run git ls-remote with the specified search string 715*4882a593Smuzhiyun """ 716*4882a593Smuzhiyun # Prevent recursion e.g. in OE if SRCPV is in PV, PV is in WORKDIR, 717*4882a593Smuzhiyun # and WORKDIR is in PATH (as a result of RSS), our call to 718*4882a593Smuzhiyun # runfetchcmd() exports PATH so this function will get called again (!) 719*4882a593Smuzhiyun # In this scenario the return call of the function isn't actually 720*4882a593Smuzhiyun # important - WORKDIR isn't needed in PATH to call git ls-remote 721*4882a593Smuzhiyun # anyway. 722*4882a593Smuzhiyun if d.getVar('_BB_GIT_IN_LSREMOTE', False): 723*4882a593Smuzhiyun return '' 724*4882a593Smuzhiyun d.setVar('_BB_GIT_IN_LSREMOTE', '1') 725*4882a593Smuzhiyun try: 726*4882a593Smuzhiyun repourl = self._get_repo_url(ud) 727*4882a593Smuzhiyun cmd = "%s ls-remote %s %s" % \ 728*4882a593Smuzhiyun (ud.basecmd, shlex.quote(repourl), search) 729*4882a593Smuzhiyun if ud.proto.lower() != 'file': 730*4882a593Smuzhiyun bb.fetch2.check_network_access(d, cmd, repourl) 731*4882a593Smuzhiyun output = runfetchcmd(cmd, d, True) 732*4882a593Smuzhiyun if not output: 733*4882a593Smuzhiyun raise bb.fetch2.FetchError("The command %s gave empty output unexpectedly" % cmd, ud.url) 734*4882a593Smuzhiyun finally: 735*4882a593Smuzhiyun d.delVar('_BB_GIT_IN_LSREMOTE') 736*4882a593Smuzhiyun return output 737*4882a593Smuzhiyun 738*4882a593Smuzhiyun def _latest_revision(self, ud, d, name): 739*4882a593Smuzhiyun """ 740*4882a593Smuzhiyun Compute the HEAD revision for the url 741*4882a593Smuzhiyun """ 742*4882a593Smuzhiyun if not d.getVar("__BBSEENSRCREV"): 743*4882a593Smuzhiyun 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)) 744*4882a593Smuzhiyun 745*4882a593Smuzhiyun # Ensure we mark as not cached 746*4882a593Smuzhiyun bb.fetch2.get_autorev(d) 747*4882a593Smuzhiyun 748*4882a593Smuzhiyun output = self._lsremote(ud, d, "") 749*4882a593Smuzhiyun # Tags of the form ^{} may not work, need to fallback to other form 750*4882a593Smuzhiyun if ud.unresolvedrev[name][:5] == "refs/" or ud.usehead: 751*4882a593Smuzhiyun head = ud.unresolvedrev[name] 752*4882a593Smuzhiyun tag = ud.unresolvedrev[name] 753*4882a593Smuzhiyun else: 754*4882a593Smuzhiyun head = "refs/heads/%s" % ud.unresolvedrev[name] 755*4882a593Smuzhiyun tag = "refs/tags/%s" % ud.unresolvedrev[name] 756*4882a593Smuzhiyun for s in [head, tag + "^{}", tag]: 757*4882a593Smuzhiyun for l in output.strip().split('\n'): 758*4882a593Smuzhiyun sha1, ref = l.split() 759*4882a593Smuzhiyun if s == ref: 760*4882a593Smuzhiyun return sha1 761*4882a593Smuzhiyun raise bb.fetch2.FetchError("Unable to resolve '%s' in upstream git repository in git ls-remote output for %s" % \ 762*4882a593Smuzhiyun (ud.unresolvedrev[name], ud.host+ud.path)) 763*4882a593Smuzhiyun 764*4882a593Smuzhiyun def latest_versionstring(self, ud, d): 765*4882a593Smuzhiyun """ 766*4882a593Smuzhiyun Compute the latest release name like "x.y.x" in "x.y.x+gitHASH" 767*4882a593Smuzhiyun by searching through the tags output of ls-remote, comparing 768*4882a593Smuzhiyun versions and returning the highest match. 769*4882a593Smuzhiyun """ 770*4882a593Smuzhiyun pupver = ('', '') 771*4882a593Smuzhiyun 772*4882a593Smuzhiyun tagregex = re.compile(d.getVar('UPSTREAM_CHECK_GITTAGREGEX') or r"(?P<pver>([0-9][\.|_]?)+)") 773*4882a593Smuzhiyun try: 774*4882a593Smuzhiyun output = self._lsremote(ud, d, "refs/tags/*") 775*4882a593Smuzhiyun except (bb.fetch2.FetchError, bb.fetch2.NetworkAccess) as e: 776*4882a593Smuzhiyun bb.note("Could not list remote: %s" % str(e)) 777*4882a593Smuzhiyun return pupver 778*4882a593Smuzhiyun 779*4882a593Smuzhiyun verstring = "" 780*4882a593Smuzhiyun revision = "" 781*4882a593Smuzhiyun for line in output.split("\n"): 782*4882a593Smuzhiyun if not line: 783*4882a593Smuzhiyun break 784*4882a593Smuzhiyun 785*4882a593Smuzhiyun tag_head = line.split("/")[-1] 786*4882a593Smuzhiyun # Ignore non-released branches 787*4882a593Smuzhiyun m = re.search(r"(alpha|beta|rc|final)+", tag_head) 788*4882a593Smuzhiyun if m: 789*4882a593Smuzhiyun continue 790*4882a593Smuzhiyun 791*4882a593Smuzhiyun # search for version in the line 792*4882a593Smuzhiyun tag = tagregex.search(tag_head) 793*4882a593Smuzhiyun if tag is None: 794*4882a593Smuzhiyun continue 795*4882a593Smuzhiyun 796*4882a593Smuzhiyun tag = tag.group('pver') 797*4882a593Smuzhiyun tag = tag.replace("_", ".") 798*4882a593Smuzhiyun 799*4882a593Smuzhiyun if verstring and bb.utils.vercmp(("0", tag, ""), ("0", verstring, "")) < 0: 800*4882a593Smuzhiyun continue 801*4882a593Smuzhiyun 802*4882a593Smuzhiyun verstring = tag 803*4882a593Smuzhiyun revision = line.split()[0] 804*4882a593Smuzhiyun pupver = (verstring, revision) 805*4882a593Smuzhiyun 806*4882a593Smuzhiyun return pupver 807*4882a593Smuzhiyun 808*4882a593Smuzhiyun def _build_revision(self, ud, d, name): 809*4882a593Smuzhiyun return ud.revisions[name] 810*4882a593Smuzhiyun 811*4882a593Smuzhiyun def gitpkgv_revision(self, ud, d, name): 812*4882a593Smuzhiyun """ 813*4882a593Smuzhiyun Return a sortable revision number by counting commits in the history 814*4882a593Smuzhiyun Based on gitpkgv.bblass in meta-openembedded 815*4882a593Smuzhiyun """ 816*4882a593Smuzhiyun rev = self._build_revision(ud, d, name) 817*4882a593Smuzhiyun localpath = ud.localpath 818*4882a593Smuzhiyun rev_file = os.path.join(localpath, "oe-gitpkgv_" + rev) 819*4882a593Smuzhiyun if not os.path.exists(localpath): 820*4882a593Smuzhiyun commits = None 821*4882a593Smuzhiyun else: 822*4882a593Smuzhiyun if not os.path.exists(rev_file) or not os.path.getsize(rev_file): 823*4882a593Smuzhiyun from pipes import quote 824*4882a593Smuzhiyun commits = bb.fetch2.runfetchcmd( 825*4882a593Smuzhiyun "git rev-list %s -- | wc -l" % quote(rev), 826*4882a593Smuzhiyun d, quiet=True).strip().lstrip('0') 827*4882a593Smuzhiyun if commits: 828*4882a593Smuzhiyun open(rev_file, "w").write("%d\n" % int(commits)) 829*4882a593Smuzhiyun else: 830*4882a593Smuzhiyun commits = open(rev_file, "r").readline(128).strip() 831*4882a593Smuzhiyun if commits: 832*4882a593Smuzhiyun return False, "%s+%s" % (commits, rev[:7]) 833*4882a593Smuzhiyun else: 834*4882a593Smuzhiyun return True, str(rev) 835*4882a593Smuzhiyun 836*4882a593Smuzhiyun def checkstatus(self, fetch, ud, d): 837*4882a593Smuzhiyun try: 838*4882a593Smuzhiyun self._lsremote(ud, d, "") 839*4882a593Smuzhiyun return True 840*4882a593Smuzhiyun except bb.fetch2.FetchError: 841*4882a593Smuzhiyun return False 842