1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 from twisted.python import log
17 from twisted.internet import defer
18
19 from buildbot.process import buildstep
20 from buildbot.steps.source.base import Source
21 from buildbot.interfaces import BuildSlaveTooOldError
24
25 if v:
26 return True
27
28
29
30
31 if isinstance(v, int) and v is not False:
32 return True
33
34
35 return False
36
37 git_describe_flags = [
38
39 ('all', lambda v: ['--all'] if v else None),
40 ('always', lambda v: ['--always'] if v else None),
41 ('contains', lambda v: ['--contains'] if v else None),
42 ('debug', lambda v: ['--debug'] if v else None),
43 ('long', lambda v: ['--long'] if v else None),
44 ('exact-match', lambda v: ['--exact-match'] if v else None),
45 ('tags', lambda v: ['--tags'] if v else None),
46
47 ('match', lambda v: ['--match', v] if v else None),
48
49 ('abbrev', lambda v: ['--abbrev=%s' % v] if isTrueOrIsExactlyZero(v) else None),
50 ('candidates', lambda v: ['--candidates=%s' % v] if isTrueOrIsExactlyZero(v) else None),
51
52 ('dirty', lambda v: ['--dirty'] if (v is True or v=='') else None),
53 ('dirty', lambda v: ['--dirty=%s' % v] if (v and v is not True) else None),
54 ]
55
56 -class Git(Source):
57 """ Class for Git with all the smarts """
58 name='git'
59 renderables = [ "repourl"]
60
61 - def __init__(self, repourl=None, branch='HEAD', mode='incremental',
62 method=None, submodules=False, shallow=False, progress=False,
63 retryFetch=False, clobberOnFailure=False, getDescription=False,
64 **kwargs):
65 """
66 @type repourl: string
67 @param repourl: the URL which points at the git repository
68
69 @type branch: string
70 @param branch: The branch or tag to check out by default. If
71 a build specifies a different branch, it will
72 be used instead of this.
73
74 @type submodules: boolean
75 @param submodules: Whether or not to update (and initialize)
76 git submodules.
77
78 @type mode: string
79 @param mode: Type of checkout. Described in docs.
80
81 @type method: string
82 @param method: Full builds can be done is different ways. This parameter
83 specifies which method to use.
84
85 @type progress: boolean
86 @param progress: Pass the --progress option when fetching. This
87 can solve long fetches getting killed due to
88 lack of output, but requires Git 1.7.2+.
89 @type shallow: boolean
90 @param shallow: Use a shallow or clone, if possible
91
92 @type retryFetch: boolean
93 @param retryFetch: Retry fetching before failing source checkout.
94
95 @type getDescription: boolean or dict
96 @param getDescription: Use 'git describe' to describe the fetched revision
97 """
98 if not getDescription and not isinstance(getDescription, dict):
99 getDescription = False
100
101 self.branch = branch
102 self.method = method
103 self.prog = progress
104 self.repourl = repourl
105 self.retryFetch = retryFetch
106 self.submodules = submodules
107 self.shallow = shallow
108 self.fetchcount = 0
109 self.clobberOnFailure = clobberOnFailure
110 self.mode = mode
111 self.getDescription = getDescription
112 Source.__init__(self, **kwargs)
113
114 assert self.mode in ['incremental', 'full']
115 assert self.repourl is not None
116 if self.mode == 'full':
117 assert self.method in ['clean', 'fresh', 'clobber', 'copy', None]
118 assert isinstance(self.getDescription, (bool, dict))
119
120 - def startVC(self, branch, revision, patch):
131 d.addCallback(checkInstall)
132
133 if self.mode == 'incremental':
134 d.addCallback(lambda _: self.incremental())
135 elif self.mode == 'full':
136 d.addCallback(lambda _: self.full())
137 if patch:
138 d.addCallback(self.patch, patch)
139 d.addCallback(self.parseGotRevision)
140 d.addCallback(self.parseCommitDescription)
141 d.addCallback(self.finish)
142 d.addErrback(self.failed)
143 return d
144
145 @defer.inlineCallbacks
147 if self.method == 'clobber':
148 yield self.clobber()
149 return
150 elif self.method == 'copy':
151 yield self.copy()
152 return
153
154 updatable = yield self._sourcedirIsUpdatable()
155 if not updatable:
156 log.msg("No git repo present, making full clone")
157 yield self._doFull()
158 elif self.method == 'clean':
159 yield self.clean()
160 elif self.method == 'fresh':
161 yield self.fresh()
162 else:
163 raise ValueError("Unknown method, check your configuration")
164
165 @defer.inlineCallbacks
167 updatable = yield self._sourcedirIsUpdatable()
168
169
170 if not updatable:
171 yield self._doFull()
172 return
173
174
175 if self.revision:
176 rc = yield self._dovccmd(['cat-file', '-e', self.revision],
177 abandonOnFailure=False)
178 else:
179 rc = 1
180
181
182
183 if rc == 0:
184 yield self._dovccmd(['reset', '--hard', self.revision, '--'])
185
186 if self.branch != 'HEAD':
187 yield self._dovccmd(['branch', '-M', self.branch],
188 abandonOnFailure=False)
189 else:
190 yield self._doFetch(None)
191
192 yield self._updateSubmodule(None)
193
195 command = ['clean', '-f', '-d']
196 d = self._dovccmd(command)
197 d.addCallback(self._doFetch)
198 d.addCallback(self._updateSubmodule)
199 d.addCallback(self._cleanSubmodule)
200 return d
201
203 cmd = buildstep.RemoteCommand('rmdir', {'dir': self.workdir,
204 'logEnviron': self.logEnviron,})
205 cmd.useLog(self.stdio_log, False)
206 d = self.runCommand(cmd)
207 def checkRemoval(res):
208 if res != 0:
209 raise RuntimeError("Failed to delete directory")
210 return res
211 d.addCallback(lambda _: checkRemoval(cmd.rc))
212 d.addCallback(lambda _: self._doFull())
213 return d
214
216 command = ['clean', '-f', '-d', '-x']
217 d = self._dovccmd(command)
218 d.addCallback(self._doFetch)
219 d.addCallback(self._updateSubmodule)
220 d.addCallback(self._cleanSubmodule)
221 return d
222
239 d.addCallback(copy)
240 def resetWorkdir(_):
241 self.workdir = 'build'
242 return 0
243
244 d.addCallback(resetWorkdir)
245 return d
246
248 d = defer.succeed(res)
249 def _gotResults(results):
250 self.setStatus(self.cmd, results)
251 log.msg("Closing log, sending result of the command %s " % \
252 (self.cmd))
253 return results
254 d.addCallback(_gotResults)
255 d.addCallbacks(self.finished, self.checkDisconnect)
256 return d
257
258 @defer.inlineCallbacks
268
269 @defer.inlineCallbacks
271 if self.getDescription==False:
272 defer.returnValue(0)
273 return
274
275 cmd = ['describe']
276 if isinstance(self.getDescription, dict):
277 for opt, arg in git_describe_flags:
278 opt = self.getDescription.get(opt, None)
279 arg = arg(opt)
280 if arg:
281 cmd.extend(arg)
282 cmd.append('HEAD')
283
284 try:
285 stdout = yield self._dovccmd(cmd, collectStdout=True)
286 desc = stdout.strip()
287 self.updateSourceProperty('commit-description', desc)
288 except:
289 pass
290
291 defer.returnValue(0)
292
293 - def _dovccmd(self, command, abandonOnFailure=True, collectStdout=False, initialStdin=None):
311 d.addCallback(lambda _: evaluateCommand(cmd))
312 return d
313
315 command = ['fetch', '-t', self.repourl, self.branch]
316
317
318
319
320 if self.prog:
321 command.append('--progress')
322
323 d = self._dovccmd(command)
324 def checkout(_):
325 if self.revision:
326 rev = self.revision
327 else:
328 rev = 'FETCH_HEAD'
329 command = ['reset', '--hard', rev, '--']
330 abandonOnFailure = not self.retryFetch and not self.clobberOnFailure
331 return self._dovccmd(command, abandonOnFailure)
332 d.addCallback(checkout)
333 def renameBranch(res):
334 if res != 0:
335 return res
336 d = self._dovccmd(['branch', '-M', self.branch], abandonOnFailure=False)
337
338 d.addCallback(lambda _: res)
339 return d
340
341 if self.branch != 'HEAD':
342 d.addCallback(renameBranch)
343 return d
344
345 - def patch(self, _, patch):
346 d = self._dovccmd(['apply', '--index', '-p', str(patch[0])],
347 initialStdin=patch[1])
348 return d
349
350 @defer.inlineCallbacks
352 """
353 Handles fallbacks for failure of fetch,
354 wrapper for self._fetch
355 """
356 res = yield self._fetch(None)
357 if res == 0:
358 defer.returnValue(res)
359 return
360 elif self.retryFetch:
361 yield self._fetch(None)
362 elif self.clobberOnFailure:
363 yield self.clobber()
364 else:
365 raise buildstep.BuildStepFailed()
366
368 args = []
369 if self.branch != 'HEAD':
370 args += ['--branch', self.branch]
371 if self.shallow:
372 args += ['--depth', '1']
373 command = ['clone'] + args + [self.repourl, '.']
374
375 if self.prog:
376 command.append('--progress')
377
378 d = self._dovccmd(command, not self.clobberOnFailure)
379
380 if self.revision:
381 d.addCallback(lambda _: self._dovccmd(['reset', '--hard',
382 self.revision, '--'],
383 not self.clobberOnFailure))
384
385
386 if self.submodules:
387 d.addCallback(lambda _: self._dovccmd(['submodule', 'update',
388 '--init', '--recursive'],
389 not self.clobberOnFailure))
390 return d
391
393 d = self._full()
394 def clobber(res):
395 if res != 0:
396 if self.clobberOnFailure:
397 return self.clobber()
398 else:
399 raise buildstep.BuildStepFailed()
400 else:
401 return res
402 d.addCallback(clobber)
403 return d
404
409
419 d.addCallback(_fail)
420 return d
421
423 if self.submodules:
424 return self._dovccmd(['submodule', 'update', '--recursive'])
425 else:
426 return defer.succeed(0)
427
429 if self.submodules:
430 command = ['submodule', 'foreach', 'git', 'clean', '-f', '-d']
431 if self.mode == 'full' and self.method == 'fresh':
432 command.append('-x')
433 return self._dovccmd(command)
434 else:
435 return defer.succeed(0)
436
438 if self.method is not None and self.mode != 'incremental':
439 return self.method
440 elif self.mode == 'incremental':
441 return None
442 elif self.method is None and self.mode == 'full':
443 return 'fresh'
444
446 d = self._dovccmd(['--version'])
447 def check(res):
448 if res == 0:
449 return True
450 return False
451 d.addCallback(check)
452 return d
453