diff options
author | Doug Anderson <dianders@google.com> | 2011-03-04 11:54:18 -0800 |
---|---|---|
committer | Shawn O. Pearce <sop@google.com> | 2011-03-11 11:53:23 -0800 |
commit | 37282b4b9c5b1d9a1ff07f7f0686a81b65a0a5c6 (patch) | |
tree | aba568b85d38de4cfef90cd771169c9422aef09c /manifest_xml.py | |
parent | 835cd6888f16ff30a3428adfa3a775efad918880 (diff) | |
download | git-repo-37282b4b9c5b1d9a1ff07f7f0686a81b65a0a5c6.tar.gz |
Support repo-level pre-upload hook and prep for future hooks.v1.7.4
All repo-level hooks are expected to live in a single project at the
top level of that project. The name of the hooks project is provided
in the manifest.xml. The manifest also lists which hooks are enabled
to make it obvious if a file somehow failed to sync down (or got
deleted).
Before running any hook, we will prompt the user to make sure that it
is OK. A user can deny running the hook, allow once, or allow
"forever" (until hooks change). This tries to keep with the git
spirit of not automatically running anything on the user's computer
that got synced down. Note that individual repo commands can add
always options to avoid these prompts as they see fit (see below for
the 'upload' options).
When hooks are run, they are loaded into the current interpreter (the
one running repo) and their main() function is run. This mechanism is
used (instead of using subprocess) to make it easier to expand to a
richer hook interface in the future. During loading, the
interpreter's sys.path is updated to contain the directory containing
the hooks so that hooks can be split into multiple files.
The upload command has two options that control hook behavior:
- no-verify=False, verify=False (DEFAULT):
If stdout is a tty, can prompt about running upload hooks if needed.
If user denies running hooks, the upload is cancelled. If stdout is
not a tty and we would need to prompt about upload hooks, upload is
cancelled.
- no-verify=False, verify=True:
Always run upload hooks with no prompt.
- no-verify=True, verify=False:
Never run upload hooks, but upload anyway (AKA bypass hooks).
- no-verify=True, verify=True:
Invalid
Sample bit of manifest.xml code for enabling hooks (assumes you have a
project named 'hooks' where hooks are stored):
<repo-hooks in-project="hooks" enabled-list="pre-upload" />
Sample main() function in pre-upload.py in hooks directory:
def main(project_list, **kwargs):
print ('These projects will be uploaded: %s' %
', '.join(project_list))
print ('I am being a good boy and ignoring anything in kwargs\n'
'that I don\'t understand.')
print 'I fail 50% of the time. How flaky.'
if random.random() <= .5:
raise Exception('Pre-upload hook failed. Have a nice day.')
Change-Id: I5cefa2cd5865c72589263cf8e2f152a43c122f70
Diffstat (limited to 'manifest_xml.py')
-rw-r--r-- | manifest_xml.py | 90 |
1 files changed, 66 insertions, 24 deletions
diff --git a/manifest_xml.py b/manifest_xml.py index 0103cf55..0e6421f1 100644 --- a/manifest_xml.py +++ b/manifest_xml.py | |||
@@ -171,6 +171,14 @@ class XmlManifest(object): | |||
171 | ce.setAttribute('dest', c.dest) | 171 | ce.setAttribute('dest', c.dest) |
172 | e.appendChild(ce) | 172 | e.appendChild(ce) |
173 | 173 | ||
174 | if self._repo_hooks_project: | ||
175 | root.appendChild(doc.createTextNode('')) | ||
176 | e = doc.createElement('repo-hooks') | ||
177 | e.setAttribute('in-project', self._repo_hooks_project.name) | ||
178 | e.setAttribute('enabled-list', | ||
179 | ' '.join(self._repo_hooks_project.enabled_repo_hooks)) | ||
180 | root.appendChild(e) | ||
181 | |||
174 | doc.writexml(fd, '', ' ', '\n', 'UTF-8') | 182 | doc.writexml(fd, '', ' ', '\n', 'UTF-8') |
175 | 183 | ||
176 | @property | 184 | @property |
@@ -189,6 +197,11 @@ class XmlManifest(object): | |||
189 | return self._default | 197 | return self._default |
190 | 198 | ||
191 | @property | 199 | @property |
200 | def repo_hooks_project(self): | ||
201 | self._Load() | ||
202 | return self._repo_hooks_project | ||
203 | |||
204 | @property | ||
192 | def notice(self): | 205 | def notice(self): |
193 | self._Load() | 206 | self._Load() |
194 | return self._notice | 207 | return self._notice |
@@ -207,6 +220,7 @@ class XmlManifest(object): | |||
207 | self._projects = {} | 220 | self._projects = {} |
208 | self._remotes = {} | 221 | self._remotes = {} |
209 | self._default = None | 222 | self._default = None |
223 | self._repo_hooks_project = None | ||
210 | self._notice = None | 224 | self._notice = None |
211 | self.branch = None | 225 | self.branch = None |
212 | self._manifest_server = None | 226 | self._manifest_server = None |
@@ -239,15 +253,15 @@ class XmlManifest(object): | |||
239 | def _ParseManifest(self, is_root_file): | 253 | def _ParseManifest(self, is_root_file): |
240 | root = xml.dom.minidom.parse(self.manifestFile) | 254 | root = xml.dom.minidom.parse(self.manifestFile) |
241 | if not root or not root.childNodes: | 255 | if not root or not root.childNodes: |
242 | raise ManifestParseError, \ | 256 | raise ManifestParseError( |
243 | "no root node in %s" % \ | 257 | "no root node in %s" % |
244 | self.manifestFile | 258 | self.manifestFile) |
245 | 259 | ||
246 | config = root.childNodes[0] | 260 | config = root.childNodes[0] |
247 | if config.nodeName != 'manifest': | 261 | if config.nodeName != 'manifest': |
248 | raise ManifestParseError, \ | 262 | raise ManifestParseError( |
249 | "no <manifest> in %s" % \ | 263 | "no <manifest> in %s" % |
250 | self.manifestFile | 264 | self.manifestFile) |
251 | 265 | ||
252 | for node in config.childNodes: | 266 | for node in config.childNodes: |
253 | if node.nodeName == 'remove-project': | 267 | if node.nodeName == 'remove-project': |
@@ -255,25 +269,30 @@ class XmlManifest(object): | |||
255 | try: | 269 | try: |
256 | del self._projects[name] | 270 | del self._projects[name] |
257 | except KeyError: | 271 | except KeyError: |
258 | raise ManifestParseError, \ | 272 | raise ManifestParseError( |
259 | 'project %s not found' % \ | 273 | 'project %s not found' % |
260 | (name) | 274 | (name)) |
275 | |||
276 | # If the manifest removes the hooks project, treat it as if it deleted | ||
277 | # the repo-hooks element too. | ||
278 | if self._repo_hooks_project and (self._repo_hooks_project.name == name): | ||
279 | self._repo_hooks_project = None | ||
261 | 280 | ||
262 | for node in config.childNodes: | 281 | for node in config.childNodes: |
263 | if node.nodeName == 'remote': | 282 | if node.nodeName == 'remote': |
264 | remote = self._ParseRemote(node) | 283 | remote = self._ParseRemote(node) |
265 | if self._remotes.get(remote.name): | 284 | if self._remotes.get(remote.name): |
266 | raise ManifestParseError, \ | 285 | raise ManifestParseError( |
267 | 'duplicate remote %s in %s' % \ | 286 | 'duplicate remote %s in %s' % |
268 | (remote.name, self.manifestFile) | 287 | (remote.name, self.manifestFile)) |
269 | self._remotes[remote.name] = remote | 288 | self._remotes[remote.name] = remote |
270 | 289 | ||
271 | for node in config.childNodes: | 290 | for node in config.childNodes: |
272 | if node.nodeName == 'default': | 291 | if node.nodeName == 'default': |
273 | if self._default is not None: | 292 | if self._default is not None: |
274 | raise ManifestParseError, \ | 293 | raise ManifestParseError( |
275 | 'duplicate default in %s' % \ | 294 | 'duplicate default in %s' % |
276 | (self.manifestFile) | 295 | (self.manifestFile)) |
277 | self._default = self._ParseDefault(node) | 296 | self._default = self._ParseDefault(node) |
278 | if self._default is None: | 297 | if self._default is None: |
279 | self._default = _Default() | 298 | self._default = _Default() |
@@ -281,29 +300,52 @@ class XmlManifest(object): | |||
281 | for node in config.childNodes: | 300 | for node in config.childNodes: |
282 | if node.nodeName == 'notice': | 301 | if node.nodeName == 'notice': |
283 | if self._notice is not None: | 302 | if self._notice is not None: |
284 | raise ManifestParseError, \ | 303 | raise ManifestParseError( |
285 | 'duplicate notice in %s' % \ | 304 | 'duplicate notice in %s' % |
286 | (self.manifestFile) | 305 | (self.manifestFile)) |
287 | self._notice = self._ParseNotice(node) | 306 | self._notice = self._ParseNotice(node) |
288 | 307 | ||
289 | for node in config.childNodes: | 308 | for node in config.childNodes: |
290 | if node.nodeName == 'manifest-server': | 309 | if node.nodeName == 'manifest-server': |
291 | url = self._reqatt(node, 'url') | 310 | url = self._reqatt(node, 'url') |
292 | if self._manifest_server is not None: | 311 | if self._manifest_server is not None: |
293 | raise ManifestParseError, \ | 312 | raise ManifestParseError( |
294 | 'duplicate manifest-server in %s' % \ | 313 | 'duplicate manifest-server in %s' % |
295 | (self.manifestFile) | 314 | (self.manifestFile)) |
296 | self._manifest_server = url | 315 | self._manifest_server = url |
297 | 316 | ||
298 | for node in config.childNodes: | 317 | for node in config.childNodes: |
299 | if node.nodeName == 'project': | 318 | if node.nodeName == 'project': |
300 | project = self._ParseProject(node) | 319 | project = self._ParseProject(node) |
301 | if self._projects.get(project.name): | 320 | if self._projects.get(project.name): |
302 | raise ManifestParseError, \ | 321 | raise ManifestParseError( |
303 | 'duplicate project %s in %s' % \ | 322 | 'duplicate project %s in %s' % |
304 | (project.name, self.manifestFile) | 323 | (project.name, self.manifestFile)) |
305 | self._projects[project.name] = project | 324 | self._projects[project.name] = project |
306 | 325 | ||
326 | for node in config.childNodes: | ||
327 | if node.nodeName == 'repo-hooks': | ||
328 | # Get the name of the project and the (space-separated) list of enabled. | ||
329 | repo_hooks_project = self._reqatt(node, 'in-project') | ||
330 | enabled_repo_hooks = self._reqatt(node, 'enabled-list').split() | ||
331 | |||
332 | # Only one project can be the hooks project | ||
333 | if self._repo_hooks_project is not None: | ||
334 | raise ManifestParseError( | ||
335 | 'duplicate repo-hooks in %s' % | ||
336 | (self.manifestFile)) | ||
337 | |||
338 | # Store a reference to the Project. | ||
339 | try: | ||
340 | self._repo_hooks_project = self._projects[repo_hooks_project] | ||
341 | except KeyError: | ||
342 | raise ManifestParseError( | ||
343 | 'project %s not found for repo-hooks' % | ||
344 | (repo_hooks_project)) | ||
345 | |||
346 | # Store the enabled hooks in the Project object. | ||
347 | self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks | ||
348 | |||
307 | def _AddMetaProjectMirror(self, m): | 349 | def _AddMetaProjectMirror(self, m): |
308 | name = None | 350 | name = None |
309 | m_url = m.GetRemote(m.remote.name).url | 351 | m_url = m.GetRemote(m.remote.name).url |