From a3373c4a4fee565acfb0190dc9a7273fb861d120 Mon Sep 17 00:00:00 2001 From: ievgenii vdovenko Date: Wed, 2 Jul 2025 17:05:00 +0200 Subject: [PATCH 1/5] feature: initial implementation of squash functionality for git and hg; no enable/disable settings option yet --- .../repository/views/repo_pull_requests.py | 12 +- rhodecode/lib/vcs/backends/base.py | 3 + rhodecode/lib/vcs/backends/git/repository.py | 92 +++++++--- rhodecode/lib/vcs/backends/hg/repository.py | 18 +- rhodecode/model/pull_request.py | 7 +- rhodecode/public/css/buttons.less | 9 + .../pullrequest_merge_checks.mako | 158 ++++++++++++------ .../pullrequests/pullrequest_show.mako | 3 + 8 files changed, 211 insertions(+), 91 deletions(-) diff --git a/rhodecode/apps/repository/views/repo_pull_requests.py b/rhodecode/apps/repository/views/repo_pull_requests.py index 9b24e585..b1106551 100644 --- a/rhodecode/apps/repository/views/repo_pull_requests.py +++ b/rhodecode/apps/repository/views/repo_pull_requests.py @@ -1481,6 +1481,10 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView): if merge_possible: log.debug("Pre-conditions checked, trying to merge.") + + controls = peppercorn.parse(self.request.POST.items()) + squash_before_merge = str2bool(controls.get("squash_before_merge", "false")) + extras = vcs_operation_context( self.request.environ, repo_name=pull_request.target_repo.repo_name, @@ -1489,7 +1493,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView): scm=pull_request.target_repo.repo_type, ) with pull_request.set_state(PullRequest.STATE_UPDATING): - self._merge_pull_request(pull_request, self._rhodecode_db_user, extras) + self._merge_pull_request( + pull_request, self._rhodecode_db_user, extras, squash_commits=squash_before_merge + ) else: log.debug("Pre-conditions failed, NOT merging.") @@ -1501,9 +1507,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView): ) ) - def _merge_pull_request(self, pull_request, user, extras): + def _merge_pull_request(self, pull_request, user, extras, squash_commits=False): _ = self.request.translate - merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras) + merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras, squash_commits=squash_commits) if merge_resp.executed: log.debug("The merge was successful, closing the pull request.") diff --git a/rhodecode/lib/vcs/backends/base.py b/rhodecode/lib/vcs/backends/base.py index 806fe07b..15aca4be 100644 --- a/rhodecode/lib/vcs/backends/base.py +++ b/rhodecode/lib/vcs/backends/base.py @@ -1169,6 +1169,7 @@ class BaseRepository(object): dry_run=False, use_rebase=False, close_branch=False, + squash_commits=False, ): """ Merge the revisions specified in `source_ref` from `source_repo` @@ -1222,6 +1223,7 @@ class BaseRepository(object): dry_run=dry_run, use_rebase=use_rebase, close_branch=close_branch, + squash_commits=squash_commits, ) except RepositoryError as exc: log.exception("Unexpected failure when running merge, dry-run=%s", dry_run) @@ -1240,6 +1242,7 @@ class BaseRepository(object): dry_run=False, use_rebase=False, close_branch=False, + squash_commits=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 index 0eb337c9..4d88c4e0 100644 --- a/rhodecode/lib/vcs/backends/git/repository.py +++ b/rhodecode/lib/vcs/backends/git/repository.py @@ -24,6 +24,7 @@ import logging import os import re +from celery.platforms import strargv from zope.cachedescriptors.property import Lazy as LazyProperty from collections import OrderedDict @@ -842,7 +843,7 @@ class GitRepository(BaseRepository): cmd.extend(["--no-tags", repository_path, branch_name]) self.run_git_command(cmd, fail_on_stderr=False) - def _local_merge(self, merge_message, user_name, user_email, heads): + def _local_merge(self, merge_message, user_name, user_email, heads, squash_commits=False): """ Merge the given head into the checked out branch. @@ -853,6 +854,7 @@ class GitRepository(BaseRepository): :param merge_message: The message to use for the merge commit. :param heads: the heads to merge. + :param squash_commits: Use squash strategy to merge. """ if self.bare: raise RepositoryError("Cannot merge into a bare git repository") @@ -863,40 +865,68 @@ class GitRepository(BaseRepository): if self.is_empty(): # TODO(skreft): do something more robust in this case. raise RepositoryError("Do not know how to merge into empty repositories yet") - unresolved = None - # N.B.(skreft): the --no-ff option is used to enforce the creation of a - # commit message. We also specify the user who is doing the merge. - cmd = [ + base_cmd = [ "-c", f'user.name="{user_name}"', "-c", f"user.email={user_email}", - "merge", - "--no-ff", - "-m", - safe_str(merge_message), ] - merge_cmd = cmd + heads + if squash_commits: + self._squash_merge(base_cmd, heads, merge_message) + else: + self._normal_merge(base_cmd, heads, merge_message) + def _normal_merge(self, base_cmd, heads, merge_message): try: + # N.B.(skreft): the --no-ff option is used to enforce the creation of a + # commit message. We also specify the user who is doing the merge. + merge_cmd = base_cmd + ["merge", "--no-ff", "-m", safe_str(merge_message)] + heads + + log.debug("Merge command: %s", merge_cmd) self.run_git_command(merge_cmd, fail_on_stderr=False) - except RepositoryError: - files = self.run_git_command(["diff", "--name-only", "--diff-filter", "U"], fail_on_stderr=False)[ - 0 - ].splitlines() - # NOTE(marcink): we add U notation for consistent with HG backend output - unresolved = [f"U {f}" for f in files] + except RepositoryError as e: + log.warning("Merge failed with error: %s", e) + self._abort_merge() - # Cleanup any merge leftovers - self._remote.invalidate_vcs_cache() - self.run_git_command(["merge", "--abort"], fail_on_stderr=False) + def _squash_merge(self, base_cmd, heads, merge_message): + try: + squash_cmd = ( + base_cmd + + [ + "merge", + "--squash", + ] + + heads + ) - if unresolved: - raise UnresolvedFilesInRepo(unresolved) - else: - raise + log.debug("Squash command: %s", squash_cmd) + self.run_git_command(squash_cmd, fail_on_stderr=False) + + commit_cmd = base_cmd + ["commit", "-m", safe_str(merge_message)] + + log.debug("Commit after squash command: %s", commit_cmd) + self.run_git_command(commit_cmd, fail_on_stderr=False) + except RepositoryError as e: + log.warning("Squash failed with error: %s", e) + self._abort_merge() + + def _abort_merge(self): + files = self.run_git_command(["diff", "--name-only", "--diff-filter", "U"], fail_on_stderr=False)[ + 0 + ].splitlines() + + # NOTE(marcink): we add U notation for consistent with HG backend output + unresolved = [f"U {f}" for f in files] + + # Cleanup merge state + self._remote.invalidate_vcs_cache() + self.run_git_command(["merge", "--abort"], fail_on_stderr=False) + + if unresolved: + raise UnresolvedFilesInRepo(unresolved) + raise RepositoryError("Merge failed without unresolved files") def _local_push(self, source_branch, repository_path, target_branch, enable_hooks=False, rc_scm_data=None): """ @@ -961,10 +991,16 @@ class GitRepository(BaseRepository): dry_run=False, use_rebase=False, close_branch=False, + squash_commits=False, ): - log.debug( - "Executing merge_repo with %s strategy, dry_run mode:%s", "rebase" if use_rebase else "merge", dry_run - ) + if squash_commits: + strategy = "squash and merge" + elif use_rebase: + strategy = "rebase" + else: + strategy = "merge" + + log.debug("Executing merge_repo with '%s' strategy, dry_run mode: %s", strategy, dry_run) from rhodecode.model.repo import RepoModel if target_ref.commit_id != self.branches[target_ref.name]: @@ -1026,7 +1062,9 @@ class GitRepository(BaseRepository): merge_failure_reason = MergeFailureReason.NONE metadata = {} try: - shadow_repo._local_merge(merge_message, merger_name, merger_email, [source_ref.commit_id]) + shadow_repo._local_merge( + merge_message, merger_name, merger_email, [source_ref.commit_id], squash_commits=squash_commits + ) merge_possible = True # Need to invalidate the cache, or otherwise we diff --git a/rhodecode/lib/vcs/backends/hg/repository.py b/rhodecode/lib/vcs/backends/hg/repository.py index 3adc80b7..7424857b 100644 --- a/rhodecode/lib/vcs/backends/hg/repository.py +++ b/rhodecode/lib/vcs/backends/hg/repository.py @@ -680,6 +680,7 @@ class MercurialRepository(BaseRepository): use_rebase=False, close_commit_id=None, dry_run=False, + squash_commits=False, ): """ Merge the given source_revision into the checked out revision. @@ -711,11 +712,11 @@ class MercurialRepository(BaseRepository): return source_ref_commit_id, True unresolved = None - if use_rebase: + if use_rebase or squash_commits: try: bookmark_name = f"rcbook{source_ref_commit_id}{target_ref_commit_id}" self.bookmark(bookmark_name, revision=source_ref.commit_id) - self._remote.rebase(source=source_ref_commit_id, dest=target_ref_commit_id) + self._remote.rebase(source=source_ref_commit_id, dest=target_ref_commit_id, collapse=squash_commits) self._remote.invalidate_vcs_cache() self._update(bookmark_name, clean=True) return self._identify(), True @@ -809,10 +810,16 @@ class MercurialRepository(BaseRepository): dry_run=False, use_rebase=False, close_branch=False, + squash_commits=False, ): - log.debug( - "Executing merge_repo with %s strategy, dry_run mode:%s", "rebase" if use_rebase else "merge", dry_run - ) + if squash_commits: + strategy = "squash and merge" + elif use_rebase: + strategy = "rebase" + else: + strategy = "merge" + + log.debug("Executing merge_repo with '%s' strategy, dry_run mode: %s", strategy, dry_run) from rhodecode.model.repo import RepoModel if target_ref.commit_id not in self._heads(): @@ -899,6 +906,7 @@ class MercurialRepository(BaseRepository): use_rebase=use_rebase, close_commit_id=close_commit_id, dry_run=dry_run, + squash_commits=squash_commits, ) merge_possible = True diff --git a/rhodecode/model/pull_request.py b/rhodecode/model/pull_request.py index dedfb409..6ffe71c3 100644 --- a/rhodecode/model/pull_request.py +++ b/rhodecode/model/pull_request.py @@ -991,12 +991,12 @@ class PullRequestModel(BaseModel): return commit_ids - def merge_repo(self, pull_request, user, extras): + def merge_repo(self, pull_request, user, extras, squash_commits=False): repo_type = pull_request.source_repo.repo_type log.debug("Merging pull request %s", pull_request) extras["user_agent"] = "{}/internal-merge".format(repo_type) - merge_state = self._merge_pull_request(pull_request, user, extras) + merge_state = self._merge_pull_request(pull_request, user, extras, squash_commits=squash_commits) if merge_state.executed: log.debug("Merge was successful, updating the pull request comments.") self._comment_and_close_pr(pull_request, user, merge_state) @@ -1007,7 +1007,7 @@ class PullRequestModel(BaseModel): log.warning("Merge failed, not updating the pull request.") return merge_state - def _merge_pull_request(self, pull_request, user, extras, merge_msg=None): + def _merge_pull_request(self, pull_request, user, extras, merge_msg=None, squash_commits=False): target_vcs = pull_request.target_repo.scm_instance() source_vcs = pull_request.source_repo.scm_instance() @@ -1047,6 +1047,7 @@ class PullRequestModel(BaseModel): message=message, use_rebase=use_rebase, close_branch=close_branch, + squash_commits=squash_commits, ) return merge_state diff --git a/rhodecode/public/css/buttons.less b/rhodecode/public/css/buttons.less index 3800f50c..8fa266b3 100644 --- a/rhodecode/public/css/buttons.less +++ b/rhodecode/public/css/buttons.less @@ -521,6 +521,15 @@ input[type="submit"] { } } +a[role="button"].btn { + &.disabled { + .border ( @border-thickness-buttons, @rcblue ); + background-color: @rcblue; + color: white; + opacity: .5; + } +} + input[type="reset"] { &:extend(.btn-default); diff --git a/rhodecode/templates/pullrequests/pullrequest_merge_checks.mako b/rhodecode/templates/pullrequests/pullrequest_merge_checks.mako index 8386d7eb..6a7c65bf 100644 --- a/rhodecode/templates/pullrequests/pullrequest_merge_checks.mako +++ b/rhodecode/templates/pullrequests/pullrequest_merge_checks.mako @@ -1,82 +1,134 @@ -
% if c.pr_merge_possible: -

