summaryrefslogtreecommitdiffstats
path: root/manifest_xml.py
diff options
context:
space:
mode:
authorDoug Anderson <dianders@google.com>2011-03-04 11:54:18 -0800
committerShawn O. Pearce <sop@google.com>2011-03-11 11:53:23 -0800
commit37282b4b9c5b1d9a1ff07f7f0686a81b65a0a5c6 (patch)
treeaba568b85d38de4cfef90cd771169c9422aef09c /manifest_xml.py
parent835cd6888f16ff30a3428adfa3a775efad918880 (diff)
downloadgit-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.py90
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