def cloneUpstream(self, project, dest): # Check for a cached git repo first git_cache = '%s/%s' % (self.cache_dir, project) # Then, if we are cloning the repo for the zuul_project, then # set its origin to be the zuul merger, as it is guaranteed to # be correct and up to date even if mirrors haven't updated # yet. Otherwise, we can not be sure about the state of the # project, so our best chance to get the most current state is # by setting origin to the git_url. if (self.zuul_url and project == self.zuul_project): git_upstream = '%s/%s' % (self.zuul_url, project) else: git_upstream = '%s/%s' % (self.git_url, project) repo_is_cloned = os.path.exists(os.path.join(dest, '.git')) if (self.cache_dir and os.path.exists(git_cache) and not repo_is_cloned): # file:// tells git not to hard-link across repos git_cache = 'file://%s' % git_cache self.log.info("Creating repo %s from cache %s", project, git_cache) new_repo = git.Repo.clone_from(git_cache, dest) self.log.info("Updating origin remote in repo %s to %s", project, git_upstream) new_repo.remotes.origin.config_writer.set('url', git_upstream) else: self.log.info("Creating repo %s from upstream %s", project, git_upstream) repo = Repo(remote=git_upstream, local=dest, email=None, username=None) if not repo.isInitialized(): raise Exception("Error cloning %s to %s" % (git_upstream, dest)) return repo
def cloneUpstream(self, project, dest): # Check for a cached git repo first git_cache = '%s/%s' % (self.cache_dir, project) git_upstream = '%s/%s' % (self.git_url, project) if (self.cache_dir and os.path.exists(git_cache) and not os.path.exists(dest)): # file:// tells git not to hard-link across repos git_cache = 'file://%s' % git_cache self.log.info("Creating repo %s from cache %s", project, git_cache) new_repo = git.Repo.clone_from(git_cache, dest) self.log.info("Updating origin remote in repo %s to %s", project, git_upstream) new_repo.remotes.origin.config_writer.set('url', git_upstream) else: self.log.info("Creating repo %s from upstream %s", project, git_upstream) repo = Repo( remote=git_upstream, local=dest, email=None, username=None) if not repo.isInitialized(): raise Exception("Error cloning %s to %s" % (git_upstream, dest)) return repo
def test_files_changes_master_fork_merges(self): """Regression test for getFilesChanges() Check if correct list of changed files is listed for a messy branch that has a merge of a fork, with the fork including a merge of a new master revision. The previously used "git merge-base" approach did not handle this case correctly. """ parent_path = os.path.join(self.upstream_root, 'org/project1') repo = git.Repo(parent_path) self.create_branch('org/project1', 'messy', commit_filename='messy1.txt') # Let time pass to reproduce the order for this error case commit_date = datetime.datetime.now() + datetime.timedelta(seconds=5) commit_date = commit_date.replace(microsecond=0).isoformat() # Create a commit on 'master' so we can merge it into the fork files = {"master.txt": "master"} master_ref = self.create_commit('org/project1', files=files, message="Add master.txt", commit_date=commit_date) repo.refs.master.commit = master_ref # Create a fork of the 'messy' branch and merge # 'master' into the fork (no fast-forward) repo.create_head("messy-fork") repo.heads["messy-fork"].commit = "messy" repo.head.reference = 'messy' repo.head.reset(index=True, working_tree=True) repo.git.checkout('messy-fork') repo.git.merge('master', no_ff=True) # Merge fork back into 'messy' branch (no fast-forward) repo.head.reference = 'messy' repo.head.reset(index=True, working_tree=True) repo.git.checkout('messy') repo.git.merge('messy-fork', no_ff=True) # Create another commit on top of 'messy' files = {"messy2.txt": "messy2"} messy_ref = self.create_commit('org/project1', files=files, head='messy', message="Add messy2.txt") repo.refs.messy.commit = messy_ref # Check that we get all changes for the 'messy' but not 'master' branch work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') changed_files = work_repo.getFilesChanges('messy', 'master') self.assertEqual(sorted(['messy1.txt', 'messy2.txt']), sorted(changed_files))
def test_files_changes(self): parent_path = os.path.join(self.upstream_root, 'org/project1') self.create_branch('org/project1', 'feature') work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') changed_files = work_repo.getFilesChanges('feature', 'master') self.assertEqual(['README'], changed_files)
def test_fetch_timeout(self): parent_path = os.path.join(self.upstream_root, 'org/project1') work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0', retry_attempts=1) work_repo.git_timeout = 0.001 self.patch(git.Git, 'GIT_PYTHON_GIT_EXECUTABLE', os.path.join(FIXTURE_DIR, 'fake_git.sh')) with testtools.ExpectedException(git.exc.GitCommandError, r'.*exit code\(-9\)'): work_repo.update()
def test_broken_cache(self): parent_path = os.path.join(self.upstream_root, 'org/project1') work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') self.waitUntilSettled() # Break the work repo path = work_repo.local_path os.remove(os.path.join(path, '.git/HEAD')) # And now reset the repo again. This should not crash work_repo.reset()
def test_create_head_path(self): parent_path = os.path.join(self.upstream_root, 'org/project1') parent_repo = git.Repo(parent_path) parent_repo.create_head("refs/heads/foobar") parent_repo.create_head("refs/heads/refs/heads/foobar") work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') repo = work_repo.createRepoObject(None) self.assertIn('foobar', repo.branches) self.assertIn('refs/heads/foobar', repo.branches) self.assertNotIn('refs/heads/refs/heads/foobar', repo.branches)
def test_clone_timeout(self): parent_path = os.path.join(self.upstream_root, 'org/project1') self.patch(git.Git, 'GIT_PYTHON_GIT_EXECUTABLE', os.path.join(FIXTURE_DIR, 'fake_git.sh')) work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0', git_timeout=0.001, retry_attempts=1) # TODO: have the merger and repo classes catch fewer # exceptions, including this one on initialization. For the # test, we try cloning again. with testtools.ExpectedException(git.exc.GitCommandError, r'.*exit code\(-9\)'): work_repo._ensure_cloned()
def test_clone_timeout(self): parent_path = os.path.join(self.upstream_root, 'org/project1') self.patch(git.Git, 'GIT_PYTHON_GIT_EXECUTABLE', os.path.join(FIXTURE_DIR, 'fake_git.sh')) work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0', git_timeout=0.001, retry_attempts=1) # TODO: have the merger and repo classes catch fewer # exceptions, including this one on initialization. For the # test, we try cloning again. with testtools.ExpectedException(git.exc.GitCommandError, r'.*exit code\(-9\)'): work_repo._ensure_cloned(None)
def test_set_remote_ref(self): parent_path = os.path.join(self.upstream_root, 'org/project1') commit_sha = self.create_commit('org/project1') self.create_commit('org/project1') work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') work_repo.setRemoteRef('master', commit_sha) work_repo.setRemoteRef('invalid', commit_sha) repo = git.Repo(self.workspace_root) self.assertEqual(repo.remotes.origin.refs.master.commit.hexsha, commit_sha) self.assertNotIn('invalid', repo.remotes.origin.refs)
def test_fetch_retry(self): parent_path = os.path.join(self.upstream_root, 'org/project1') work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0', retry_interval=1) self.patch(git.Git, 'GIT_PYTHON_GIT_EXECUTABLE', os.path.join(FIXTURE_DIR, 'git_fetch_error.sh')) work_repo.update() # This is created on the first fetch self.assertTrue(os.path.exists(os.path.join( self.workspace_root, 'stamp1'))) # This is created on the second fetch self.assertTrue(os.path.exists(os.path.join( self.workspace_root, 'stamp2')))
def cloneUpstream(self, project, dest): git_upstream = '%s/%s' % (self.git_url, project) self.log.info("Creating repo %s from upstream %s", project, git_upstream) repo = Repo( remote=git_upstream, local=dest, email=None, username=None) if not repo.isInitialized(): raise Exception("Error cloning %s to %s" % (git_upstream, dest)) return repo
def test_branch_rename(self): parent_path = os.path.join(self.upstream_root, 'org/project1') # Clone upstream so that current head is master work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') # Rename master to main in upstream repo gitrepo = git.Repo(parent_path) main_branch = gitrepo.create_head('main') gitrepo.head.reference = main_branch gitrepo.delete_head(gitrepo.heads['master'], force=True) # And now reset the repo. This should not crash work_repo.reset()
def test_create_head_at_char(self): """Test that we can create branches containing the '@' char. This is a regression test to make sure we are not using GitPython APIs that interpret the '@' as a special char. """ parent_path = os.path.join(self.upstream_root, 'org/project1') parent_repo = git.Repo(parent_path) parent_repo.create_head("refs/heads/foo@bar") work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') repo = work_repo.createRepoObject(None) self.assertIn('foo@bar', repo.branches)
def test_update_needed(self): parent_path = os.path.join(self.upstream_root, 'org/project1') repo = git.Repo(parent_path) self.create_branch('org/project1', 'stable') repo_state_no_update_master = { 'refs/heads/master': repo.commit('refs/heads/master').hexsha, } repo_state_no_update = { 'refs/heads/master': repo.commit('refs/heads/master').hexsha, 'refs/heads/stable': repo.commit('refs/heads/stable').hexsha, } repo_state_update = { 'refs/heads/master': repo.commit('refs/heads/master').hexsha, 'refs/heads/stable': repo.commit('refs/heads/stable').hexsha, 'refs/heads/test': '1234567', } work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') self.assertFalse(work_repo.isUpdateNeeded(repo_state_no_update_master)) self.assertFalse(work_repo.isUpdateNeeded(repo_state_no_update)) self.assertTrue(work_repo.isUpdateNeeded(repo_state_update)) # Get repo and update for the first time. merger = self.executor_server.merger merger.updateRepo('gerrit', 'org/project1') repo = merger.getRepo('gerrit', 'org/project1') # Branches master and stable must exist self.assertEqual(['master', 'stable'], repo.getBranches()) # Now create an additional branch in the parent repo self.create_branch('org/project1', 'stable2') # Update with repo state and expect no update done self.log.info('Calling updateRepo with repo_state_no_update') merger.updateRepo('gerrit', 'org/project1', repo_state=repo_state_no_update) repo = merger.getRepo('gerrit', 'org/project1') self.assertEqual(['master', 'stable'], repo.getBranches()) # Update with repo state and expect update self.log.info('Calling updateRepo with repo_state_update') merger.updateRepo('gerrit', 'org/project1', repo_state=repo_state_update) repo = merger.getRepo('gerrit', 'org/project1') self.assertEqual(['master', 'stable', 'stable2'], repo.getBranches())
def test_broken_gitmodules(self): parent_path = os.path.join(self.upstream_root, 'org/project1') work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') self.waitUntilSettled() # Break the gitmodules path = work_repo.local_path with open(os.path.join(path, '.gitmodules'), 'w') as f: f.write('[submodule "libfoo"]\n' 'path = include/foo\n' '---\n' 'url = git://example.com/git/lib.git') # And now reset the repo again. This should not crash work_repo.reset()
def test_deleted_local_ref(self): parent_path = os.path.join(self.upstream_root, 'org/project1') self.create_branch('org/project1', 'foobar') work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') # Delete local ref on the cached repo. This leaves us with a remote # ref but no local ref anymore. gitrepo = git.Repo(work_repo.local_path) gitrepo.delete_head('foobar', force=True) # Delete the branch upstream. self.delete_branch('org/project1', 'foobar') # And now reset the repo again. This should not crash work_repo.reset()
def test_broken_cache(self): parent_path = os.path.join(self.upstream_root, 'org/project1') work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') self.waitUntilSettled() # Break the work repo path = work_repo.local_path os.remove(os.path.join(path, '.git/HEAD')) # And now reset the repo again. This should not crash work_repo.reset() # Now open a cache repo and break it in a way that git.Repo is happy # at first but git won't be because of a broken HEAD revision. merger = self.executor_server.merger cache_repo = merger.getRepo('gerrit', 'org/project') with open(os.path.join(cache_repo.local_path, '.git/HEAD'), 'w'): pass cache_repo.update() # Now open a cache repo and break it in a way that git.Repo is happy # at first but git won't be because of a corrupt object file. # # To construct this we create a commit so we have a guaranteed free # object file, then we break it by truncating it. fn = os.path.join(cache_repo.local_path, 'commit_filename') with open(fn, 'a') as f: f.write("test") repo = cache_repo.createRepoObject(None) repo.index.add([fn]) repo.index.commit('test commit') # Pick the first object file we find and break it objects_path = os.path.join(cache_repo.local_path, '.git', 'objects') object_dir = os.path.join(objects_path, [ d for d in os.listdir(objects_path) if len(d) == 2 ][0]) object_to_break = os.path.join(object_dir, os.listdir(object_dir)[0]) self.log.error(os.stat(object_to_break)) os.chmod(object_to_break, 644) with open(object_to_break, 'w'): pass os.chmod(object_to_break, 444) cache_repo.update()
def test_garbage_collect(self): '''Tests that git gc doesn't prune FETCH_HEAD''' parent_path = os.path.join(self.upstream_root, 'org/project1') repo = git.Repo(parent_path) change_ref = 'refs/changes/1/1' self.log.info('Creating a commit on %s', change_ref) repo.head.reference = repo.head.commit files = {"README": "creating fake commit\n"} for name, content in files.items(): file_name = os.path.join(parent_path, name) with open(file_name, 'a') as f: f.write(content) repo.index.add([file_name]) commit = repo.index.commit('Test commit') ref = git.refs.Reference(repo, change_ref) ref.set_commit(commit) self.log.info('Cloning parent repo') work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') self.log.info('Fetch %s', change_ref) work_repo.fetch(change_ref) self.log.info('Checkout master and run garbage collection') work_repo_object = work_repo.createRepoObject(None) work_repo.checkout('master') result = work_repo_object.git.gc('--prune=now') self.log.info(result) self.log.info('Dereferencing FETCH_HEAD') commit = work_repo_object.commit('FETCH_HEAD') self.assertIsNotNone(commit)
def test_ensure_cloned(self): parent_path = os.path.join(self.upstream_root, "org/project1") # Forge a repo having a submodule parent_repo = git.Repo(parent_path) parent_repo.git.submodule("add", os.path.join(self.upstream_root, "org/project2"), "subdir") parent_repo.index.commit("Adding project2 as a submodule in subdir") # git 1.7.8 changed .git from being a directory to a file pointing # to the parent repository /.git/modules/* self.assertTrue( os.path.exists(os.path.join(parent_path, "subdir", ".git")), msg=".git file in submodule should be a file" ) work_repo = Repo(parent_path, self.workspace_root, "*****@*****.**", "User Name") self.assertTrue( os.path.isdir(os.path.join(self.workspace_root, "subdir")), msg="Cloned repository has a submodule placeholder directory", ) self.assertFalse( os.path.exists(os.path.join(self.workspace_root, "subdir", ".git")), msg="Submodule is not initialized" ) sub_repo = Repo( os.path.join(self.upstream_root, "org/project2"), os.path.join(self.workspace_root, "subdir"), "*****@*****.**", "User Name", ) self.assertTrue( os.path.exists(os.path.join(self.workspace_root, "subdir", ".git")), msg="Cloned over the submodule placeholder", ) self.assertEquals( os.path.join(self.upstream_root, "org/project1"), work_repo.createRepoObject().remotes[0].url, message="Parent clone still point to upstream project1", ) self.assertEquals( os.path.join(self.upstream_root, "org/project2"), sub_repo.createRepoObject().remotes[0].url, message="Sub repository points to upstream project2", )
def test_broken_cache(self): parent_path = os.path.join(self.upstream_root, 'org/project1') work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') self.waitUntilSettled() # Break the work repo path = work_repo.local_path os.remove(os.path.join(path, '.git/HEAD')) # And now reset the repo again. This should not crash work_repo.reset() # Now open a cache repo and break it in a way that git.Repo is happy # at first but git won't be. merger = self.executor_server.merger cache_repo = merger.getRepo('gerrit', 'org/project') with open(os.path.join(cache_repo.local_path, '.git/HEAD'), 'w'): pass cache_repo.update()
def test_ensure_cloned(self): parent_path = os.path.join(self.upstream_root, 'org/project1') # Forge a repo having a submodule parent_repo = git.Repo(parent_path) parent_repo.git.submodule('add', os.path.join( self.upstream_root, 'org/project2'), 'subdir') parent_repo.index.commit('Adding project2 as a submodule in subdir') # git 1.7.8 changed .git from being a directory to a file pointing # to the parent repository /.git/modules/* self.assertTrue(os.path.exists( os.path.join(parent_path, 'subdir', '.git')), msg='.git file in submodule should be a file') work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') self.assertTrue( os.path.isdir(os.path.join(self.workspace_root, 'subdir')), msg='Cloned repository has a submodule placeholder directory') self.assertFalse(os.path.exists( os.path.join(self.workspace_root, 'subdir', '.git')), msg='Submodule is not initialized') sub_repo = Repo( os.path.join(self.upstream_root, 'org/project2'), os.path.join(self.workspace_root, 'subdir'), '*****@*****.**', 'User Name', '0', '0') self.assertTrue(os.path.exists( os.path.join(self.workspace_root, 'subdir', '.git')), msg='Cloned over the submodule placeholder') self.assertEqual( os.path.join(self.upstream_root, 'org/project1'), work_repo.createRepoObject().remotes[0].url, message="Parent clone still point to upstream project1") self.assertEqual( os.path.join(self.upstream_root, 'org/project2'), sub_repo.createRepoObject().remotes[0].url, message="Sub repository points to upstream project2")
def cloneUpstream(self, project, dest): # Check for a cached git repo first git_cache = '%s/%s' % (self.cache_dir, project) git_upstream = '%s/%s' % (self.git_url, project) if (self.cache_dir and os.path.exists(git_cache) and not os.path.exists(dest)): # file:// tells git not to hard-link across repos git_cache = 'file://%s' % git_cache self.log.info("Creating repo %s from cache %s", project, git_cache) new_repo = git.Repo.clone_from(git_cache, dest) self.log.info("Updating origin remote in repo %s to %s", project, git_upstream) new_repo.remotes.origin.config_writer.set('url', git_upstream) else: self.log.info("Creating repo %s from upstream %s", project, git_upstream) repo = Repo(remote=git_upstream, local=dest, email=None, username=None) if not repo.isInitialized(): raise Exception("Error cloning %s to %s" % (git_upstream, dest)) return repo
def test_set_refs(self): parent_path = os.path.join(self.upstream_root, 'org/project1') remote_sha = self.create_commit('org/project1') self.create_branch('org/project1', 'foobar') work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') repo = git.Repo(self.workspace_root) new_sha = repo.heads.foobar.commit.hexsha work_repo.setRefs({'refs/heads/master': new_sha}, True) self.assertEqual(work_repo.getBranchHead('master').hexsha, new_sha) self.assertIn('master', repo.remotes.origin.refs) work_repo.setRefs({'refs/heads/master': remote_sha}) self.assertEqual(work_repo.getBranchHead('master').hexsha, remote_sha) self.assertNotIn('master', repo.remotes.origin.refs)
def test_ensure_cloned(self): parent_path = os.path.join(self.upstream_root, 'org/project1') # Forge a repo having a submodule parent_repo = git.Repo(parent_path) parent_repo.git.submodule('add', os.path.join( self.upstream_root, 'org/project2'), 'subdir') parent_repo.index.commit('Adding project2 as a submodule in subdir') # git 1.7.8 changed .git from being a directory to a file pointing # to the parent repository /.git/modules/* self.assertTrue(os.path.exists( os.path.join(parent_path, 'subdir', '.git')), msg='.git file in submodule should be a file') work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') self.assertTrue( os.path.isdir(os.path.join(self.workspace_root, 'subdir')), msg='Cloned repository has a submodule placeholder directory') self.assertFalse(os.path.exists( os.path.join(self.workspace_root, 'subdir', '.git')), msg='Submodule is not initialized') sub_repo = Repo( os.path.join(self.upstream_root, 'org/project2'), os.path.join(self.workspace_root, 'subdir'), '*****@*****.**', 'User Name', '0', '0') self.assertTrue(os.path.exists( os.path.join(self.workspace_root, 'subdir', '.git')), msg='Cloned over the submodule placeholder') self.assertEqual( os.path.join(self.upstream_root, 'org/project1'), work_repo.createRepoObject(None).remotes[0].url, message="Parent clone still point to upstream project1") self.assertEqual( os.path.join(self.upstream_root, 'org/project2'), sub_repo.createRepoObject(None).remotes[0].url, message="Sub repository points to upstream project2")
def test_repo_repr(self): local_path = "/dev/null" repo = Repo("remote", local_path, "*****@*****.**", "User Name", "0", "0") self.assertIn(local_path, repr(repo))
def test_repo_reset_branch_conflict(self): """Test correct reset with conflicting branch names""" parent_path = os.path.join(self.upstream_root, 'org/project1') parent_repo = git.Repo(parent_path) parent_repo.create_head("foobar") work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') # Checkout branch that will be deleted from the remote repo work_repo.checkout("foobar") # Delete remote branch and create a branch that conflicts with # the branch checked out locally. parent_repo.delete_head("foobar") parent_repo.create_head("foobar/sub") work_repo.reset() work_repo.checkout("foobar/sub") # Try the reverse conflict parent_path = os.path.join(self.upstream_root, 'org/project2') parent_repo = git.Repo(parent_path) parent_repo.create_head("foobar/sub") work_repo = Repo(parent_path, self.workspace_root, '*****@*****.**', 'User Name', '0', '0') # Checkout branch that will be deleted from the remote repo work_repo.checkout("foobar/sub") # Delete remote branch and create a branch that conflicts with # the branch checked out locally. parent_repo.delete_head("foobar/sub") # Note: Before git 2.13 deleting a a ref foo/bar leaves an empty # directory foo behind that will block creating the reference foo # in the future. As a workaround we must clean up empty directories # in .git/refs. if parent_repo.git.version_info[:2] < (2, 13): Repo._cleanup_leaked_ref_dirs(parent_path, None, []) parent_repo.create_head("foobar") work_repo.reset() work_repo.checkout("foobar")
def test_merge_temp_refs(self): """ Test that the merge updates local zuul refs in order to avoid garbage collection of needed objects. """ merger = self.executor_server.merger parent_path = os.path.join(self.upstream_root, 'org/project') parent_repo = git.Repo(parent_path) parent_repo.create_head("foo/bar") # Simple change A A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A') item_a = self._item_from_fake_change(A) # Simple change B on branch foo/bar B = self.fake_gerrit.addFakeChange('org/project', 'foo/bar', 'B') item_b = self._item_from_fake_change(B) # Simple change C C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C') item_c = self._item_from_fake_change(C) # Merge A -> B -> C result = merger.mergeChanges([item_a, item_b, item_c]) self.assertIsNotNone(result) merge_state = result[3] cache_repo = merger.getRepo('gerrit', 'org/project') repo = cache_repo.createRepoObject(zuul_event_id="dummy") # Make sure zuul refs are updated foobar_zuul_ref = Repo.refNameToZuulRef("foo/bar") master_zuul_ref = Repo.refNameToZuulRef("master") ref_map = {r.path: r for r in repo.refs} self.assertIn(foobar_zuul_ref, ref_map) self.assertIn(master_zuul_ref, ref_map) self.assertEqual(ref_map[master_zuul_ref].commit.hexsha, merge_state[("gerrit", "org/project", "master")]) self.assertEqual(ref_map[foobar_zuul_ref].commit.hexsha, merge_state[("gerrit", "org/project", "foo/bar")]) # Delete the remote branch so a reset cleanes up the local branch parent_repo.delete_head('foo/bar', force=True) # Note: Before git 2.13 deleting a a ref foo/bar leaves an empty # directory foo behind that will block creating the reference foo # in the future. As a workaround we must clean up empty directories # in .git/refs. if parent_repo.git.version_info[:2] < (2, 13): Repo._cleanup_leaked_ref_dirs(parent_path, None, []) cache_repo.reset() self.assertNotIn(foobar_zuul_ref, [r.path for r in repo.refs]) # Create another head 'foo' that can't be created if the 'foo/bar' # branch wasn't cleaned up properly parent_repo.create_head("foo") # Change B now on branch 'foo' B = self.fake_gerrit.addFakeChange('org/project', 'foo', 'B') item_b = self._item_from_fake_change(B) # Merge A -> B -> C result = merger.mergeChanges([item_a, item_b, item_c]) self.assertIsNotNone(result) merge_state = result[3] foo_zuul_ref = Repo.refNameToZuulRef("foo") ref_map = {r.path: r for r in repo.refs} self.assertIn(foo_zuul_ref, ref_map) self.assertIn(master_zuul_ref, ref_map) self.assertEqual(ref_map[master_zuul_ref].commit.hexsha, merge_state[("gerrit", "org/project", "master")]) self.assertEqual(ref_map[foo_zuul_ref].commit.hexsha, merge_state[("gerrit", "org/project", "foo")])