- - ${_('This pull request can be merged automatically.')} -

+

+ + ${_('This pull request can be merged automatically.')} +

% else: -

- - ${_('Merge is not currently possible because of below failed checks.')} -

+

+ + ${_('Merge is not currently possible because of below failed checks.')} +

% endif % if c.pr_merge_errors.items(): % endif
% if c.allowed_to_merge: - ## Merge info, show only if all errors are taken care of - % if not c.pr_merge_errors and c.pr_merge_info: -
-
    - % for pr_merge_key, pr_merge_details in c.pr_merge_info.items(): -
  • - - ${pr_merge_details['message']} -
  • - % endfor -
-
- % endif + ## Merge info, show only if all errors are taken care of + % if not c.pr_merge_errors and c.pr_merge_info: +
+
    + % for pr_merge_key, pr_merge_details in c.pr_merge_info.items(): +
  • + - ${pr_merge_details['message']} +
  • + % endfor +
+
+ % endif
- ${h.secure_form(h.route_path('pullrequest_merge', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id), id='merge_pull_request_form', request=request)} - <% merge_disabled = ' disabled' if c.pr_merge_possible is False else '' %> + ${h.secure_form(h.route_path('pullrequest_merge', repo_name=c.repo_name, + pull_request_id=c.pull_request.pull_request_id), id='merge_pull_request_form', request=request, + style='display: flex; justify-content: flex-end;')} + <% merge_disabled = ' disabled' if c.pr_merge_possible is False else '' %> - % if c.allowed_to_close: - ## close PR action, injected later next to COMMENT button - % if c.pull_request_review_status == c.REVIEW_STATUS_APPROVED: - - ${_('Close with status {}').format(h.commit_status_lbl(c.REVIEW_STATUS_APPROVED))} - - % else: - - ${_('Close with status {}').format(h.commit_status_lbl(c.REVIEW_STATUS_REJECTED))} - + % if c.allowed_to_close: + ## close PR action, injected later next to COMMENT button + % if c.pull_request_review_status == c.REVIEW_STATUS_APPROVED: + + ${_('Close with status {}').format(h.commit_status_lbl(c.REVIEW_STATUS_APPROVED))} + + % else: + + ${_('Close with status {}').format(h.commit_status_lbl(c.REVIEW_STATUS_REJECTED))} + + % endif % endif - % endif + + ${h.end_form()} + +
% elif c.rhodecode_user.username != h.DEFAULT_USER: - ${_('refresh checks')} - + ${_('refresh checks')} + % else: - + % endif
+ + + diff --git a/rhodecode/templates/pullrequests/pullrequest_show.mako b/rhodecode/templates/pullrequests/pullrequest_show.mako index 14388051..586f5d2a 100644 --- a/rhodecode/templates/pullrequests/pullrequest_show.mako +++ b/rhodecode/templates/pullrequests/pullrequest_show.mako @@ -975,6 +975,9 @@ window.setObserversData = ${c.pull_request_set_observers_data_json | n}; if (!$('#merge_pull_request').attr('disabled')) { $('#merge_pull_request').attr('disabled', 'disabled'); } + if (!$('#pull_request_more_options').hasClass('disabled')) { + $('#pull_request_more_options').addClass('disabled'); + } return true; }); From b561675bb4d204b3578abd8d5f42f37edb543648 Mon Sep 17 00:00:00 2001 From: ievgenii vdovenko Date: Thu, 3 Jul 2025 13:39:16 +0200 Subject: [PATCH 2/5] feature: add an option to the repo settings to disable/enable strategy selector for a user in a pull request --- .../repository/views/repo_pull_requests.py | 3 ++ rhodecode/lib/vcs/backends/git/repository.py | 9 +--- rhodecode/model/forms.py | 2 + rhodecode/model/settings.py | 2 + rhodecode/templates/base/vcs_settings.mako | 17 +++++++- .../pullrequest_merge_checks.mako | 42 ++++++++++--------- 6 files changed, 45 insertions(+), 30 deletions(-) diff --git a/rhodecode/apps/repository/views/repo_pull_requests.py b/rhodecode/apps/repository/views/repo_pull_requests.py index b1106551..44d64ca8 100644 --- a/rhodecode/apps/repository/views/repo_pull_requests.py +++ b/rhodecode/apps/repository/views/repo_pull_requests.py @@ -428,6 +428,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView): raise HTTPNotFound() c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver) + if pull_request_at_ver.target_repo.repo_type in ["hg", "git"]: + key = "rhodecode_%s_merge_strategy_selector" % pull_request_at_ver.target_repo.repo_type + c.show_merge_strategy = self._get_general_setting(pull_request_at_ver.target_repo, key) c.pull_request = pull_request_display_obj c.renderer = pull_request_at_ver.description_renderer or c.renderer diff --git a/rhodecode/lib/vcs/backends/git/repository.py b/rhodecode/lib/vcs/backends/git/repository.py index 4d88c4e0..60558703 100644 --- a/rhodecode/lib/vcs/backends/git/repository.py +++ b/rhodecode/lib/vcs/backends/git/repository.py @@ -892,14 +892,7 @@ class GitRepository(BaseRepository): def _squash_merge(self, base_cmd, heads, merge_message): try: - squash_cmd = ( - base_cmd - + [ - "merge", - "--squash", - ] - + heads - ) + squash_cmd = base_cmd + ["merge", "--squash"] + heads log.debug("Squash command: %s", squash_cmd) self.run_git_command(squash_cmd, fail_on_stderr=False) diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py index 695886b2..c0501f1a 100644 --- a/rhodecode/model/forms.py +++ b/rhodecode/model/forms.py @@ -442,11 +442,13 @@ class _BaseVcsSettingsForm(formencode.Schema): rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False) rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False) + rhodecode_hg_merge_strategy_selector = v.StringBoolean(if_missing=False) # git vcs_git_lfs_enabled = v.StringBoolean(if_missing=False) rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False) rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False) + rhodecode_git_merge_strategy_selector = v.StringBoolean(if_missing=False) # cache rhodecode_diff_cache = v.StringBoolean(if_missing=False) diff --git a/rhodecode/model/settings.py b/rhodecode/model/settings.py index 7026a638..3e1099ff 100644 --- a/rhodecode/model/settings.py +++ b/rhodecode/model/settings.py @@ -452,8 +452,10 @@ class VcsSettingsModel(object): "auto_merge_enabled", "hg_use_rebase_for_merging", "hg_close_branch_before_merging", + "hg_merge_strategy_selector", "git_use_rebase_for_merging", "git_close_branch_before_merging", + "git_merge_strategy_selector", "diff_cache", ) diff --git a/rhodecode/templates/base/vcs_settings.mako b/rhodecode/templates/base/vcs_settings.mako index 6bd45548..fd61b941 100644 --- a/rhodecode/templates/base/vcs_settings.mako +++ b/rhodecode/templates/base/vcs_settings.mako @@ -269,12 +269,18 @@
${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.')}
- +
+ ${h.checkbox('rhodecode_hg_merge_strategy_selector' + suffix, 'True', **kwargs)} + +
+
+ ${_('When enabled, a drop-down menu will appear in the pull request interface, allowing users to choose the merge strategy they prefer.')} +
@@ -301,6 +307,13 @@
${_('Delete branch after merging it into destination branch.')}
+
+ ${h.checkbox('rhodecode_git_merge_strategy_selector' + suffix, 'True', **kwargs)} + +
+
+ ${_('When enabled, a drop-down menu will appear in the pull request interface, allowing users to choose the merge strategy they prefer.')} +
% endif diff --git a/rhodecode/templates/pullrequests/pullrequest_merge_checks.mako b/rhodecode/templates/pullrequests/pullrequest_merge_checks.mako index 6a7c65bf..29df248a 100644 --- a/rhodecode/templates/pullrequests/pullrequest_merge_checks.mako +++ b/rhodecode/templates/pullrequests/pullrequest_merge_checks.mako @@ -71,27 +71,29 @@ - - - + % if c.show_merge_strategy: + + + - % if not merge_disabled and c.rhodecode_db_repo.repo_type in ['git', 'hg']: - + % if not merge_disabled and c.rhodecode_db_repo.repo_type in ['git', 'hg']: + + % endif % endif ${h.end_form()} From 8c7c09e49ff60074a9630b50d099162dad9aef6c Mon Sep 17 00:00:00 2001 From: ievgenii vdovenko Date: Thu, 3 Jul 2025 13:44:27 +0200 Subject: [PATCH 3/5] feature: changes the log message --- rhodecode/lib/vcs/backends/hg/repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rhodecode/lib/vcs/backends/hg/repository.py b/rhodecode/lib/vcs/backends/hg/repository.py index 7424857b..b125daaf 100644 --- a/rhodecode/lib/vcs/backends/hg/repository.py +++ b/rhodecode/lib/vcs/backends/hg/repository.py @@ -813,7 +813,7 @@ class MercurialRepository(BaseRepository): squash_commits=False, ): if squash_commits: - strategy = "squash and merge" + strategy = "rebase and collapse" elif use_rebase: strategy = "rebase" else: From ab8e075dba38d635cb6d857bb6f1dd404c1c8b5b Mon Sep 17 00:00:00 2001 From: ievgenii vdovenko Date: Thu, 3 Jul 2025 13:47:40 +0200 Subject: [PATCH 4/5] feature: adds default value for c.show_merge_strategy for svn --- rhodecode/apps/repository/views/repo_pull_requests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rhodecode/apps/repository/views/repo_pull_requests.py b/rhodecode/apps/repository/views/repo_pull_requests.py index 44d64ca8..8a652246 100644 --- a/rhodecode/apps/repository/views/repo_pull_requests.py +++ b/rhodecode/apps/repository/views/repo_pull_requests.py @@ -428,6 +428,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView): raise HTTPNotFound() c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver) + c.show_merge_strategy = False if pull_request_at_ver.target_repo.repo_type in ["hg", "git"]: key = "rhodecode_%s_merge_strategy_selector" % pull_request_at_ver.target_repo.repo_type c.show_merge_strategy = self._get_general_setting(pull_request_at_ver.target_repo, key) From 2de10ebfb2db1e317d921347bed91c655c11f7f0 Mon Sep 17 00:00:00 2001 From: ievgenii vdovenko Date: Fri, 4 Jul 2025 12:26:30 +0200 Subject: [PATCH 5/5] tests: adds tests for squash functionality and new VCS setting --- .../models/settings/test_vcs_settings.py | 2 + rhodecode/tests/vcs/test_repository.py | 63 ++++++++++++++++--- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/rhodecode/tests/models/settings/test_vcs_settings.py b/rhodecode/tests/models/settings/test_vcs_settings.py index 4bf845f0..63fe3e22 100644 --- a/rhodecode/tests/models/settings/test_vcs_settings.py +++ b/rhodecode/tests/models/settings/test_vcs_settings.py @@ -38,8 +38,10 @@ GENERAL_FORM_DATA = { "rhodecode_use_outdated_comments": True, "rhodecode_hg_use_rebase_for_merging": True, "rhodecode_hg_close_branch_before_merging": True, + "rhodecode_hg_merge_strategy_selector": True, "rhodecode_git_use_rebase_for_merging": True, "rhodecode_git_close_branch_before_merging": True, + "rhodecode_git_merge_strategy_selector": True, "rhodecode_diff_cache": True, } diff --git a/rhodecode/tests/vcs/test_repository.py b/rhodecode/tests/vcs/test_repository.py index beffb370..4ce46e18 100644 --- a/rhodecode/tests/vcs/test_repository.py +++ b/rhodecode/tests/vcs/test_repository.py @@ -297,17 +297,19 @@ class TestRepositoryGetCommonAncestor: @pytest.mark.backends("git", "hg") class TestRepositoryMerge(object): - def prepare_for_success(self, vcsbackend): + def prepare_for_success(self, vcsbackend, numer_of_source_commits=1): self.target_repo = vcsbackend.create_repo(number_of_commits=1) self.source_repo = vcsbackend.clone_repo(self.target_repo) vcsbackend.add_file(self.target_repo, b"README_MERGE1", b"Version 1") vcsbackend.add_file(self.source_repo, b"README_MERGE2", b"Version 2") - imc = self.source_repo.in_memory_commit - imc.add(FileNode(b"file_x", content=self.source_repo.name)) - imc.commit( - message="Automatic commit from repo merge test", - author="Automatic ", - ) + for i in range(numer_of_source_commits): + imc = self.source_repo.in_memory_commit + file = FileNode(("file_%s" % (i + 1)).encode(), content=self.source_repo.name) + imc.add(file) + imc.commit( + message="Automatic commit from repo merge test. autocommit %s" % i, + author="Automatic ", + ) self.target_commit = self.target_repo.get_commit() self.source_commit = self.source_repo.get_commit() # This only works for Git and Mercurial @@ -385,6 +387,53 @@ class TestRepositoryMerge(object): assert merge_commit.message.strip() == "merge message 1" assert merge_commit.author == "test user " + @pytest.mark.parametrize("numer_of_source_commits", [10, 14, 20, 40]) + def test_squash_commits_success(self, vcsbackend, numer_of_source_commits): + self.prepare_for_success(vcsbackend, numer_of_source_commits=numer_of_source_commits) + + target_repo = backends.get_backend(vcsbackend.alias)(self.target_repo.path) + target_repo_initial_count = len(target_repo.get_commits()) + + self.target_repo.merge( + self.repo_id, + self.workspace_id, + self.target_ref, + self.source_repo, + self.source_ref, + "test user", + "test@rhodecode.com", + "merge message 2", + squash_commits=True, + ) + + target_repo = backends.get_backend(vcsbackend.alias)(self.target_repo.path) + target_commits_after_merge = list(target_repo.get_commits()) + assert len(target_commits_after_merge) == (target_repo_initial_count + 1) + + def test_squash_merge_conflict(self, vcsbackend): + self.prepare_for_conflict(vcsbackend) + + expected_merge_response = MergeResponse(False, False, None, MergeFailureReason.MERGE_FAILED) + target_repo = backends.get_backend(vcsbackend.alias)(self.target_repo.path) + target_repo_initial_count = len(target_repo.get_commits()) + + merge_response = self.target_repo.merge( + self.repo_id, + self.workspace_id, + self.target_ref, + self.source_repo, + self.source_ref, + "test_user", + "test@rhodecode.com", + "test message", + squash_commits=True, + ) + assert merge_response == expected_merge_response + + target_repo = backends.get_backend(vcsbackend.alias)(self.target_repo.path) + target_commits_after_conflict = list(target_repo.get_commits()) + assert len(target_commits_after_conflict) == target_repo_initial_count + def test_merge_success_dry_run(self, vcsbackend): self.prepare_for_success(vcsbackend)