From a1bfd2cd7253b1662e08f5ec5be3d863430c756c Mon Sep 17 00:00:00 2001 From: Nico Sallembien Date: Tue, 6 Apr 2010 10:40:01 -0700 Subject: Add a 'smart sync' option to repo sync This option allows the user to specify a manifest server to use when syncing. This manifest server will provide a manifest pegging each project to a known green build. This allows developers to work on a known good tree that is known to build and pass tests, preventing failed builds to hamper productivity. The manifest used is not "sticky" so as to allow subsequent 'repo sync' calls to sync to the tip of the tree. Change-Id: Id0a24ece20f5a88034ad364b416a1dd2e394226d --- subcmds/sync.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) (limited to 'subcmds/sync.py') diff --git a/subcmds/sync.py b/subcmds/sync.py index ceb81eaa..deff171a 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -17,9 +17,11 @@ from optparse import SUPPRESS_HELP import os import re import shutil +import socket import subprocess import sys import time +import xmlrpclib from git_command import GIT from project import HEAD @@ -57,6 +59,10 @@ back to the manifest revision. This option is especially helpful if the project is currently on a topic branch, but the manifest revision is temporarily needed. +The -s/--smart-sync option can be used to sync to a known good +build as specified by the manifest-server element in the current +manifest. + SSH Connections --------------- @@ -97,6 +103,9 @@ later is required to fix a server side protocol bug. p.add_option('-d','--detach', dest='detach_head', action='store_true', help='detach projects back to manifest revision') + p.add_option('-s', '--smart-sync', + dest='smart_sync', action='store_true', + help='smart sync using manifest from a known good build') g = p.add_option_group('repo Version options') g.add_option('--no-repo-verify', @@ -182,6 +191,49 @@ uncommitted changes are present' % project.relpath print >>sys.stderr, 'error: cannot combine -n and -l' sys.exit(1) + if opt.smart_sync: + if not self.manifest.manifest_server: + print >>sys.stderr, \ + 'error: cannot smart sync: no manifest server defined in manifest' + sys.exit(1) + try: + server = xmlrpclib.Server(self.manifest.manifest_server) + p = self.manifest.manifestProject + b = p.GetBranch(p.CurrentBranch) + branch = b.merge + + env = dict(os.environ) + if (env.has_key('TARGET_PRODUCT') and + env.has_key('TARGET_BUILD_VARIANT')): + target = '%s-%s' % (env['TARGET_PRODUCT'], + env['TARGET_BUILD_VARIANT']) + [success, manifest_str] = server.GetApprovedManifest(branch, target) + else: + [success, manifest_str] = server.GetApprovedManifest(branch) + + if success: + manifest_name = "smart_sync_override.xml" + manifest_path = os.path.join(self.manifest.manifestProject.worktree, + manifest_name) + try: + f = open(manifest_path, 'w') + try: + f.write(manifest_str) + self.manifest.Override(manifest_name) + finally: + f.close() + except IOError: + print >>sys.stderr, 'error: cannot write manifest to %s' % \ + manifest_path + sys.exit(1) + else: + print >>sys.stderr, 'error: %s' % manifest_str + sys.exit(1) + except socket.error: + print >>sys.stderr, 'error: cannot connect to manifest server %s' % ( + self.manifest.manifest_server) + sys.exit(1) + rp = self.manifest.repoProject rp.PreSync() -- cgit v1.2.3-54-g00ecf From f3fdf823cf9785e4ceca3e8416b719282d84b6d0 Mon Sep 17 00:00:00 2001 From: Anthony Date: Sat, 26 Sep 2009 13:38:52 -0400 Subject: sync: Safely skip already deleted projects Do not error if a project is missing on the filesystem, is deleted from manifest.xml, but still exists in project.list. Change-Id: I1d13e435473c83091e27e4df571504ef493282dd --- subcmds/sync.py | 54 +++++++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 25 deletions(-) (limited to 'subcmds/sync.py') diff --git a/subcmds/sync.py b/subcmds/sync.py index deff171a..02dd82df 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -147,32 +147,36 @@ later is required to fix a server side protocol bug. if not path: continue if path not in new_project_paths: - project = Project( - manifest = self.manifest, - name = path, - remote = RemoteSpec('origin'), - gitdir = os.path.join(self.manifest.topdir, - path, '.git'), - worktree = os.path.join(self.manifest.topdir, path), - relpath = path, - revisionExpr = 'HEAD', - revisionId = None) - if project.IsDirty(): - print >>sys.stderr, 'error: Cannot remove project "%s": \ + """If the path has already been deleted, we don't need to do it + """ + if os.path.exists(self.manifest.topdir + '/' + path): + project = Project( + manifest = self.manifest, + name = path, + remote = RemoteSpec('origin'), + gitdir = os.path.join(self.manifest.topdir, + path, '.git'), + worktree = os.path.join(self.manifest.topdir, path), + relpath = path, + revisionExpr = 'HEAD', + revisionId = None) + + if project.IsDirty(): + print >>sys.stderr, 'error: Cannot remove project "%s": \ uncommitted changes are present' % project.relpath - print >>sys.stderr, ' commit changes, then run sync again' - return -1 - else: - print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree - shutil.rmtree(project.worktree) - # Try deleting parent subdirs if they are empty - dir = os.path.dirname(project.worktree) - while dir != self.manifest.topdir: - try: - os.rmdir(dir) - except OSError: - break - dir = os.path.dirname(dir) + print >>sys.stderr, ' commit changes, then run sync again' + return -1 + else: + print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree + shutil.rmtree(project.worktree) + # Try deleting parent subdirs if they are empty + dir = os.path.dirname(project.worktree) + while dir != self.manifest.topdir: + try: + os.rmdir(dir) + except OSError: + break + dir = os.path.dirname(dir) new_project_paths.sort() fd = open(file_path, 'w') -- cgit v1.2.3-54-g00ecf From 5732e47ebb7a096e3afad49687098c4181c4b300 Mon Sep 17 00:00:00 2001 From: Nico Sallembien Date: Mon, 26 Apr 2010 11:17:29 -0700 Subject: Strip refs/heads in the branch sent to the manifest server. The manifest server doesn't want to have refs/heads passed to it, so we need to strip that when the branch contains it. Change-Id: I044f8a9629220e886fd5e02e3c1ac4b4bb6020ba --- subcmds/sync.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'subcmds/sync.py') diff --git a/subcmds/sync.py b/subcmds/sync.py index 02dd82df..67213d3a 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -24,6 +24,7 @@ import time import xmlrpclib from git_command import GIT +from git_refs import R_HEADS from project import HEAD from project import Project from project import RemoteSpec @@ -205,6 +206,8 @@ uncommitted changes are present' % project.relpath p = self.manifest.manifestProject b = p.GetBranch(p.CurrentBranch) branch = b.merge + if branch.startswith(R_HEADS): + branch = branch[len(R_HEADS):] env = dict(os.environ) if (env.has_key('TARGET_PRODUCT') and -- cgit v1.2.3-54-g00ecf From 719965af35a2fab96cb578c8a19a48a2cf9fe8e8 Mon Sep 17 00:00:00 2001 From: Nico Sallembien Date: Tue, 20 Apr 2010 15:28:19 -0700 Subject: Override manifest file only after it is fully written to disk. We called "Override()" before closing the file passed in argument. Change-Id: I15adb99deb14297ef72fcb1b0945eb246f172fb0 --- subcmds/sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'subcmds/sync.py') diff --git a/subcmds/sync.py b/subcmds/sync.py index 67213d3a..613cc81c 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -226,13 +226,13 @@ uncommitted changes are present' % project.relpath f = open(manifest_path, 'w') try: f.write(manifest_str) - self.manifest.Override(manifest_name) finally: f.close() except IOError: print >>sys.stderr, 'error: cannot write manifest to %s' % \ manifest_path sys.exit(1) + self.manifest.Override(manifest_name) else: print >>sys.stderr, 'error: %s' % manifest_str sys.exit(1) -- cgit v1.2.3-54-g00ecf From 6623b21e1073a70f7d5cc6eddd364bdab337b439 Mon Sep 17 00:00:00 2001 From: Nico Sallembien Date: Tue, 11 May 2010 12:57:01 -0700 Subject: Aliasing sync -s to 'smartsync' This alias will let people use this command without having to remember the option. Change-Id: I3256d9e8e884c5be9e77f70e9cfb73e0f0c544c6 --- subcmds/smartsync.py | 33 +++++++++++++++++++++++++++++++++ subcmds/sync.py | 9 +++++---- 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 subcmds/smartsync.py (limited to 'subcmds/sync.py') diff --git a/subcmds/smartsync.py b/subcmds/smartsync.py new file mode 100644 index 00000000..1edbd35b --- /dev/null +++ b/subcmds/smartsync.py @@ -0,0 +1,33 @@ +# +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sync import Sync + +class Smartsync(Sync): + common = True + helpSummary = "Update working tree to the latest known good revision" + helpUsage = """ +%prog [...] +""" + helpDescription = """ +The '%prog' command is a shortcut for sync -s. +""" + + def _Options(self, p): + Sync._Options(self, p, show_smart=False) + + def Execute(self, opt, args): + opt.smart_sync = True + Sync.Execute(self, opt, args) diff --git a/subcmds/sync.py b/subcmds/sync.py index 613cc81c..9b8a6122 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -94,7 +94,7 @@ later is required to fix a server side protocol bug. """ - def _Options(self, p): + def _Options(self, p, show_smart=True): p.add_option('-l','--local-only', dest='local_only', action='store_true', help="only update working tree, don't fetch") @@ -104,9 +104,10 @@ later is required to fix a server side protocol bug. p.add_option('-d','--detach', dest='detach_head', action='store_true', help='detach projects back to manifest revision') - p.add_option('-s', '--smart-sync', - dest='smart_sync', action='store_true', - help='smart sync using manifest from a known good build') + if show_smart: + p.add_option('-s', '--smart-sync', + dest='smart_sync', action='store_true', + help='smart sync using manifest from a known good build') g = p.add_option_group('repo Version options') g.add_option('--no-repo-verify', -- cgit v1.2.3-54-g00ecf From 18afd7f679ab6271a34f4ec01e7755dd85c5dcf4 Mon Sep 17 00:00:00 2001 From: Roy Lee Date: Sun, 9 May 2010 04:32:08 +0800 Subject: sync: support --jobs to fetch projects simultaneously This patch does two things for being compatibile with those Python which are built without threading support: 1. As the Python document and Shawn suggested, import dummy_threading when the threading is not available. 2. Reserve the single threaded code and make it default. In cases the --jobs does not work properly with dummy_threading, we still have a safe fallback. Change-Id: I40909ef8e9b5c22f315c0a1da9be38eed8b0a2dc --- subcmds/sync.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) (limited to 'subcmds/sync.py') diff --git a/subcmds/sync.py b/subcmds/sync.py index 9b8a6122..6cac2e52 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -23,6 +23,11 @@ import sys import time import xmlrpclib +try: + import threading as _threading +except ImportError: + import dummy_threading as _threading + from git_command import GIT from git_refs import R_HEADS from project import HEAD @@ -35,6 +40,7 @@ from project import SyncBuffer from progress import Progress class Sync(Command, MirrorSafeCommand): + jobs = 1 common = True helpSummary = "Update working tree to the latest revision" helpUsage = """ @@ -104,6 +110,9 @@ later is required to fix a server side protocol bug. p.add_option('-d','--detach', dest='detach_head', action='store_true', help='detach projects back to manifest revision') + p.add_option('-j','--jobs', + dest='jobs', action='store', type='int', + help="number of projects to fetch simultaneously") if show_smart: p.add_option('-s', '--smart-sync', dest='smart_sync', action='store_true', @@ -117,16 +126,44 @@ later is required to fix a server side protocol bug. dest='repo_upgraded', action='store_true', help=SUPPRESS_HELP) + def _FetchHelper(self, project, lock, fetched, pm, sem): + if not project.Sync_NetworkHalf(): + print >>sys.stderr, 'error: Cannot fetch %s' % project.name + sem.release() + sys.exit(1) + + lock.acquire() + fetched.add(project.gitdir) + pm.update() + lock.release() + sem.release() + def _Fetch(self, projects): fetched = set() pm = Progress('Fetching projects', len(projects)) - for project in projects: - pm.update() - if project.Sync_NetworkHalf(): - fetched.add(project.gitdir) - else: - print >>sys.stderr, 'error: Cannot fetch %s' % project.name - sys.exit(1) + + if self.jobs == 1: + for project in projects: + pm.update() + if project.Sync_NetworkHalf(): + fetched.add(project.gitdir) + else: + print >>sys.stderr, 'error: Cannot fetch %s' % project.name + sys.exit(1) + else: + threads = set() + lock = _threading.Lock() + sem = _threading.Semaphore(self.jobs) + for project in projects: + sem.acquire() + t = _threading.Thread(target = self._FetchHelper, + args = (project, lock, fetched, pm, sem)) + threads.add(t) + t.start() + + for t in threads: + t.join() + pm.end() return fetched @@ -190,6 +227,8 @@ uncommitted changes are present' % project.relpath return 0 def Execute(self, opt, args): + if opt.jobs: + self.jobs = opt.jobs if opt.network_only and opt.detach_head: print >>sys.stderr, 'error: cannot combine -n and -d' sys.exit(1) -- cgit v1.2.3-54-g00ecf From 16614f86b3cc8d61ccae7197624fa93fc752767b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 29 Oct 2010 12:05:43 -0700 Subject: sync --quiet: be more quiet Change-Id: I5e8363c7b32e4546d1236cfc5a32e01c3e5ea8e6 Signed-off-by: Shawn O. Pearce --- project.py | 17 +++++++++++------ subcmds/sync.py | 24 ++++++++++++++++-------- 2 files changed, 27 insertions(+), 14 deletions(-) (limited to 'subcmds/sync.py') diff --git a/project.py b/project.py index 8ffed842..ce85b863 100644 --- a/project.py +++ b/project.py @@ -618,18 +618,19 @@ class Project(object): ## Sync ## - def Sync_NetworkHalf(self): + def Sync_NetworkHalf(self, quiet=False): """Perform only the network IO portion of the sync process. Local working directory/branch state is not affected. """ is_new = not self.Exists if is_new: - print >>sys.stderr - print >>sys.stderr, 'Initializing project %s ...' % self.name + if not quiet: + print >>sys.stderr + print >>sys.stderr, 'Initializing project %s ...' % self.name self._InitGitDir() self._InitRemote() - if not self._RemoteFetch(initial = is_new): + if not self._RemoteFetch(initial=is_new, quiet=quiet): return False #Check that the requested ref was found after fetch @@ -642,7 +643,7 @@ class Project(object): # rev = self.revisionExpr if rev.startswith(R_TAGS): - self._RemoteFetch(None, rev[len(R_TAGS):]) + self._RemoteFetch(None, rev[len(R_TAGS):], quiet=quiet) if self.worktree: self._InitMRef() @@ -1025,7 +1026,9 @@ class Project(object): ## Direct Git Commands ## - def _RemoteFetch(self, name=None, tag=None, initial=False): + def _RemoteFetch(self, name=None, tag=None, + initial=False, + quiet=False): if not name: name = self.remote.name @@ -1088,6 +1091,8 @@ class Project(object): ref_dir = None cmd = ['fetch'] + if quiet: + cmd.append('--quiet') if not self.worktree: cmd.append('--update-head-ok') cmd.append(name) diff --git a/subcmds/sync.py b/subcmds/sync.py index 6cac2e52..1f4b137f 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -110,6 +110,9 @@ later is required to fix a server side protocol bug. p.add_option('-d','--detach', dest='detach_head', action='store_true', help='detach projects back to manifest revision') + p.add_option('-q','--quiet', + dest='quiet', action='store_true', + help='be more quiet') p.add_option('-j','--jobs', dest='jobs', action='store', type='int', help="number of projects to fetch simultaneously") @@ -126,8 +129,8 @@ later is required to fix a server side protocol bug. dest='repo_upgraded', action='store_true', help=SUPPRESS_HELP) - def _FetchHelper(self, project, lock, fetched, pm, sem): - if not project.Sync_NetworkHalf(): + def _FetchHelper(self, opt, project, lock, fetched, pm, sem): + if not project.Sync_NetworkHalf(quiet=opt.quiet): print >>sys.stderr, 'error: Cannot fetch %s' % project.name sem.release() sys.exit(1) @@ -138,14 +141,14 @@ later is required to fix a server side protocol bug. lock.release() sem.release() - def _Fetch(self, projects): + def _Fetch(self, projects, opt): fetched = set() pm = Progress('Fetching projects', len(projects)) if self.jobs == 1: for project in projects: pm.update() - if project.Sync_NetworkHalf(): + if project.Sync_NetworkHalf(quiet=opt.quiet): fetched.add(project.gitdir) else: print >>sys.stderr, 'error: Cannot fetch %s' % project.name @@ -157,7 +160,12 @@ later is required to fix a server side protocol bug. for project in projects: sem.acquire() t = _threading.Thread(target = self._FetchHelper, - args = (project, lock, fetched, pm, sem)) + args = (opt, + project, + lock, + fetched, + pm, + sem)) threads.add(t) t.start() @@ -291,7 +299,7 @@ uncommitted changes are present' % project.relpath _PostRepoUpgrade(self.manifest) if not opt.local_only: - mp.Sync_NetworkHalf() + mp.Sync_NetworkHalf(quiet=opt.quiet) if mp.HasChanges: syncbuf = SyncBuffer(mp.config) @@ -308,7 +316,7 @@ uncommitted changes are present' % project.relpath to_fetch.append(rp) to_fetch.extend(all) - fetched = self._Fetch(to_fetch) + fetched = self._Fetch(to_fetch, opt) _PostRepoFetch(rp, opt.no_repo_verify) if opt.network_only: # bail out now; the rest touches the working tree @@ -320,7 +328,7 @@ uncommitted changes are present' % project.relpath for project in all: if project.gitdir not in fetched: missing.append(project) - self._Fetch(missing) + self._Fetch(missing, opt) if self.manifest.IsMirror: # bail out now, we have no working tree -- cgit v1.2.3-54-g00ecf From 5df6de075e5fb674368d38f858419425bc8d8d07 Mon Sep 17 00:00:00 2001 From: Andrei Warkentin Date: Fri, 2 Jul 2010 17:58:31 -0500 Subject: sync: Use --force-broken to continue other projects This adds a new flag -f/--force-broken that will allow the rest of the sync process to continue instead of bailing when a particular project fails to sync. Change-Id: I23680f2ee7927410f7ed930b1d469424c9aa246e Signed-off-by: Andrei Warkentin Signed-off-by: Shawn O. Pearce --- subcmds/sync.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'subcmds/sync.py') diff --git a/subcmds/sync.py b/subcmds/sync.py index 1f4b137f..ca78467b 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -70,6 +70,9 @@ The -s/--smart-sync option can be used to sync to a known good build as specified by the manifest-server element in the current manifest. +The -f/--force-broken option can be used to proceed with syncing +other projects if a project sync fails. + SSH Connections --------------- @@ -101,6 +104,9 @@ later is required to fix a server side protocol bug. """ def _Options(self, p, show_smart=True): + p.add_option('-f', '--force-broken', + dest='force_broken', action='store_true', + help="continue sync even if a project fails to sync") p.add_option('-l','--local-only', dest='local_only', action='store_true', help="only update working tree, don't fetch") @@ -132,8 +138,11 @@ later is required to fix a server side protocol bug. def _FetchHelper(self, opt, project, lock, fetched, pm, sem): if not project.Sync_NetworkHalf(quiet=opt.quiet): print >>sys.stderr, 'error: Cannot fetch %s' % project.name - sem.release() - sys.exit(1) + if opt.force_broken: + print >>sys.stderr, 'warn: --force-broken, continuing to sync' + else: + sem.release() + sys.exit(1) lock.acquire() fetched.add(project.gitdir) @@ -152,7 +161,10 @@ later is required to fix a server side protocol bug. fetched.add(project.gitdir) else: print >>sys.stderr, 'error: Cannot fetch %s' % project.name - sys.exit(1) + if opt.force_broken: + print >>sys.stderr, 'warn: --force-broken, continuing to sync' + else: + sys.exit(1) else: threads = set() lock = _threading.Lock() -- cgit v1.2.3-54-g00ecf From 2b8db3ce3e7344b9f3b5216637c5af0d54be5656 Mon Sep 17 00:00:00 2001 From: Doug Anderson Date: Mon, 1 Nov 2010 15:08:06 -0700 Subject: Added feature to print a from manifest at the end of a sync. This feature is used to convey information on a when a branch has ceased development or if it is an experimental branch with a few gotchas, etc. You add it to your manifest XML by doing something like this: NOTE TO DEVELOPERS: If you checkin code, you have to pinky-swear that it contains no bugs. Anyone who breaks their promise will have tomatoes thrown at them in the team meeting. Be sure to bring an extra set of clothes. ... Carriage returns and indentation are relevant for the text in this tag. This feature was requested by Anush Elangovan on the ChromiumOS team. --- docs/manifest-format.txt | 5 +++- manifest_xml.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ subcmds/sync.py | 5 ++++ 3 files changed, 71 insertions(+), 1 deletion(-) (limited to 'subcmds/sync.py') diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt index 211344ee..2e1c8c35 100644 --- a/docs/manifest-format.txt +++ b/docs/manifest-format.txt @@ -20,12 +20,15 @@ A manifest XML file (e.g. 'default.xml') roughly conforms to the following DTD: + + diff --git a/manifest_xml.py b/manifest_xml.py index d0c9debe..9d68f09f 100644 --- a/manifest_xml.py +++ b/manifest_xml.py @@ -107,6 +107,15 @@ class XmlManifest(object): root = doc.createElement('manifest') doc.appendChild(root) + # Save out the notice. There's a little bit of work here to give it the + # right whitespace, which assumes that the notice is automatically indented + # by 4 by minidom. + if self.notice: + notice_element = root.appendChild(doc.createElement('notice')) + notice_lines = self.notice.splitlines() + indented_notice = ('\n'.join(" "*4 + line for line in notice_lines))[4:] + notice_element.appendChild(doc.createTextNode(indented_notice)) + d = self.default sort_remotes = list(self.remotes.keys()) sort_remotes.sort() @@ -179,6 +188,11 @@ class XmlManifest(object): self._Load() return self._default + @property + def notice(self): + self._Load() + return self._notice + @property def manifest_server(self): self._Load() @@ -193,6 +207,7 @@ class XmlManifest(object): self._projects = {} self._remotes = {} self._default = None + self._notice = None self.branch = None self._manifest_server = None @@ -263,6 +278,14 @@ class XmlManifest(object): if self._default is None: self._default = _Default() + for node in config.childNodes: + if node.nodeName == 'notice': + if self._notice is not None: + raise ManifestParseError, \ + 'duplicate notice in %s' % \ + (self.manifestFile) + self._notice = self._ParseNotice(node) + for node in config.childNodes: if node.nodeName == 'manifest-server': url = self._reqatt(node, 'url') @@ -338,6 +361,45 @@ class XmlManifest(object): d.revisionExpr = None return d + def _ParseNotice(self, node): + """ + reads a element from the manifest file + + The element is distinct from other tags in the XML in that the + data is conveyed between the start and end tag (it's not an empty-element + tag). + + The white space (carriage returns, indentation) for the notice element is + relevant and is parsed in a way that is based on how python docstrings work. + In fact, the code is remarkably similar to here: + http://www.python.org/dev/peps/pep-0257/ + """ + # Get the data out of the node... + notice = node.childNodes[0].data + + # Figure out minimum indentation, skipping the first line (the same line + # as the tag)... + minIndent = sys.maxint + lines = notice.splitlines() + for line in lines[1:]: + lstrippedLine = line.lstrip() + if lstrippedLine: + indent = len(line) - len(lstrippedLine) + minIndent = min(indent, minIndent) + + # Strip leading / trailing blank lines and also indentation. + cleanLines = [lines[0].strip()] + for line in lines[1:]: + cleanLines.append(line[minIndent:].rstrip()) + + # Clear completely blank lines from front and back... + while cleanLines and not cleanLines[0]: + del cleanLines[0] + while cleanLines and not cleanLines[-1]: + del cleanLines[-1] + + return '\n'.join(cleanLines) + def _ParseProject(self, node): """ reads a element from the manifest file diff --git a/subcmds/sync.py b/subcmds/sync.py index ca78467b..d6ea442a 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -361,6 +361,11 @@ uncommitted changes are present' % project.relpath if not syncbuf.Finish(): sys.exit(1) + # If there's a notice that's supposed to print at the end of the sync, print + # it now... + if self.manifest.notice: + print self.manifest.notice + def _PostRepoUpgrade(manifest): for project in manifest.projects.values(): if project.Exists: -- cgit v1.2.3-54-g00ecf