diff --git a/docs/api/methods/repo-methods.rst b/docs/api/methods/repo-methods.rst --- a/docs/api/methods/repo-methods.rst +++ b/docs/api/methods/repo-methods.rst @@ -533,6 +533,7 @@ get_repo_settings "hooks_outgoing_pull_logger": true, "phases_publish": "True", "rhodecode_hg_use_rebase_for_merging": true, + "rhodecode_hg_close_branch_before_merging": false, "rhodecode_pr_merge_enabled": true, "rhodecode_use_outdated_comments": true } diff --git a/rhodecode/lib/vcs/backends/base.py b/rhodecode/lib/vcs/backends/base.py --- a/rhodecode/lib/vcs/backends/base.py +++ b/rhodecode/lib/vcs/backends/base.py @@ -420,7 +420,7 @@ class BaseRepository(object): def merge(self, target_ref, source_repo, source_ref, workspace_id, user_name='', user_email='', message='', dry_run=False, - use_rebase=False): + use_rebase=False, close_branch=False): """ Merge the revisions specified in `source_ref` from `source_repo` onto the `target_ref` of this repository. @@ -445,6 +445,7 @@ class BaseRepository(object): :param dry_run: If `True` the merge will not take place. :param use_rebase: If `True` commits from the source will be rebased on top of the target instead of being merged. + :param close_branch: If `True` branch will be close before merging it """ if dry_run: message = message or 'dry_run_merge_message' @@ -465,7 +466,7 @@ class BaseRepository(object): return self._merge_repo( shadow_repository_path, target_ref, source_repo, source_ref, message, user_name, user_email, dry_run=dry_run, - use_rebase=use_rebase) + use_rebase=use_rebase, close_branch=close_branch) except RepositoryError: log.exception( 'Unexpected failure when running merge, dry-run=%s', @@ -475,7 +476,8 @@ class BaseRepository(object): def _merge_repo(self, shadow_repository_path, target_ref, source_repo, source_ref, merge_message, - merger_name, merger_email, dry_run=False, use_rebase=False): + merger_name, merger_email, dry_run=False, + use_rebase=False, close_branch=False): """Internal implementation of merge.""" raise NotImplementedError 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 @@ -849,7 +849,7 @@ class GitRepository(BaseRepository): def _merge_repo(self, shadow_repository_path, target_ref, source_repo, source_ref, merge_message, merger_name, merger_email, dry_run=False, - use_rebase=False): + use_rebase=False, close_branch=False): if target_ref.commit_id != self.branches[target_ref.name]: return MergeResponse( False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD) diff --git a/rhodecode/lib/vcs/backends/hg/repository.py b/rhodecode/lib/vcs/backends/hg/repository.py --- a/rhodecode/lib/vcs/backends/hg/repository.py +++ b/rhodecode/lib/vcs/backends/hg/repository.py @@ -646,6 +646,28 @@ class MercurialRepository(BaseRepository self._remote.update(clean=True) raise + def _local_close(self, target_ref, user_name, user_email, + source_ref, close_message=''): + """ + Close the branch of the given source_revision + + Returns the commit id of the close and a boolean indicating if the + commit needs to be pushed. + """ + self._update(target_ref.commit_id) + message = close_message or "Closing branch" + try: + self._remote.commit( + message=safe_str(message), + username=safe_str('%s <%s>' % (user_name, user_email)), + close_branch=True) + self._remote.invalidate_vcs_cache() + return self._identify(), True + except RepositoryError: + # Cleanup any commit leftovers + self._remote.update(clean=True) + raise + def _is_the_same_branch(self, target_ref, source_ref): return ( self._get_branch_name(target_ref) == @@ -679,7 +701,7 @@ class MercurialRepository(BaseRepository def _merge_repo(self, shadow_repository_path, target_ref, source_repo, source_ref, merge_message, merger_name, merger_email, dry_run=False, - use_rebase=False): + use_rebase=False, close_branch=False): if target_ref.commit_id not in self._heads(): return MergeResponse( False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD) @@ -712,26 +734,40 @@ class MercurialRepository(BaseRepository merge_ref = None merge_failure_reason = MergeFailureReason.NONE - try: - merge_commit_id, needs_push = shadow_repo._local_merge( - target_ref, merge_message, merger_name, merger_email, - source_ref, use_rebase=use_rebase) + if close_branch and not use_rebase: + try: + close_commit_id, needs_push = shadow_repo._local_close( + target_ref, merger_name, merger_email, source_ref) + target_ref.commit_id = close_commit_id + merge_possible = True + except RepositoryError: + log.exception('Failure when doing close branch on hg shadow repo') + merge_possible = False + merge_failure_reason = MergeFailureReason.MERGE_FAILED + else: merge_possible = True - # Set a bookmark pointing to the merge commit. This bookmark may be - # used to easily identify the last successful merge commit in the - # shadow repository. - shadow_repo.bookmark('pr-merge', revision=merge_commit_id) - merge_ref = Reference('book', 'pr-merge', merge_commit_id) - except SubrepoMergeError: - log.exception( - 'Subrepo merge error during local merge on hg shadow repo.') - merge_possible = False - merge_failure_reason = MergeFailureReason.SUBREPO_MERGE_FAILED - except RepositoryError: - log.exception('Failure when doing local merge on hg shadow repo') - merge_possible = False - merge_failure_reason = MergeFailureReason.MERGE_FAILED + if merge_possible: + try: + merge_commit_id, needs_push = shadow_repo._local_merge( + target_ref, merge_message, merger_name, merger_email, + source_ref, use_rebase=use_rebase) + merge_possible = True + + # Set a bookmark pointing to the merge commit. This bookmark may be + # used to easily identify the last successful merge commit in the + # shadow repository. + shadow_repo.bookmark('pr-merge', revision=merge_commit_id) + merge_ref = Reference('book', 'pr-merge', merge_commit_id) + except SubrepoMergeError: + log.exception( + 'Subrepo merge error during local merge on hg shadow repo.') + merge_possible = False + merge_failure_reason = MergeFailureReason.SUBREPO_MERGE_FAILED + except RepositoryError: + log.exception('Failure when doing local merge on hg shadow repo') + merge_possible = False + merge_failure_reason = MergeFailureReason.MERGE_FAILED if merge_possible and not dry_run: if needs_push: diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py --- a/rhodecode/model/forms.py +++ b/rhodecode/model/forms.py @@ -388,7 +388,9 @@ class _BaseVcsSettingsForm(formencode.Sc extensions_largefiles = v.StringBoolean(if_missing=False) extensions_evolve = v.StringBoolean(if_missing=False) phases_publish = v.StringBoolean(if_missing=False) + rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False) + rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False) # git vcs_git_lfs_enabled = v.StringBoolean(if_missing=False) diff --git a/rhodecode/model/pull_request.py b/rhodecode/model/pull_request.py --- a/rhodecode/model/pull_request.py +++ b/rhodecode/model/pull_request.py @@ -552,6 +552,7 @@ class PullRequestModel(BaseModel): workspace_id = self._workspace_id(pull_request) use_rebase = self._use_rebase_for_merging(pull_request) + close_branch = self._close_branch_before_merging(pull_request) callback_daemon, extras = prepare_callback_daemon( extras, protocol=vcs_settings.HOOKS_PROTOCOL, @@ -565,7 +566,8 @@ class PullRequestModel(BaseModel): merge_state = target_vcs.merge( target_ref, source_vcs, pull_request.source_ref_parts, workspace_id, user_name=user.username, - user_email=user.email, message=message, use_rebase=use_rebase) + user_email=user.email, message=message, use_rebase=use_rebase, + close_branch=close_branch) return merge_state def _comment_and_close_pr(self, pull_request, user, merge_state): @@ -1249,9 +1251,11 @@ class PullRequestModel(BaseModel): workspace_id = self._workspace_id(pull_request) source_vcs = pull_request.source_repo.scm_instance() use_rebase = self._use_rebase_for_merging(pull_request) + close_branch = self._close_branch_before_merging(pull_request) merge_state = target_vcs.merge( target_reference, source_vcs, pull_request.source_ref_parts, - workspace_id, dry_run=True, use_rebase=use_rebase) + workspace_id, dry_run=True, use_rebase=use_rebase, + close_branch=close_branch) # Do not store the response if there was an unknown error. if merge_state.failure_reason != MergeFailureReason.UNKNOWN: @@ -1416,14 +1420,21 @@ class PullRequestModel(BaseModel): return vcs_diff def _is_merge_enabled(self, pull_request): + return self._get_general_setting( + pull_request, 'rhodecode_pr_merge_enabled') + + def _use_rebase_for_merging(self, pull_request): + return self._get_general_setting( + pull_request, 'rhodecode_hg_use_rebase_for_merging') + + def _close_branch_before_merging(self, pull_request): + return self._get_general_setting( + pull_request, 'rhodecode_hg_close_branch_before_merging') + + def _get_general_setting(self, pull_request, settings_key, default=False): settings_model = VcsSettingsModel(repo=pull_request.target_repo) settings = settings_model.get_general_settings() - return settings.get('rhodecode_pr_merge_enabled', False) - - def _use_rebase_for_merging(self, pull_request): - settings_model = VcsSettingsModel(repo=pull_request.target_repo) - settings = settings_model.get_general_settings() - return settings.get('rhodecode_hg_use_rebase_for_merging', False) + return settings.get(settings_key, default) def _log_audit_action(self, action, action_data, user, pull_request): audit_logger.store( diff --git a/rhodecode/model/settings.py b/rhodecode/model/settings.py --- a/rhodecode/model/settings.py +++ b/rhodecode/model/settings.py @@ -408,7 +408,8 @@ class VcsSettingsModel(object): GENERAL_SETTINGS = ( 'use_outdated_comments', 'pr_merge_enabled', - 'hg_use_rebase_for_merging') + 'hg_use_rebase_for_merging', + 'hg_close_branch_before_merging') HOOKS_SETTINGS = ( ('hooks', 'changegroup.repo_size'), diff --git a/rhodecode/templates/base/vcs_settings.mako b/rhodecode/templates/base/vcs_settings.mako --- a/rhodecode/templates/base/vcs_settings.mako +++ b/rhodecode/templates/base/vcs_settings.mako @@ -161,6 +161,14 @@ ${_('Use rebase instead of creating a merge commit when merging via web interface.')} +
+ ${h.checkbox('rhodecode_hg_close_branch_before_merging' + suffix, 'True', **kwargs)} + +
+
+ ${_('Close branch before merging it into destination branch. No effect when rebase strategy is use.')} +
+ % endif