Merge "Ensure correct cleanup on repo update and reset"
This commit is contained in:
commit
b07251b4f0
|
@ -76,6 +76,55 @@ class TestMergerRepo(ZuulTestCase):
|
|||
sub_repo.createRepoObject(None).remotes[0].url,
|
||||
message="Sub repository points to upstream project2")
|
||||
|
||||
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,
|
||||
'none@example.org', '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,
|
||||
'none@example.org', '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_set_refs(self):
|
||||
parent_path = os.path.join(self.upstream_root, 'org/project1')
|
||||
remote_sha = self.create_commit('org/project1')
|
||||
|
|
|
@ -270,46 +270,60 @@ class Repo(object):
|
|||
self._ensure_cloned(zuul_event_id, build=build)
|
||||
return self._createRepoObject(self.local_path, self.env)
|
||||
|
||||
@staticmethod
|
||||
def _cleanup_leaked_ref_dirs(local_path, log, messages):
|
||||
for root, dirs, files in os.walk(
|
||||
os.path.join(local_path, '.git/refs'), topdown=False):
|
||||
if not os.listdir(root):
|
||||
if log:
|
||||
log.debug("Cleaning empty ref dir %s", root)
|
||||
else:
|
||||
messages.append("Cleaning empty ref dir %s" % root)
|
||||
os.rmdir(root)
|
||||
|
||||
@staticmethod
|
||||
def _reset(local_path, env, log=None):
|
||||
messages = []
|
||||
repo = Repo._createRepoObject(local_path, env)
|
||||
origin = repo.remotes.origin
|
||||
seen = set()
|
||||
head = None
|
||||
stale_refs = origin.stale_refs
|
||||
# Update our local heads to match the remote, and pick one to
|
||||
# reset the repo to. We don't delete anything at this point
|
||||
# because we want to make sure the repo is in a state stable
|
||||
# enough for git to operate.
|
||||
|
||||
# Reset the working directory to the default remote branch.
|
||||
for ref in origin.refs:
|
||||
if ref.remote_head != "HEAD":
|
||||
continue
|
||||
# Use the ref the remote HEAD is pointing to
|
||||
head_ref = ref.ref
|
||||
head = head_ref.remote_head
|
||||
repo.create_head(head, head_ref, force=True)
|
||||
if log:
|
||||
log.debug("Reset to %s", head)
|
||||
else:
|
||||
messages.append("Reset to %s" % head)
|
||||
repo.head.reference = head
|
||||
break
|
||||
|
||||
# Delete local heads that no longer exist on the remote end
|
||||
remote_heads = {r.remote_head for r in origin.refs}
|
||||
for ref in repo.heads:
|
||||
if ref.name not in remote_heads:
|
||||
if log:
|
||||
log.debug("Delete stale local ref %s", ref)
|
||||
else:
|
||||
messages.append("Delete stale local ref %s" % ref)
|
||||
repo.delete_head(ref, 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 repo.git.version_info[:2] < (2, 13):
|
||||
Repo._cleanup_leaked_ref_dirs(local_path, log, messages)
|
||||
|
||||
# Update our local heads to match the remote
|
||||
for ref in origin.refs:
|
||||
if ref.remote_head == 'HEAD':
|
||||
continue
|
||||
if ref in stale_refs:
|
||||
continue
|
||||
repo.create_head(ref.remote_head, ref, force=True)
|
||||
seen.add(ref.remote_head)
|
||||
if head is None:
|
||||
head = ref.remote_head
|
||||
if log:
|
||||
log.debug("Reset to %s", head)
|
||||
else:
|
||||
messages.append("Reset to %s" % head)
|
||||
repo.head.reference = head
|
||||
for ref in stale_refs:
|
||||
if log:
|
||||
log.debug("Delete stale ref %s", ref.remote_head)
|
||||
else:
|
||||
messages.append("Delete stale ref %s" % ref.remote_head)
|
||||
# A stale ref means the upstream branch (e.g. foobar) was deleted
|
||||
# so we need to delete both our local head (if existing) and the
|
||||
# remote tracking head. Both repo.heads and ref.remote_head
|
||||
# contain the pure branch name so they can be compared easily.
|
||||
for head in repo.heads:
|
||||
if head.name == ref.remote_head:
|
||||
repo.delete_head(ref.remote_head, force=True)
|
||||
break
|
||||
git.refs.RemoteReference.delete(repo, ref, force=True)
|
||||
return messages
|
||||
|
||||
def reset(self, zuul_event_id=None, build=None, process_worker=None):
|
||||
|
@ -523,7 +537,7 @@ class Repo(object):
|
|||
# --tags' is all that is necessary. See
|
||||
# https://github.com/git/git/blob/master/Documentation/RelNotes/1.9.0.txt#L18-L20
|
||||
self._git_fetch(repo, 'origin', zuul_event_id)
|
||||
self._git_fetch(repo, 'origin', zuul_event_id, tags=True)
|
||||
self._git_fetch(repo, 'origin', zuul_event_id, tags=True, prune=True)
|
||||
|
||||
def isUpdateNeeded(self, repo_state, zuul_event_id=None):
|
||||
repo = self.createRepoObject(zuul_event_id)
|
||||
|
|
Loading…
Reference in New Issue