# HG changeset patch # User Marcin Kuzminski # Date 2018-06-08 13:42:01 # Node ID e8c62649d62212df4a7376d84005b3706c64a4ae # Parent a94bb3a196045b8439db02074895c1ab53777efb 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. diff --git a/rhodecode/apps/repository/tests/test_repo_pullrequests.py b/rhodecode/apps/repository/tests/test_repo_pullrequests.py --- a/rhodecode/apps/repository/tests/test_repo_pullrequests.py +++ b/rhodecode/apps/repository/tests/test_repo_pullrequests.py @@ -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'}, diff --git a/rhodecode/lib/vcs/backends/git/repository.py b/rhodecode/lib/vcs/backends/git/repository.py --- a/rhodecode/lib/vcs/backends/git/repository.py +++ b/rhodecode/lib/vcs/backends/git/repository.py @@ -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) diff --git a/rhodecode/templates/pullrequests/pullrequest_show.mako b/rhodecode/templates/pullrequests/pullrequest_show.mako --- a/rhodecode/templates/pullrequests/pullrequest_show.mako +++ b/rhodecode/templates/pullrequests/pullrequest_show.mako @@ -417,6 +417,7 @@ ${_('Missing commits')}: ${_('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}