Compare commits

...

2 Commits

Author SHA1 Message Date
f3fdf823cf 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
2010-04-14 14:21:50 -07:00
a1bfd2cd72 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
2010-04-13 10:20:37 -07:00
3 changed files with 135 additions and 28 deletions

View File

@ -22,6 +22,7 @@ following DTD:
<!DOCTYPE manifest [ <!DOCTYPE manifest [
<!ELEMENT manifest (remote*, <!ELEMENT manifest (remote*,
default?, default?,
manifest-server?,
remove-project*, remove-project*,
project*)> project*)>
@ -34,6 +35,9 @@ following DTD:
<!ATTLIST default remote IDREF #IMPLIED> <!ATTLIST default remote IDREF #IMPLIED>
<!ATTLIST default revision CDATA #IMPLIED> <!ATTLIST default revision CDATA #IMPLIED>
<!ELEMENT manifest-server (EMPTY)>
<!ATTLIST url CDATA #REQUIRED>
<!ELEMENT project (EMPTY)> <!ELEMENT project (EMPTY)>
<!ATTLIST project name CDATA #REQUIRED> <!ATTLIST project name CDATA #REQUIRED>
<!ATTLIST project path CDATA #IMPLIED> <!ATTLIST project path CDATA #IMPLIED>
@ -89,6 +93,27 @@ Attribute `revision`: Name of a Git branch (e.g. `master` or
revision attribute will use this revision. revision attribute will use this revision.
Element manifest-server
-----------------------
At most one manifest-server may be specified. The url attribute
is used to specify the URL of a manifest server, which is an
XML RPC service that will return a manifest in which each project
is pegged to a known good revision for the current branch and
target.
The manifest server should implement:
GetApprovedManifest(branch, target)
The target to use is defined by environment variables TARGET_PRODUCT
and TARGET_BUILD_VARIANT. These variables are used to create a string
of the form $TARGET_PRODUCT-$TARGET_BUILD_VARIANT, e.g. passion-userdebug.
If one of those variables or both are not present, the program will call
GetApprovedManifest without the target paramater and the manifest server
should choose a reasonable default target.
Element project Element project
--------------- ---------------

View File

@ -65,8 +65,8 @@ class XmlManifest(object):
self._Unload() self._Unload()
def Link(self, name): def Override(self, name):
"""Update the repo metadata to use a different manifest. """Use a different manifest, just for the current instantiation.
""" """
path = os.path.join(self.manifestProject.worktree, name) path = os.path.join(self.manifestProject.worktree, name)
if not os.path.isfile(path): if not os.path.isfile(path):
@ -80,6 +80,11 @@ class XmlManifest(object):
finally: finally:
self.manifestFile = old self.manifestFile = old
def Link(self, name):
"""Update the repo metadata to use a different manifest.
"""
self.Override(name)
try: try:
if os.path.exists(self.manifestFile): if os.path.exists(self.manifestFile):
os.remove(self.manifestFile) os.remove(self.manifestFile)
@ -123,6 +128,12 @@ class XmlManifest(object):
root.appendChild(e) root.appendChild(e)
root.appendChild(doc.createTextNode('')) root.appendChild(doc.createTextNode(''))
if self._manifest_server:
e = doc.createElement('manifest-server')
e.setAttribute('url', self._manifest_server)
root.appendChild(e)
root.appendChild(doc.createTextNode(''))
sort_projects = list(self.projects.keys()) sort_projects = list(self.projects.keys())
sort_projects.sort() sort_projects.sort()
@ -168,6 +179,11 @@ class XmlManifest(object):
self._Load() self._Load()
return self._default return self._default
@property
def manifest_server(self):
self._Load()
return self._manifest_server
@property @property
def IsMirror(self): def IsMirror(self):
return self.manifestProject.config.GetBoolean('repo.mirror') return self.manifestProject.config.GetBoolean('repo.mirror')
@ -178,6 +194,7 @@ class XmlManifest(object):
self._remotes = {} self._remotes = {}
self._default = None self._default = None
self.branch = None self.branch = None
self._manifest_server = None
def _Load(self): def _Load(self):
if not self._loaded: if not self._loaded:
@ -246,6 +263,15 @@ class XmlManifest(object):
if self._default is None: if self._default is None:
self._default = _Default() self._default = _Default()
for node in config.childNodes:
if node.nodeName == 'manifest-server':
url = self._reqatt(node, 'url')
if self._manifest_server is not None:
raise ManifestParseError, \
'duplicate manifest-server in %s' % \
(self.manifestFile)
self._manifest_server = url
for node in config.childNodes: for node in config.childNodes:
if node.nodeName == 'project': if node.nodeName == 'project':
project = self._ParseProject(node) project = self._ParseProject(node)

View File

@ -17,9 +17,11 @@ from optparse import SUPPRESS_HELP
import os import os
import re import re
import shutil import shutil
import socket
import subprocess import subprocess
import sys import sys
import time import time
import xmlrpclib
from git_command import GIT from git_command import GIT
from project import HEAD 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 if the project is currently on a topic branch, but the manifest
revision is temporarily needed. 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 SSH Connections
--------------- ---------------
@ -97,6 +103,9 @@ later is required to fix a server side protocol bug.
p.add_option('-d','--detach', p.add_option('-d','--detach',
dest='detach_head', action='store_true', dest='detach_head', action='store_true',
help='detach projects back to manifest revision') 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 = p.add_option_group('repo Version options')
g.add_option('--no-repo-verify', g.add_option('--no-repo-verify',
@ -138,32 +147,36 @@ later is required to fix a server side protocol bug.
if not path: if not path:
continue continue
if path not in new_project_paths: if path not in new_project_paths:
project = Project( """If the path has already been deleted, we don't need to do it
manifest = self.manifest, """
name = path, if os.path.exists(self.manifest.topdir + '/' + path):
remote = RemoteSpec('origin'), project = Project(
gitdir = os.path.join(self.manifest.topdir, manifest = self.manifest,
path, '.git'), name = path,
worktree = os.path.join(self.manifest.topdir, path), remote = RemoteSpec('origin'),
relpath = path, gitdir = os.path.join(self.manifest.topdir,
revisionExpr = 'HEAD', path, '.git'),
revisionId = None) worktree = os.path.join(self.manifest.topdir, path),
if project.IsDirty(): relpath = path,
print >>sys.stderr, 'error: Cannot remove project "%s": \ revisionExpr = 'HEAD',
revisionId = None)
if project.IsDirty():
print >>sys.stderr, 'error: Cannot remove project "%s": \
uncommitted changes are present' % project.relpath uncommitted changes are present' % project.relpath
print >>sys.stderr, ' commit changes, then run sync again' print >>sys.stderr, ' commit changes, then run sync again'
return -1 return -1
else: else:
print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
shutil.rmtree(project.worktree) shutil.rmtree(project.worktree)
# Try deleting parent subdirs if they are empty # Try deleting parent subdirs if they are empty
dir = os.path.dirname(project.worktree) dir = os.path.dirname(project.worktree)
while dir != self.manifest.topdir: while dir != self.manifest.topdir:
try: try:
os.rmdir(dir) os.rmdir(dir)
except OSError: except OSError:
break break
dir = os.path.dirname(dir) dir = os.path.dirname(dir)
new_project_paths.sort() new_project_paths.sort()
fd = open(file_path, 'w') fd = open(file_path, 'w')
@ -182,6 +195,49 @@ uncommitted changes are present' % project.relpath
print >>sys.stderr, 'error: cannot combine -n and -l' print >>sys.stderr, 'error: cannot combine -n and -l'
sys.exit(1) 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 = self.manifest.repoProject
rp.PreSync() rp.PreSync()