git: use force fetch and update for target ref. This solves a case

when in PRs a target is force updated and is out of sync. Before
we used a pull which --ff-only fails obviosly because two are out of
sync. This change uses new logic that resets the target branch according to the
source target branch allowing smooth merge simulation.
This commit is contained in:
Marcin Kuzminski 2018-06-08 15:42:01 +02:00
parent 560fd7144f
commit 367a32a69a
3 changed files with 90 additions and 5 deletions

View file

@ -747,6 +747,69 @@ class TestPullrequestsView(object):
assert 'Pull request updated to' in response.body
assert 'with 1 added, 1 removed commits.' in response.body
def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
backend = backend_git
commits = [
{'message': 'master-commit-1'},
{'message': 'master-commit-2-change-1'},
{'message': 'master-commit-3-change-2'},
{'message': 'feat-commit-1', 'parents': ['master-commit-1']},
{'message': 'feat-commit-2'},
]
commit_ids = backend.create_master_repo(commits)
target = backend.create_repo(heads=['master-commit-3-change-2'])
source = backend.create_repo(heads=['feat-commit-2'])
# create pr from a in source to A in target
pull_request = PullRequest()
pull_request.source_repo = source
# TODO: johbo: Make sure that we write the source ref this way!
pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
branch=backend.default_branch_name,
commit_id=commit_ids['master-commit-3-change-2'])
pull_request.target_repo = target
# TODO: johbo: Target ref should be branch based, since tip can jump
# from branch to branch
pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
branch=backend.default_branch_name,
commit_id=commit_ids['feat-commit-2'])
pull_request.revisions = [
commit_ids['feat-commit-1'],
commit_ids['feat-commit-2']
]
pull_request.title = u"Test"
pull_request.description = u"Description"
pull_request.author = UserModel().get_by_username(
TEST_USER_ADMIN_LOGIN)
Session().add(pull_request)
Session().commit()
pull_request_id = pull_request.pull_request_id
# PR is created, now we simulate a force-push into target,
# that drops a 2 last commits
vcsrepo = target.scm_instance()
vcsrepo.config.clear_section('hooks')
vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
# update PR
self.app.post(
route_path('pullrequest_update',
repo_name=target.repo_name,
pull_request_id=pull_request_id),
params={'update_commits': 'true',
'csrf_token': csrf_token},
status=200)
response = self.app.get(route_path(
'pullrequest_new',
repo_name=target.repo_name))
assert response.status_int == 200
response.mustcontain('Pull request updated to')
response.mustcontain('with 0 added, 0 removed commits.')
def test_update_of_ancestor_reference(self, backend, csrf_token):
commits = [
{'message': 'ancestor'},

View file

@ -757,7 +757,7 @@ class GitRepository(BaseRepository):
cmd = ['fetch', self.path, source_branch]
self.run_git_command(cmd, fail_on_stderr=False)
def _local_fetch(self, repository_path, branch_name):
def _local_fetch(self, repository_path, branch_name, use_origin=False):
"""
Fetch a branch from a local repository.
"""
@ -765,7 +765,17 @@ class GitRepository(BaseRepository):
if repository_path == self.path:
raise ValueError('Cannot fetch from the same repository')
cmd = ['fetch', '--no-tags', repository_path, branch_name]
if use_origin:
branch_name = '+{branch}:refs/heads/{branch}'.format(
branch=branch_name)
cmd = ['fetch', '--no-tags', '--update-head-ok',
repository_path, branch_name]
self.run_git_command(cmd, fail_on_stderr=False)
def _local_reset(self, branch_name):
branch_name = '{}'.format(branch_name)
cmd = ['reset', '--hard', branch_name]
self.run_git_command(cmd, fail_on_stderr=False)
def _last_fetch_heads(self):
@ -840,7 +850,7 @@ class GitRepository(BaseRepository):
'merge', '--no-ff', '-m', safe_str(merge_message)]
cmd.extend(heads)
try:
self.run_git_command(cmd, fail_on_stderr=False)
output = self.run_git_command(cmd, fail_on_stderr=False)
except RepositoryError:
# Cleanup any merge leftovers
self.run_git_command(['merge', '--abort'], fail_on_stderr=False)
@ -897,6 +907,8 @@ class GitRepository(BaseRepository):
merger_name, merger_email, dry_run=False,
use_rebase=False, close_branch=False):
if target_ref.commit_id != self.branches[target_ref.name]:
log.warning('Target ref %s commit mismatch %s vs %s', target_ref,
target_ref.commit_id, self.branches[target_ref.name])
return MergeResponse(
False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
@ -907,20 +919,29 @@ class GitRepository(BaseRepository):
if shadow_repo.get_remote_ref(source_ref.name):
shadow_repo._checkout(source_ref.name, force=True)
# checkout target
# checkout target, and fetch changes
shadow_repo._checkout(target_ref.name, force=True)
shadow_repo._local_pull(self.path, target_ref.name)
# fetch/reset pull the target, in case it is changed
# this handles even force changes
shadow_repo._local_fetch(self.path, target_ref.name, use_origin=True)
shadow_repo._local_reset(target_ref.name)
# Need to reload repo to invalidate the cache, or otherwise we cannot
# retrieve the last target commit.
shadow_repo = GitRepository(shadow_repository_path)
if target_ref.commit_id != shadow_repo.branches[target_ref.name]:
log.warning('Shadow Target ref %s commit mismatch %s vs %s',
target_ref, target_ref.commit_id,
shadow_repo.branches[target_ref.name])
return MergeResponse(
False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
# calculate new branch
pr_branch = shadow_repo._get_new_pr_branch(
source_ref.name, target_ref.name)
log.debug('using pull-request merge branch: `%s`', pr_branch)
# checkout to temp branch, and fetch changes
shadow_repo._checkout(pr_branch, create=True)
try:
shadow_repo._local_fetch(source_repo.path, source_ref.name)

View file

@ -417,6 +417,7 @@
<strong>${_('Missing commits')}:</strong>
${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
</div>
</div>
</div>