bitbake: fetch2/git: add nobranch option for SRC_URI to skip SHA validating for branch
[bitbake.git] / lib / bb / fetch2 / git.py
1 # ex:ts=4:sw=4:sts=4:et
2 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 """
4 BitBake 'Fetch' git implementation
5
6 git fetcher support the SRC_URI with format of:
7 SRC_URI = "git://some.host/somepath;OptionA=xxx;OptionB=xxx;..."
8
9 Supported 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 """
48
49 #Copyright (C) 2005 Richard Purdie
50 #
51 # This program is free software; you can redistribute it and/or modify
52 # it under the terms of the GNU General Public License version 2 as
53 # published by the Free Software Foundation.
54 #
55 # This program is distributed in the hope that it will be useful,
56 # but WITHOUT ANY WARRANTY; without even the implied warranty of
57 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
58 # GNU General Public License for more details.
59 #
60 # You should have received a copy of the GNU General Public License along
61 # with this program; if not, write to the Free Software Foundation, Inc.,
62 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
63
64 import os
65 import bb
66 from   bb    import data
67 from   bb.fetch2 import FetchMethod
68 from   bb.fetch2 import runfetchcmd
69 from   bb.fetch2 import logger
70
71 class Git(FetchMethod):
72     """Class to fetch a module or modules from git repositories"""
73     def init(self, d):
74         pass
75
76     def supports(self, ud, d):
77         """
78         Check to see if a given url can be fetched with git.
79         """
80         return ud.type in ['git']
81
82     def supports_checksum(self, urldata):
83         return False
84
85     def urldata_init(self, ud, d):
86         """
87         init git specific variable within url data
88         so that the git method like latest_revision() can work
89         """
90         if 'protocol' in ud.parm:
91             ud.proto = ud.parm['protocol']
92         elif not ud.host:
93             ud.proto = 'file'
94         else:
95             ud.proto = "git"
96
97         if not ud.proto in ('git', 'file', 'ssh', 'http', 'https', 'rsync'):
98             raise bb.fetch2.ParameterError("Invalid protocol type", ud.url)
99
100         ud.nocheckout = ud.parm.get("nocheckout","0") == "1"
101
102         ud.rebaseable = ud.parm.get("rebaseable","0") == "1"
103
104         ud.nobranch = ud.parm.get("nobranch","0") == "1"
105
106         # bareclone implies nocheckout
107         ud.bareclone = ud.parm.get("bareclone","0") == "1"
108         if ud.bareclone:
109             ud.nocheckout = 1
110   
111         ud.unresolvedrev = {}
112         branches = ud.parm.get("branch", "master").split(',')
113         if len(branches) != len(ud.names):
114             raise bb.fetch2.ParameterError("The number of name and branch parameters is not balanced", ud.url)
115         ud.branches = {}
116         for name in ud.names:
117             branch = branches[ud.names.index(name)]
118             ud.branches[name] = branch
119             ud.unresolvedrev[name] = branch
120
121         ud.basecmd = data.getVar("FETCHCMD_git", d, True) or "git"
122
123         ud.write_tarballs = ((data.getVar("BB_GENERATE_MIRROR_TARBALLS", d, True) or "0") != "0") or ud.rebaseable
124
125         ud.setup_revisons(d)
126
127         for name in ud.names:
128             # Ensure anything that doesn't look like a sha256 checksum/revision is translated into one
129             if not ud.revisions[name] or len(ud.revisions[name]) != 40  or (False in [c in "abcdef0123456789" for c in ud.revisions[name]]):
130                 if ud.revisions[name]:
131                     ud.unresolvedrev[name] = ud.revisions[name]
132                 ud.revisions[name] = self.latest_revision(ud, d, name)
133
134         gitsrcname = '%s%s' % (ud.host.replace(':','.'), ud.path.replace('/', '.').replace('*', '.'))
135         # for rebaseable git repo, it is necessary to keep mirror tar ball
136         # per revision, so that even the revision disappears from the
137         # upstream repo in the future, the mirror will remain intact and still
138         # contains the revision
139         if ud.rebaseable:
140             for name in ud.names:
141                 gitsrcname = gitsrcname + '_' + ud.revisions[name]
142         ud.mirrortarball = 'git2_%s.tar.gz' % (gitsrcname)
143         ud.fullmirror = os.path.join(d.getVar("DL_DIR", True), ud.mirrortarball)
144         gitdir = d.getVar("GITDIR", True) or (d.getVar("DL_DIR", True) + "/git2/")
145         ud.clonedir = os.path.join(gitdir, gitsrcname)
146
147         ud.localfile = ud.clonedir
148
149     def localpath(self, ud, d):
150         return ud.clonedir
151
152     def need_update(self, ud, d):
153         if not os.path.exists(ud.clonedir):
154             return True
155         os.chdir(ud.clonedir)
156         for name in ud.names:
157             if not self._contains_ref(ud, d, name):
158                 return True
159         if ud.write_tarballs and not os.path.exists(ud.fullmirror):
160             return True
161         return False
162
163     def try_premirror(self, ud, d):
164         # If we don't do this, updating an existing checkout with only premirrors
165         # is not possible
166         if d.getVar("BB_FETCH_PREMIRRORONLY", True) is not None:
167             return True
168         if os.path.exists(ud.clonedir):
169             return False
170         return True
171
172     def download(self, ud, d):
173         """Fetch url"""
174
175         if ud.user:
176             username = ud.user + '@'
177         else:
178             username = ""
179
180         ud.repochanged = not os.path.exists(ud.fullmirror)
181
182         # If the checkout doesn't exist and the mirror tarball does, extract it
183         if not os.path.exists(ud.clonedir) and os.path.exists(ud.fullmirror):
184             bb.utils.mkdirhier(ud.clonedir)
185             os.chdir(ud.clonedir)
186             runfetchcmd("tar -xzf %s" % (ud.fullmirror), d)
187
188         repourl = "%s://%s%s%s" % (ud.proto, username, ud.host, ud.path)
189
190         # If the repo still doesn't exist, fallback to cloning it
191         if not os.path.exists(ud.clonedir):
192             # We do this since git will use a "-l" option automatically for local urls where possible
193             if repourl.startswith("file://"):
194                 repourl = repourl[7:]
195             clone_cmd = "%s clone --bare --mirror %s %s" % (ud.basecmd, repourl, ud.clonedir)
196             if ud.proto.lower() != 'file':
197                 bb.fetch2.check_network_access(d, clone_cmd)
198             runfetchcmd(clone_cmd, d)
199
200         os.chdir(ud.clonedir)
201         # Update the checkout if needed
202         needupdate = False
203         for name in ud.names:
204             if not self._contains_ref(ud, d, name):
205                 needupdate = True
206         if needupdate:
207             try: 
208                 runfetchcmd("%s remote rm origin" % ud.basecmd, d) 
209             except bb.fetch2.FetchError:
210                 logger.debug(1, "No Origin")
211
212             runfetchcmd("%s remote add --mirror=fetch origin %s" % (ud.basecmd, repourl), d)
213             fetch_cmd = "%s fetch -f --prune %s refs/*:refs/*" % (ud.basecmd, repourl)
214             if ud.proto.lower() != 'file':
215                 bb.fetch2.check_network_access(d, fetch_cmd, ud.url)
216             runfetchcmd(fetch_cmd, d)
217             runfetchcmd("%s prune-packed" % ud.basecmd, d)
218             runfetchcmd("%s pack-redundant --all | xargs -r rm" % ud.basecmd, d)
219             ud.repochanged = True
220         os.chdir(ud.clonedir)
221         for name in ud.names:
222             if not self._contains_ref(ud, d, name):
223                 raise bb.fetch2.FetchError("Unable to find revision %s in branch %s even from upstream" % (ud.revisions[name], ud.branches[name]))
224
225     def build_mirror_data(self, ud, d):
226         # Generate a mirror tarball if needed
227         if ud.write_tarballs and (ud.repochanged or not os.path.exists(ud.fullmirror)):
228             # it's possible that this symlink points to read-only filesystem with PREMIRROR
229             if os.path.islink(ud.fullmirror):
230                 os.unlink(ud.fullmirror)
231
232             os.chdir(ud.clonedir)
233             logger.info("Creating tarball of git repository")
234             runfetchcmd("tar -czf %s %s" % (ud.fullmirror, os.path.join(".") ), d)
235             runfetchcmd("touch %s.done" % (ud.fullmirror), d)
236
237     def unpack(self, ud, destdir, d):
238         """ unpack the downloaded src to destdir"""
239
240         subdir = ud.parm.get("subpath", "")
241         if subdir != "":
242             readpathspec = ":%s" % (subdir)
243             def_destsuffix = "%s/" % os.path.basename(subdir)
244         else:
245             readpathspec = ""
246             def_destsuffix = "git/"
247
248         destsuffix = ud.parm.get("destsuffix", def_destsuffix)
249         destdir = ud.destdir = os.path.join(destdir, destsuffix)
250         if os.path.exists(destdir):
251             bb.utils.prunedir(destdir)
252
253         cloneflags = "-s -n"
254         if ud.bareclone:
255             cloneflags += " --mirror"
256
257         # Versions of git prior to 1.7.9.2 have issues where foo.git and foo get confused
258         # and you end up with some horrible union of the two when you attempt to clone it
259         # The least invasive workaround seems to be a symlink to the real directory to
260         # fool git into ignoring any .git version that may also be present.
261         #
262         # The issue is fixed in more recent versions of git so we can drop this hack in future
263         # when that version becomes common enough.
264         clonedir = ud.clonedir
265         if not ud.path.endswith(".git"):
266             indirectiondir = destdir[:-1] + ".indirectionsymlink"
267             if os.path.exists(indirectiondir):
268                 os.remove(indirectiondir)
269             bb.utils.mkdirhier(os.path.dirname(indirectiondir))
270             os.symlink(ud.clonedir, indirectiondir)
271             clonedir = indirectiondir
272
273         runfetchcmd("git clone %s %s/ %s" % (cloneflags, clonedir, destdir), d)
274         if not ud.nocheckout:
275             os.chdir(destdir)
276             if subdir != "":
277                 runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d)
278                 runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d)
279             else:
280                 runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revisions[ud.names[0]]), d)
281         return True
282
283     def clean(self, ud, d):
284         """ clean the git directory """
285
286         bb.utils.remove(ud.localpath, True)
287         bb.utils.remove(ud.fullmirror)
288         bb.utils.remove(ud.fullmirror + ".done")
289
290     def supports_srcrev(self):
291         return True
292
293     def _contains_ref(self, ud, d, name):
294         cmd = ""
295         if ud.nobranch:
296             cmd = "%s log --pretty=oneline -n 1 %s -- 2> /dev/null | wc -l" % (
297                 ud.basecmd, ud.revisions[name])
298         else:
299             cmd =  "%s branch --contains %s --list %s 2> /dev/null | wc -l" % (
300                 ud.basecmd, ud.revisions[name], ud.branches[name])
301         try:
302             output = runfetchcmd(cmd, d, quiet=True)
303         except bb.fetch2.FetchError:
304             return False
305         if len(output.split()) > 1:
306             raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
307         return output.split()[0] != "0"
308
309     def _revision_key(self, ud, d, name):
310         """
311         Return a unique key for the url
312         """
313         return "git:" + ud.host + ud.path.replace('/', '.') + ud.unresolvedrev[name]
314
315     def _latest_revision(self, ud, d, name):
316         """
317         Compute the HEAD revision for the url
318         """
319         if ud.user:
320             username = ud.user + '@'
321         else:
322             username = ""
323
324         cmd = "%s ls-remote %s://%s%s%s %s" % \
325               (ud.basecmd, ud.proto, username, ud.host, ud.path, ud.unresolvedrev[name])
326         if ud.proto.lower() != 'file':
327             bb.fetch2.check_network_access(d, cmd)
328         output = runfetchcmd(cmd, d, True)
329         if not output:
330             raise bb.fetch2.FetchError("The command %s gave empty output unexpectedly" % cmd, ud.url)
331         return output.split()[0]
332
333     def _build_revision(self, ud, d, name):
334         return ud.revisions[name]
335
336     def checkstatus(self, ud, d):
337         fetchcmd = "%s ls-remote %s" % (ud.basecmd, ud.url)
338         try:
339             runfetchcmd(fetchcmd, d, quiet=True)
340             return True
341         except FetchError:
342             return False