Delay initialization of local repos

Now that live reconfiguration is an option, we sometimes load the
Zuul config before the remote repo is created.  To handle that,
gracefully handle that case when the Repo object is created by the
Merger.  Keep track of whether the initial clone has happened, and
check for that before every local Repo operation.  If we get an
event that involves a repo before it exists (unlikely!) that will
still error and raise an exception (that should be caught higher
up in the stack).

Add a test for this case.

In the test suite, when adding a fake change to the upstream repo,
always reset it so that HEAD is master so that new repos clone
from a consistent state.

Also remove an errant assertEmptyQueues because it's handled by
assertFinalState.

Change-Id: Ic6eec83e3faa2a15be4b23d4cfcfbddcac82983c
This commit is contained in:
James E. Blair 2013-07-24 10:39:30 -07:00
parent c28d1b0d19
commit 287c06dca6
3 changed files with 97 additions and 10 deletions

View File

@ -0,0 +1,44 @@
pipelines:
- name: check
manager: IndependentPipelineManager
trigger:
- event: patchset-created
success:
verified: 1
failure:
verified: -1
- name: post
manager: IndependentPipelineManager
trigger:
- event: ref-updated
ref: ^(?!refs/).*$
- name: gate
manager: DependentPipelineManager
failure-message: Build failed. For information on how to proceed, see http://wiki.example.org/Test_Failures
trigger:
- event: comment-added
approval:
- approved: 1
success:
verified: 2
submit: true
failure:
verified: -2
start:
verified: 0
precedence: high
projects:
- name: org/new-project
check:
- project-merge:
- project-test1
- project-test2
gate:
- project-merge:
- project-test1
- project-test2
post:
- project-post

View File

@ -148,7 +148,11 @@ class FakeChange(object):
f.close()
repo.index.add([fn])
return repo.index.commit(msg)
r = repo.index.commit(msg)
repo.head.reference = 'master'
repo.head.reset(index=True, working_tree=True)
repo.git.clean('-x', '-f', '-d')
return r
def addPatchset(self, files=[], large=False):
self.latest_patchset += 1
@ -2445,4 +2449,23 @@ class TestScheduler(testtools.TestCase):
'SUCCESS')
self.assertEqual(A.data['status'], 'MERGED')
self.assertEqual(A.reported, 2)
self.assertEmptyQueues()
def test_delayed_repo_init(self):
self.config.set('zuul', 'layout_config',
'tests/fixtures/layout-delayed-repo-init.yaml')
self.sched.reconfigure(self.config)
self.init_repo("org/new-project")
A = self.fake_gerrit.addFakeChange('org/new-project', 'master', 'A')
A.addApproval('CRVW', 2)
self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
self.waitUntilSettled()
self.assertEqual(self.getJobFromHistory('project-merge').result,
'SUCCESS')
self.assertEqual(self.getJobFromHistory('project-test1').result,
'SUCCESS')
self.assertEqual(self.getJobFromHistory('project-test2').result,
'SUCCESS')
self.assertEqual(A.data['status'], 'MERGED')
self.assertEqual(A.reported, 2)

View File

@ -29,24 +29,37 @@ class Repo(object):
def __init__(self, remote, local, email, username):
self.remote_url = remote
self.local_path = local
self._ensure_cloned()
self.repo = git.Repo(self.local_path)
if email:
self.repo.config_writer().set_value('user', 'email', email)
if username:
self.repo.config_writer().set_value('user', 'name', username)
self.repo.config_writer().write()
self.email = email
self.username = username
self._initialized = False
try:
self._ensure_cloned()
except:
self.log.exception("Unable to initialize repo for %s" % remote)
def _ensure_cloned(self):
if self._initialized:
return
if not os.path.exists(self.local_path):
self.log.debug("Cloning from %s to %s" % (self.remote_url,
self.local_path))
git.Repo.clone_from(self.remote_url, self.local_path)
self.repo = git.Repo(self.local_path)
if self.email:
self.repo.config_writer().set_value('user', 'email',
self.email)
if self.username:
self.repo.config_writer().set_value('user', 'name',
self.username)
self.repo.config_writer().write()
self._initialized = True
def recreateRepoObject(self):
self._ensure_cloned()
self.repo = git.Repo(self.local_path)
def reset(self):
self._ensure_cloned()
self.log.debug("Resetting repository %s" % self.local_path)
self.update()
origin = self.repo.remotes.origin
@ -64,21 +77,25 @@ class Repo(object):
return self.repo.heads[branch]
def checkout(self, ref):
self._ensure_cloned()
self.log.debug("Checking out %s" % ref)
self.repo.head.reference = ref
self.repo.head.reset(index=True, working_tree=True)
def cherryPick(self, ref):
self._ensure_cloned()
self.log.debug("Cherry-picking %s" % ref)
self.fetch(ref)
self.repo.git.cherry_pick("FETCH_HEAD")
def merge(self, ref):
self._ensure_cloned()
self.log.debug("Merging %s" % ref)
self.fetch(ref)
self.repo.git.merge("FETCH_HEAD")
def fetch(self, ref):
self._ensure_cloned()
# The git.remote.fetch method may read in git progress info and
# interpret it improperly causing an AssertionError. Because the
# data was fetched properly subsequent fetches don't seem to fail.
@ -97,16 +114,19 @@ class Repo(object):
self.repo = git.Repo(self.local_path)
def createZuulRef(self, ref, commit='HEAD'):
self._ensure_cloned()
self.log.debug("CreateZuulRef %s at %s " % (ref, commit))
ref = ZuulReference.create(self.repo, ref, commit)
return ref.commit
def push(self, local, remote):
self._ensure_cloned()
self.log.debug("Pushing %s:%s to %s " % (local, remote,
self.remote_url))
self.repo.remotes.origin.push('%s:%s' % (local, remote))
def update(self):
self._ensure_cloned()
self.log.debug("Updating repository %s" % self.local_path)
origin = self.repo.remotes.origin
origin.update()
@ -151,7 +171,7 @@ class Merger(object):
self.repos[project] = repo
except:
self.log.exception("Unable to initialize repo for %s" % project)
self.log.exception("Unable to add project %s" % project)
def getRepo(self, project):
r = self.repos.get(project, None)