Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
f141afab
Commit
f141afab
authored
Feb 02, 2017
by
Robert Speicher
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'squash' into 'master'
Allow squashing MRs Closes #150 See merge request !1024
parents
ed6925f4
442bb68f
Changes
37
Hide whitespace changes
Inline
Side-by-side
Showing
37 changed files
with
720 additions
and
123 deletions
+720
-123
app/assets/stylesheets/pages/merge_requests.scss
app/assets/stylesheets/pages/merge_requests.scss
+13
-4
app/controllers/projects/merge_requests_controller.rb
app/controllers/projects/merge_requests_controller.rb
+3
-2
app/helpers/merge_requests_helper.rb
app/helpers/merge_requests_helper.rb
+12
-0
app/models/merge_request.rb
app/models/merge_request.rb
+19
-3
app/models/repository.rb
app/models/repository.rb
+2
-2
app/services/merge_requests/ff_merge_service.rb
app/services/merge_requests/ff_merge_service.rb
+1
-1
app/services/merge_requests/merge_service.rb
app/services/merge_requests/merge_service.rb
+20
-4
app/services/merge_requests/rebase_service.rb
app/services/merge_requests/rebase_service.rb
+25
-74
app/services/merge_requests/squash_service.rb
app/services/merge_requests/squash_service.rb
+69
-0
app/services/merge_requests/working_copy_base_service.rb
app/services/merge_requests/working_copy_base_service.rb
+53
-0
app/views/projects/merge_requests/widget/open/_accept.html.haml
...ews/projects/merge_requests/widget/open/_accept.html.haml
+8
-2
app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
...requests/widget/open/_merge_when_build_succeeds.html.haml
+6
-4
app/views/shared/issuable/_form.html.haml
app/views/shared/issuable/_form.html.haml
+2
-0
app/views/shared/issuable/form/_branch_chooser.html.haml
app/views/shared/issuable/form/_branch_chooser.html.haml
+0
-9
app/views/shared/issuable/form/_merge_params.html.haml
app/views/shared/issuable/form/_merge_params.html.haml
+20
-0
changelogs/unreleased-ee/squash.yml
changelogs/unreleased-ee/squash.yml
+4
-0
db/migrate/20161230123835_add_squash_to_merge_requests.rb
db/migrate/20161230123835_add_squash_to_merge_requests.rb
+14
-0
db/schema.rb
db/schema.rb
+1
-0
doc/api/merge_requests.md
doc/api/merge_requests.md
+23
-1
doc/user/project/merge_requests.md
doc/user/project/merge_requests.md
+7
-0
doc/user/project/merge_requests/img/squash_edit_form.png
doc/user/project/merge_requests/img/squash_edit_form.png
+0
-0
doc/user/project/merge_requests/img/squash_mr_commits.png
doc/user/project/merge_requests/img/squash_mr_commits.png
+0
-0
doc/user/project/merge_requests/img/squash_mr_widget.png
doc/user/project/merge_requests/img/squash_mr_widget.png
+0
-0
doc/user/project/merge_requests/img/squash_squashed_commit.png
...ser/project/merge_requests/img/squash_squashed_commit.png
+0
-0
doc/user/project/merge_requests/squash_and_merge.md
doc/user/project/merge_requests/squash_and_merge.md
+56
-0
lib/api/entities.rb
lib/api/entities.rb
+1
-0
lib/api/merge_requests.rb
lib/api/merge_requests.rb
+7
-1
spec/controllers/projects/merge_requests_controller_spec.rb
spec/controllers/projects/merge_requests_controller_spec.rb
+21
-2
spec/features/merge_requests/squash_spec.rb
spec/features/merge_requests/squash_spec.rb
+107
-0
spec/lib/gitlab/diff/position_tracer_spec.rb
spec/lib/gitlab/diff/position_tracer_spec.rb
+3
-1
spec/lib/gitlab/import_export/safe_model_attributes.yml
spec/lib/gitlab/import_export/safe_model_attributes.yml
+1
-0
spec/models/merge_request_spec.rb
spec/models/merge_request_spec.rb
+38
-7
spec/models/repository_spec.rb
spec/models/repository_spec.rb
+11
-3
spec/requests/api/merge_requests_spec.rb
spec/requests/api/merge_requests_spec.rb
+19
-1
spec/services/merge_requests/refresh_service_spec.rb
spec/services/merge_requests/refresh_service_spec.rb
+2
-1
spec/services/merge_requests/squash_service_spec.rb
spec/services/merge_requests/squash_service_spec.rb
+150
-0
spec/support/test_env.rb
spec/support/test_env.rb
+2
-1
No files found.
app/assets/stylesheets/pages/merge_requests.scss
View file @
f141afab
...
@@ -56,15 +56,24 @@
...
@@ -56,15 +56,24 @@
&
.right
{
&
.right
{
float
:
right
;
float
:
right
;
padding-right
:
0
;
padding-right
:
0
;
}
a
{
.modify-merge-commit-link
{
color
:
$gl-text-color
;
color
:
$gl-text-color
;
}
}
}
.
remove_source_
checkbox
{
.
merge-param-
checkbox
{
margin
:
0
;
margin
:
0
;
}
}
a
.fa-question-circle
{
color
:
$gl-text-color-secondary
;
&
:hover
,
&
:focus
{
color
:
$link-hover-color
;
}
}
}
}
}
}
...
...
app/controllers/projects/merge_requests_controller.rb
View file @
f141afab
...
@@ -338,7 +338,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
...
@@ -338,7 +338,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return
return
end
end
@merge_request
.
update
(
merge_error:
nil
)
@merge_request
.
update
(
merge_error:
nil
,
squash:
merge_params
[
:squash
]
)
if
params
[
:merge_when_build_succeeds
].
present?
if
params
[
:merge_when_build_succeeds
].
present?
unless
@merge_request
.
head_pipeline
unless
@merge_request
.
head_pipeline
...
@@ -699,6 +699,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
...
@@ -699,6 +699,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
approvals_before_merge
approvals_before_merge
approver_group_ids
approver_group_ids
approver_ids
approver_ids
squash
]
]
end
end
...
@@ -716,7 +717,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
...
@@ -716,7 +717,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
def
merge_params
def
merge_params
params
.
permit
(
:should_remove_source_branch
,
:commit_message
)
params
.
permit
(
:should_remove_source_branch
,
:commit_message
,
:squash
)
end
end
# Make sure merge requests created before 8.0
# Make sure merge requests created before 8.0
...
...
app/helpers/merge_requests_helper.rb
View file @
f141afab
...
@@ -181,4 +181,16 @@ module MergeRequestsHelper
...
@@ -181,4 +181,16 @@ module MergeRequestsHelper
def
different_base?
(
version1
,
version2
)
def
different_base?
(
version1
,
version2
)
version1
&&
version2
&&
version1
.
base_commit_sha
!=
version2
.
base_commit_sha
version1
&&
version2
&&
version1
.
base_commit_sha
!=
version2
.
base_commit_sha
end
end
def
merge_params
(
merge_request
)
{
merge_when_build_succeeds:
true
,
should_remove_source_branch:
true
,
sha:
merge_request
.
diff_head_sha
}.
merge
(
merge_params_ee
(
merge_request
))
end
def
merge_params_ee
(
merge_request
)
{
squash:
merge_request
.
squash
}
end
end
end
app/models/merge_request.rb
View file @
f141afab
...
@@ -812,6 +812,10 @@ class MergeRequest < ActiveRecord::Base
...
@@ -812,6 +812,10 @@ class MergeRequest < ActiveRecord::Base
File
.
join
(
Gitlab
.
config
.
shared
.
path
,
'tmp/rebase'
,
source_project
.
id
.
to_s
,
id
.
to_s
).
to_s
File
.
join
(
Gitlab
.
config
.
shared
.
path
,
'tmp/rebase'
,
source_project
.
id
.
to_s
,
id
.
to_s
).
to_s
end
end
def
squash_dir_path
File
.
join
(
Gitlab
.
config
.
shared
.
path
,
'tmp/squash'
,
source_project
.
id
.
to_s
,
id
.
to_s
).
to_s
end
def
rebase_in_progress?
def
rebase_in_progress?
# The source project can be deleted
# The source project can be deleted
return
false
unless
source_project
return
false
unless
source_project
...
@@ -820,14 +824,26 @@ class MergeRequest < ActiveRecord::Base
...
@@ -820,14 +824,26 @@ class MergeRequest < ActiveRecord::Base
end
end
def
clean_stuck_rebase
def
clean_stuck_rebase
expiration_time
=
Time
.
now
-
15
.
minutes
if
File
.
mtime
(
rebase_dir_path
)
<
15
.
minutes
.
ago
if
File
.
new
(
rebase_dir_path
).
mtime
<
expiration_time
FileUtils
.
rm_rf
(
rebase_dir_path
)
FileUtils
.
rm_rf
(
rebase_dir_path
)
true
true
end
end
end
end
def
squash_in_progress?
# The source project can be deleted
return
false
unless
source_project
File
.
exist?
(
squash_dir_path
)
&&
!
clean_stuck_squash
end
def
clean_stuck_squash
if
File
.
mtime
(
squash_dir_path
)
<
15
.
minutes
.
ago
FileUtils
.
rm_rf
(
squash_dir_path
)
true
end
end
def
diverged_commits_count
def
diverged_commits_count
cache
=
Rails
.
cache
.
read
(
:"merge_request_
#{
id
}
_diverged_commits"
)
cache
=
Rails
.
cache
.
read
(
:"merge_request_
#{
id
}
_diverged_commits"
)
...
...
app/models/repository.rb
View file @
f141afab
...
@@ -1027,9 +1027,9 @@ class Repository
...
@@ -1027,9 +1027,9 @@ class Repository
end
end
end
end
def
merge
(
user
,
merge_request
,
options
=
{})
def
merge
(
user
,
source
,
merge_request
,
options
=
{})
our_commit
=
rugged
.
branches
[
merge_request
.
target_branch
].
target
our_commit
=
rugged
.
branches
[
merge_request
.
target_branch
].
target
their_commit
=
rugged
.
lookup
(
merge_request
.
diff_head_sha
)
their_commit
=
rugged
.
lookup
(
source
)
raise
"Invalid merge target"
if
our_commit
.
nil?
raise
"Invalid merge target"
if
our_commit
.
nil?
raise
"Invalid merge source"
if
their_commit
.
nil?
raise
"Invalid merge source"
if
their_commit
.
nil?
...
...
app/services/merge_requests/ff_merge_service.rb
View file @
f141afab
...
@@ -10,7 +10,7 @@ module MergeRequests
...
@@ -10,7 +10,7 @@ module MergeRequests
def
commit
def
commit
repository
.
ff_merge
(
current_user
,
repository
.
ff_merge
(
current_user
,
merge_request
.
diff_head_sha
,
source
,
merge_request
.
target_branch
,
merge_request
.
target_branch
,
merge_request:
merge_request
)
merge_request:
merge_request
)
ensure
ensure
...
...
app/services/merge_requests/merge_service.rb
View file @
f141afab
...
@@ -6,7 +6,7 @@ module MergeRequests
...
@@ -6,7 +6,7 @@ module MergeRequests
# Executed when you do merge via GitLab UI
# Executed when you do merge via GitLab UI
#
#
class
MergeService
<
MergeRequests
::
BaseService
class
MergeService
<
MergeRequests
::
BaseService
attr_reader
:merge_request
attr_reader
:merge_request
,
:source
def
execute
(
merge_request
)
def
execute
(
merge_request
)
if
project
.
merge_requests_ff_only_enabled
&&
!
self
.
is_a?
(
FfMergeService
)
if
project
.
merge_requests_ff_only_enabled
&&
!
self
.
is_a?
(
FfMergeService
)
...
@@ -24,6 +24,10 @@ module MergeRequests
...
@@ -24,6 +24,10 @@ module MergeRequests
return
error
(
message
)
return
error
(
message
)
end
end
@source
=
find_merge_source
return
log_merge_error
(
'No source for merge'
,
true
)
unless
@source
merge_request
.
in_locked_state
do
merge_request
.
in_locked_state
do
if
commit
if
commit
after_merge
after_merge
...
@@ -64,7 +68,7 @@ module MergeRequests
...
@@ -64,7 +68,7 @@ module MergeRequests
committer:
committer
committer:
committer
}
}
commit_id
=
repository
.
merge
(
current_user
,
merge_request
,
options
)
commit_id
=
repository
.
merge
(
current_user
,
source
,
merge_request
,
options
)
if
commit_id
if
commit_id
merge_request
.
update
(
merge_commit_sha:
commit_id
)
merge_request
.
update
(
merge_commit_sha:
commit_id
)
...
@@ -103,9 +107,21 @@ module MergeRequests
...
@@ -103,9 +107,21 @@ module MergeRequests
end
end
def
merge_request_info
def
merge_request_info
project
=
merge_request
.
project
merge_request
.
to_reference
(
full:
true
)
end
def
find_merge_source
return
merge_request
.
diff_head_sha
unless
merge_request
.
squash
"
#{
project
.
to_reference
}#{
merge_request
.
to_reference
}
"
squash_result
=
SquashService
.
new
(
project
,
current_user
,
params
).
execute
(
merge_request
)
if
squash_result
[
:status
]
==
:success
squash_result
[
:squash_sha
]
else
log_merge_error
(
"Squashing
#{
merge_request_info
}
failed"
)
nil
end
end
end
end
end
end
end
app/services/merge_requests/rebase_service.rb
View file @
f141afab
module
MergeRequests
module
MergeRequests
# MergeService class
class
RebaseService
<
MergeRequests
::
WorkingCopyBaseService
#
# Do git merge and in case of success
# mark merge request as merged and execute all hooks and notifications
# Executed when you do merge via GitLab UI
#
class
RebaseService
<
MergeRequests
::
BaseService
include
Gitlab
::
Popen
attr_reader
:merge_request
def
execute
(
merge_request
)
def
execute
(
merge_request
)
@merge_request
=
merge_request
@merge_request
=
merge_request
...
@@ -22,92 +12,53 @@ module MergeRequests
...
@@ -22,92 +12,53 @@ module MergeRequests
def
rebase
def
rebase
if
merge_request
.
rebase_in_progress?
if
merge_request
.
rebase_in_progress?
log
(
'Rebase task canceled: Another rebase is already in progress'
)
log
_error
(
'Rebase task canceled: Another rebase is already in progress'
)
return
false
return
false
end
end
# Clone
run_git_command
(
output
,
status
=
popen
(
%W(clone -b
#{
merge_request
.
source_branch
}
--
#{
source_project
.
repository
.
path_to_repo
}
#{
tree_path
}
)
,
%W(git clone -b
#{
merge_request
.
source_branch
}
--
#{
source_project
.
repository
.
path_to_repo
}
#{
tree_path
}
)
,
nil
,
nil
,
git_env
git_env
,
'clone repository for rebase'
)
)
unless
status
.
zero?
run_git_command
(
log
(
'Failed to clone repository for rebase:'
)
%W(pull --rebase
#{
target_project
.
repository
.
path_to_repo
}
#{
merge_request
.
target_branch
}
)
,
log
(
output
)
return
false
end
# Rebase
output
,
status
=
popen
(
%W(git pull --rebase
#{
target_project
.
repository
.
path_to_repo
}
#{
merge_request
.
target_branch
}
)
,
tree_path
,
tree_path
,
git_env
git_env
,
'rebase branch'
)
)
unless
status
.
zero?
rebase_sha
=
run_git_command
(
log
(
'Failed to rebase branch:'
)
%W(rev-parse
#{
merge_request
.
source_branch
}
)
,
log
(
output
)
return
false
end
output
,
status
=
popen
(
%W(git rev-parse
#{
merge_request
.
source_branch
}
)
,
tree_path
,
tree_path
,
git_env
git_env
,
'get SHA of rebased branch'
)
)
unless
status
.
zero?
merge_request
.
update_attributes
(
rebase_commit_sha:
rebase_sha
)
log
(
'Failed to get SHA of rebased branch:'
)
log
(
output
)
return
false
end
merge_request
.
update_attributes
(
rebase_commit_sha:
output
.
chomp
)
run_git_command
(
# Push
%W(push -f origin
#{
merge_request
.
source_branch
}
)
,
output
,
status
=
popen
(
%W(git push -f origin
#{
merge_request
.
source_branch
}
)
,
tree_path
,
tree_path
,
git_env
git_env
,
'push rebased branch'
)
)
unless
status
.
zero?
log
(
'Failed to push rebased branch:'
)
log
(
output
)
return
false
end
true
true
rescue
=>
ex
rescue
GitCommandError
log
(
'Failed to rebase branch:'
)
false
log
(
ex
.
message
)
rescue
=>
e
log_error
(
'Failed to rebase branch:'
)
log_error
(
e
)
false
ensure
ensure
clean_dir
clean_dir
end
end
def
source_project
@source_project
||=
merge_request
.
source_project
end
def
target_project
@target_project
||=
merge_request
.
target_project
end
def
tree_path
def
tree_path
@tree_path
||=
merge_request
.
rebase_dir_path
@tree_path
||=
merge_request
.
rebase_dir_path
end
end
def
log
(
message
)
Gitlab
::
GitLogger
.
error
(
message
)
end
def
clean_dir
FileUtils
.
rm_rf
(
tree_path
)
if
File
.
exist?
(
tree_path
)
end
def
git_env
{
'GL_ID'
=>
Gitlab
::
GlId
.
gl_id
(
current_user
),
'GL_PROTOCOL'
=>
'web'
}
end
end
end
end
end
app/services/merge_requests/squash_service.rb
0 → 100644
View file @
f141afab
require
'securerandom'
module
MergeRequests
class
SquashService
<
MergeRequests
::
WorkingCopyBaseService
attr_reader
:repository
,
:rugged
def
execute
(
merge_request
)
@merge_request
=
merge_request
@repository
=
target_project
.
repository
@rugged
=
repository
.
rugged
squash
||
error
(
'Failed to squash. Should be done manually'
)
end
def
squash
if
merge_request
.
commits_count
<=
1
return
success
(
squash_sha:
merge_request
.
diff_head_sha
)
end
if
merge_request
.
squash_in_progress?
log_error
(
'Squash task canceled: Another squash is already in progress'
)
return
false
end
run_git_command
(
%W(worktree add
#{
tree_path
}
#{
merge_request
.
target_branch
}
--detach)
,
repository
.
path_to_repo
,
git_env
,
'add worktree for squash'
)
run_git_command
(
%w(apply --cached)
,
tree_path
,
git_env
,
'apply patch'
)
do
|
stdin
|
stdin
.
puts
(
merge_request_to_patch
)
end
run_git_command
(
%W(commit -C
#{
merge_request
.
diff_head_sha
}
)
,
tree_path
,
git_env
.
merge
(
'GIT_COMMITTER_NAME'
=>
current_user
.
name
,
'GIT_COMMITTER_EMAIL'
=>
current_user
.
email
),
'commit squashed changes'
)
squash_sha
=
run_git_command
(
%w(rev-parse HEAD)
,
tree_path
,
git_env
,
'get SHA of squashed commit'
)
success
(
squash_sha:
squash_sha
)
rescue
GitCommandError
false
rescue
=>
e
log_error
(
"Failed to squash merge request
#{
merge_request
.
to_reference
(
full:
true
)
}
:"
)
log_error
(
e
.
message
)
false
ensure
clean_dir
end
def
tree_path
@tree_path
||=
merge_request
.
squash_dir_path
end
def
merge_request_to_patch
@merge_request_to_patch
||=
rugged
.
diff
(
merge_request
.
diff_base_sha
,
merge_request
.
diff_head_sha
).
patch
end
end
end
app/services/merge_requests/working_copy_base_service.rb
0 → 100644
View file @
f141afab
module
MergeRequests
class
WorkingCopyBaseService
<
MergeRequests
::
BaseService
class
GitCommandError
<
StandardError
;
end
include
Gitlab
::
Popen
attr_reader
:merge_request
def
run_git_command
(
command
,
path
,
env
,
message
=
nil
,
&
block
)
git_command
=
[
Gitlab
.
config
.
git
.
bin_path
]
+
command
output
,
status
=
popen
(
git_command
,
path
,
env
,
&
block
)
unless
status
.
zero?
if
message
log_error
(
"Failed to
#{
message
}
with `
#{
git_command
.
join
(
' '
)
}
`:"
)
else
log_error
(
"`
#{
git_command
.
join
(
' '
)
}
` failed:"
)
end
log_error
(
output
)
raise
GitCommandError
end
output
.
chomp
end
def
source_project
@source_project
||=
merge_request
.
source_project
end
def
target_project
@target_project
||=
merge_request
.
target_project
end
def
log_error
(
message
)
Gitlab
::
GitLogger
.
error
(
message
)
end
def
clean_dir
FileUtils
.
rm_rf
(
tree_path
)
if
File
.
exist?
(
tree_path
)
end
def
git_env
{
'GL_ID'
=>
Gitlab
::
GlId
.
gl_id
(
current_user
),
'GL_PROTOCOL'
=>
'web'
}
end
# Don't try to print expensive instance variables.
def
inspect
"#<
#{
self
.
class
}
#{
merge_request
.
to_reference
(
full:
true
)
}
>"
end
end
end
app/views/projects/merge_requests/widget/open/_accept.html.haml
View file @
f141afab
...
@@ -35,11 +35,17 @@
...
@@ -35,11 +35,17 @@
The source branch will be removed.
The source branch will be removed.
-
elsif
@merge_request
.
can_remove_source_branch?
(
current_user
)
-
elsif
@merge_request
.
can_remove_source_branch?
(
current_user
)
.accept-control.checkbox
.accept-control.checkbox
=
label_tag
:should_remove_source_branch
,
class:
"
remove_source_
checkbox"
do
=
label_tag
:should_remove_source_branch
,
class:
"
merge-param-
checkbox"
do
=
check_box_tag
:should_remove_source_branch
=
check_box_tag
:should_remove_source_branch
Remove source branch
Remove source branch
.accept-control.checkbox
=
label_tag
:squash
,
class:
'merge-param-checkbox'
do
=
hidden_field_tag
:squash
,
'0'
,
id:
nil
=
check_box_tag
:squash
,
'1'
,
@merge_request
.
squash
Squash commits
=
link_to
icon
(
'question-circle'
),
help_page_path
(
'user/project/merge_requests/squash_and_merge'
),
title:
'About this feature'
,
data:
{
toggle:
'tooltip'
,
placement:
'bottom'
,
container:
'body'
}
.accept-control
.right
.accept-control
-
if
@project
.
merge_requests_ff_only_enabled
-
if
@project
.
merge_requests_ff_only_enabled
Fast-forward merge without a merge commit
Fast-forward merge without a merge commit
-
else
-
else
...
...
app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
View file @
f141afab
...
@@ -7,10 +7,12 @@
...
@@ -7,10 +7,12 @@
%div
%div
%p
%p
=
succeed
'.'
do
=
succeed
'.'
do
The changes will be
-
if
@merge_request
.
squash
squashed and
-
if
@project
.
merge_requests_ff_only_enabled
-
if
@project
.
merge_requests_ff_only_enabled
The changes will be fast-forward merged into
fast-forward
-
else
merged into
The changes will be merged into
%span
.label-branch
=
@merge_request
.
target_branch
%span
.label-branch
=
@merge_request
.
target_branch
-
if
@merge_request
.
remove_source_branch?
-
if
@merge_request
.
remove_source_branch?
The source branch will be removed.
The source branch will be removed.
...
@@ -22,7 +24,7 @@
...
@@ -22,7 +24,7 @@
-
if
remove_source_branch_button
||
user_can_cancel_automatic_merge
-
if
remove_source_branch_button
||
user_can_cancel_automatic_merge
.clearfix.prepend-top-10
.clearfix.prepend-top-10
-
if
remove_source_branch_button
-
if
remove_source_branch_button
=
link_to
merge_namespace_project_merge_request_path
(
@merge_request
.
target_project
.
namespace
,
@merge_request
.
target_project
,
@merge_request
,
merge_
when_build_succeeds:
true
,
should_remove_source_branch:
true
,
sha:
@merge_request
.
diff_head_sha
),
remote:
true
,
method: :post
,
class:
"btn btn-grouped btn-primary btn-sm remove_source_branch"
do
=
link_to
merge_namespace_project_merge_request_path
(
@merge_request
.
target_project
.
namespace
,
@merge_request
.
target_project
,
@merge_request
,
merge_
params
(
@merge_request
)
),
remote:
true
,
method: :post
,
class:
"btn btn-grouped btn-primary btn-sm remove_source_branch"
do
=
icon
(
'times'
)
=
icon
(
'times'
)
Remove Source Branch When Merged
Remove Source Branch When Merged
...
...
app/views/shared/issuable/_form.html.haml
View file @
f141afab
...
@@ -45,6 +45,8 @@
...
@@ -45,6 +45,8 @@
=
render
'shared/issuable/form/branch_chooser'
,
issuable:
issuable
,
form:
form
=
render
'shared/issuable/form/branch_chooser'
,
issuable:
issuable
,
form:
form
=
render
'shared/issuable/form/merge_params'
,
issuable:
issuable
-
if
@merge_request_for_resolving_discussions
-
if
@merge_request_for_resolving_discussions
.form-group
.form-group
.col-sm-10.col-sm-offset-2
.col-sm-10.col-sm-offset-2
...
...
app/views/shared/issuable/form/_branch_chooser.html.haml
View file @
f141afab
...
@@ -19,12 +19,3 @@
...
@@ -19,12 +19,3 @@
-
if
issuable
.
new_record?
-
if
issuable
.
new_record?
=
link_to
'Change branches'
,
mr_change_branches_path
(
issuable
)
=
link_to
'Change branches'
,
mr_change_branches_path
(
issuable
)
-
if
issuable
.
can_remove_source_branch?
(
current_user
)
.form-group
.col-sm-10.col-sm-offset-2
.checkbox
=
label_tag
'merge_request[force_remove_source_branch]'
do
=
hidden_field_tag
'merge_request[force_remove_source_branch]'
,
'0'
,
id:
nil
=
check_box_tag
'merge_request[force_remove_source_branch]'
,
'1'
,
issuable
.
force_remove_source_branch?
Remove source branch when merge request is accepted.
app/views/shared/issuable/form/_merge_params.html.haml
0 → 100644
View file @
f141afab
-
issuable
=
local_assigns
.
fetch
(
:issuable
)
-
return
unless
issuable
.
is_a?
(
MergeRequest
)
-
return
if
issuable
.
closed_without_fork?
.form-group
.col-sm-10.col-sm-offset-2
-
if
issuable
.
can_remove_source_branch?
(
current_user
)
.checkbox
=
label_tag
'merge_request[force_remove_source_branch]'
do
=
hidden_field_tag
'merge_request[force_remove_source_branch]'
,
'0'
,
id:
nil
=
check_box_tag
'merge_request[force_remove_source_branch]'
,
'1'
,
issuable
.
force_remove_source_branch?
Remove source branch when merge request is accepted.
.checkbox
=
label_tag
'merge_request[squash]'
do
=
hidden_field_tag
'merge_request[squash]'
,
'0'
,
id:
nil
=
check_box_tag
'merge_request[squash]'
,
'1'
,
issuable
.
squash
Squash commits when merge request is accepted.
=
link_to
'About this feature'
,
help_page_path
(
'user/project/merge_requests/squash_and_merge'
)
changelogs/unreleased-ee/squash.yml
0 → 100644
View file @
f141afab
---
title
:
Allow squashing merge requests into a single commit
merge_request
:
author
:
db/migrate/20161230123835_add_squash_to_merge_requests.rb
0 → 100644
View file @
f141afab
class
AddSquashToMergeRequests
<
ActiveRecord
::
Migration
include
Gitlab
::
Database
::
MigrationHelpers
disable_ddl_transaction!
DOWNTIME
=
false
def
up
add_column_with_default
:merge_requests
,
:squash
,
:boolean
,
default:
false
,
allow_null:
false
end
def
down
remove_column
:merge_requests
,
:squash
end
end
db/schema.rb
View file @
f141afab
...
@@ -782,6 +782,7 @@ ActiveRecord::Schema.define(version: 20170121130655) do
...
@@ -782,6 +782,7 @@ ActiveRecord::Schema.define(version: 20170121130655) do
t
.
text
"title_html"
t
.
text
"title_html"
t
.
text
"description_html"
t
.
text
"description_html"
t
.
integer
"time_estimate"
t
.
integer
"time_estimate"
t
.
boolean
"squash"
,
default:
false
,
null:
false
end
end
add_index
"merge_requests"
,
[
"assignee_id"
],
name:
"index_merge_requests_on_assignee_id"
,
using: :btree
add_index
"merge_requests"
,
[
"assignee_id"
],
name:
"index_merge_requests_on_assignee_id"
,
using: :btree
...
...
doc/api/merge_requests.md
View file @
f141afab
...
@@ -75,6 +75,7 @@ Parameters:
...
@@ -75,6 +75,7 @@ Parameters:
"approvals_before_merge"
:
null
"approvals_before_merge"
:
null
"should_remove_source_branch"
:
true
,
"should_remove_source_branch"
:
true
,
"force_remove_source_branch"
:
false
,
"force_remove_source_branch"
:
false
,
"squash"
:
false
,
"web_url"
:
"http://example.com/example/example/merge_requests/1"
"web_url"
:
"http://example.com/example/example/merge_requests/1"
}
}
]
]
...
@@ -145,6 +146,7 @@ Parameters:
...
@@ -145,6 +146,7 @@ Parameters:
"approvals_before_merge"
:
null
"approvals_before_merge"
:
null
"should_remove_source_branch"
:
true
,
"should_remove_source_branch"
:
true
,
"force_remove_source_branch"
:
false
,
"force_remove_source_branch"
:
false
,
"squash"
:
false
,
"web_url"
:
"http://example.com/example/example/merge_requests/1"
"web_url"
:
"http://example.com/example/example/merge_requests/1"
}
}
```
```
...
@@ -251,6 +253,7 @@ Parameters:
...
@@ -251,6 +253,7 @@ Parameters:
"approvals_before_merge"
:
null
,
"approvals_before_merge"
:
null
,
"should_remove_source_branch"
:
true
,
"should_remove_source_branch"
:
true
,
"force_remove_source_branch"
:
false
,
"force_remove_source_branch"
:
false
,
"squash"
:
false
,
"web_url"
:
"http://example.com/example/example/merge_requests/1"
,
"web_url"
:
"http://example.com/example/example/merge_requests/1"
,
"changes"
:
[
"changes"
:
[
{
{
...
@@ -287,6 +290,7 @@ POST /projects/:id/merge_requests
...
@@ -287,6 +290,7 @@ POST /projects/:id/merge_requests
|
`milestone_id`
| integer | no | The ID of a milestone |
|
`milestone_id`
| integer | no | The ID of a milestone |
|
`remove_source_branch`
| boolean | no | Flag indicating if a merge request should remove the source branch when merging |
|
`remove_source_branch`
| boolean | no | Flag indicating if a merge request should remove the source branch when merging |
|
`approvals_before_merge`
| integer| no | Number of approvals required before this can be merged (see below) |
|
`approvals_before_merge`
| integer| no | Number of approvals required before this can be merged (see below) |
|
`squash`
| boolean| no | Squash commits into a single commit when merging |
If
`approvals_before_merge`
is not provided, it inherits the value from the
If
`approvals_before_merge`
is not provided, it inherits the value from the
target project. If it is provided, then the following conditions must hold in
target project. If it is provided, then the following conditions must hold in
...
@@ -349,6 +353,7 @@ order for it to take effect:
...
@@ -349,6 +353,7 @@ order for it to take effect:
"approvals_before_merge"
:
null
"approvals_before_merge"
:
null
"should_remove_source_branch"
:
true
,
"should_remove_source_branch"
:
true
,
"force_remove_source_branch"
:
false
,
"force_remove_source_branch"
:
false
,
"squash"
:
false
,
"web_url"
:
"http://example.com/example/example/merge_requests/1"
"web_url"
:
"http://example.com/example/example/merge_requests/1"
}
}
```
```
...
@@ -374,6 +379,7 @@ PUT /projects/:id/merge_requests/:merge_request_id
...
@@ -374,6 +379,7 @@ PUT /projects/:id/merge_requests/:merge_request_id
|
`labels`
| string | no | Labels for MR as a comma-separated list |
|
`labels`
| string | no | Labels for MR as a comma-separated list |
|
`milestone_id`
| integer | no | The ID of a milestone |
|
`milestone_id`
| integer | no | The ID of a milestone |
|
`remove_source_branch`
| boolean | no | Flag indicating if a merge request should remove the source branch when merging |
|
`remove_source_branch`
| boolean | no | Flag indicating if a merge request should remove the source branch when merging |
|
`squash`
| boolean| no | Squash commits into a single commit when merging |
```
json
```
json
{
{
...
@@ -426,6 +432,7 @@ PUT /projects/:id/merge_requests/:merge_request_id
...
@@ -426,6 +432,7 @@ PUT /projects/:id/merge_requests/:merge_request_id
"approvals_before_merge"
:
null
"approvals_before_merge"
:
null
"should_remove_source_branch"
:
true
,
"should_remove_source_branch"
:
true
,
"force_remove_source_branch"
:
false
,
"force_remove_source_branch"
:
false
,
"squash"
:
false
,
"web_url"
:
"http://example.com/example/example/merge_requests/1"
"web_url"
:
"http://example.com/example/example/merge_requests/1"
}
}
```
```
...
@@ -471,7 +478,19 @@ Parameters:
...
@@ -471,7 +478,19 @@ Parameters:
-
`merge_commit_message`
(optional) - Custom merge commit message
-
`merge_commit_message`
(optional) - Custom merge commit message
-
`should_remove_source_branch`
(optional) - if
`true`
removes the source branch
-
`should_remove_source_branch`
(optional) - if
`true`
removes the source branch
-
`merge_when_build_succeeds`
(optional) - if
`true`
the MR is merged when the build succeeds
-
`merge_when_build_succeeds`
(optional) - if
`true`
the MR is merged when the build succeeds
-
`sha`
(optional) - if present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail
-
`sha`
(optional) - if present, then this SHA must
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
|
`id`
| string | yes | The ID of a project |
|
`merge_request_id`
| integer | yes | The ID of the merge request |
|
`merge_commit_message`
| string | no | Custom merge commit message |
|
`should_remove_source_branch`
| boolean | no | Remove the source branch after merge |
|
`merge_when_build_succeeds`
| boolean | no | Merge when build succeeds, rather than immediately |
|
`sha`
| string | no | If present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail |
|
`squash`
| boolean | no | Squash the merge request into a single commit |
```
json
```
json
{
{
...
@@ -525,6 +544,7 @@ Parameters:
...
@@ -525,6 +544,7 @@ Parameters:
"approvals_before_merge"
:
null
"approvals_before_merge"
:
null
"should_remove_source_branch"
:
true
,
"should_remove_source_branch"
:
true
,
"force_remove_source_branch"
:
false
,
"force_remove_source_branch"
:
false
,
"squash"
:
false
,
"web_url"
:
"http://example.com/example/example/merge_requests/1"
"web_url"
:
"http://example.com/example/example/merge_requests/1"
}
}
```
```
...
@@ -698,6 +718,7 @@ Parameters:
...
@@ -698,6 +718,7 @@ Parameters:
"approvals_before_merge"
:
null
"approvals_before_merge"
:
null
"should_remove_source_branch"
:
true
,
"should_remove_source_branch"
:
true
,
"force_remove_source_branch"
:
false
,
"force_remove_source_branch"
:
false
,
"squash"
:
false
,
"web_url"
:
"http://example.com/example/example/merge_requests/1"
"web_url"
:
"http://example.com/example/example/merge_requests/1"
}
}
```
```
...
@@ -1021,6 +1042,7 @@ Example response:
...
@@ -1021,6 +1042,7 @@ Example response:
"user_notes_count"
:
7
,
"user_notes_count"
:
7
,
"should_remove_source_branch"
:
true
,
"should_remove_source_branch"
:
true
,
"force_remove_source_branch"
:
false
,
"force_remove_source_branch"
:
false
,
"squash"
:
true
,
"web_url"
:
"http://example.com/example/example/merge_requests/1"
"web_url"
:
"http://example.com/example/example/merge_requests/1"
},
},
"target_url"
:
"https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7"
,
"target_url"
:
"https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7"
,
...
...
doc/user/project/merge_requests.md
View file @
f141afab
...
@@ -36,6 +36,13 @@ you hide discussions that are no longer relevant.
...
@@ -36,6 +36,13 @@ you hide discussions that are no longer relevant.
[
Read more about resolving discussion comments in merge requests reviews.
](
merge_requests/merge_request_discussion_resolution.md
)
[
Read more about resolving discussion comments in merge requests reviews.
](
merge_requests/merge_request_discussion_resolution.md
)
## Squash and merge
GitLab allows you to squash all changes present in a merge request into a single
commit when merging, to allow for a neater commit history.
[
Learn more about squash and merge.
](
merge_requests/squash_and_merge
)
## Resolve conflicts
## Resolve conflicts
When a merge request has conflicts, GitLab may provide the option to resolve
When a merge request has conflicts, GitLab may provide the option to resolve
...
...
doc/user/project/merge_requests/img/squash_edit_form.png
0 → 100644
View file @
f141afab
28 KB
doc/user/project/merge_requests/img/squash_mr_commits.png
0 → 100644
View file @
f141afab
109 KB
doc/user/project/merge_requests/img/squash_mr_widget.png
0 → 100644
View file @
f141afab
19 KB
doc/user/project/merge_requests/img/squash_squashed_commit.png
0 → 100644
View file @
f141afab
73.8 KB
doc/user/project/merge_requests/squash_and_merge.md
0 → 100644
View file @
f141afab
# Squash and merge
> [Introduced][ee-1024] in GitLab Enterprise Edition 8.16.
Squashing lets you tidy up the commit history of a branch when accepting a merge
request. It applies all of the changes in the merge request as a single commit,
and then merges that commit using the merge method set for the project.
In other words, squashing a merge request turns a long list of commits:
![
List of commits from a merge request
][
mr-commits
]
Into a single commit on merge:
![
A squashed commit followed by a merge commit
][
squashed-commit
]
Note that the squashed commit is still followed by a merge commit, as the merge
method for this example repository uses a merge commit. Squashing also works
with the fast-forward merge strategy: see
[
squashing and fast-forward merge
](
#squashing-and-fast-forward-merge
)
for more
details.
## Enabling squash for a merge request
Anyone who can create or edit a merge request can choose for it to be squashed
on the merge request form:
![
Squash commits checkbox on edit form
][
squash-edit-form
]
This can then be overridden at the time of accepting the merge request:
![
Squash commits checkbox on accept merge request form
][
squash-mr-widget
]
## Commit metadata for squashed commits
The squashed commit has the following metadata:
*
Message: taken from the last commit in the source branch.
*
Author: taken from the last commit in the source branch.
*
Committer: the user who initiated the squash.
## Squashing and [fast-forward merge][ff-merge]
When a project has the fast-forward merge setting enabled, the merge request
must be able to be fast-forwarded without squashing in order to squash it. This
is because squashing is only available when accepting a merge request, so a
merge request may need to be
[
rebased
][
rebase
]
before squashing, even though
squashing can itself be considered equivalent to rebasing.
[
ee-1024
]:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1024
[
mr-commits
]:
img/squash_mr_commits.png
[
squashed-commit
]:
img/squash_squashed_commit.png
[
squash-edit-form
]:
img/squash_edit_form.png
[
squash-mr-widget
]:
img/squash_mr_widget.png
[
ff-merge
]:
./fast_forward_merge
[
rebase
]:
../../../workflow/rebase_before_merge
lib/api/entities.rb
View file @
f141afab
...
@@ -326,6 +326,7 @@ module API
...
@@ -326,6 +326,7 @@ module API
expose
:approvals_before_merge
expose
:approvals_before_merge
expose
:should_remove_source_branch?
,
as: :should_remove_source_branch
expose
:should_remove_source_branch?
,
as: :should_remove_source_branch
expose
:force_remove_source_branch?
,
as: :force_remove_source_branch
expose
:force_remove_source_branch?
,
as: :force_remove_source_branch
expose
:squash
expose
:web_url
do
|
merge_request
,
options
|
expose
:web_url
do
|
merge_request
,
options
|
Gitlab
::
UrlBuilder
.
build
(
merge_request
)
Gitlab
::
UrlBuilder
.
build
(
merge_request
)
...
...
lib/api/merge_requests.rb
View file @
f141afab
...
@@ -34,6 +34,7 @@ module API
...
@@ -34,6 +34,7 @@ module API
optional
:labels
,
type:
String
,
desc:
'Comma-separated list of label names'
optional
:labels
,
type:
String
,
desc:
'Comma-separated list of label names'
optional
:approvals_before_merge
,
type:
Integer
,
desc:
'Number of approvals required before this can be merged'
optional
:approvals_before_merge
,
type:
Integer
,
desc:
'Number of approvals required before this can be merged'
optional
:remove_source_branch
,
type:
Boolean
,
desc:
'Remove source branch when merging'
optional
:remove_source_branch
,
type:
Boolean
,
desc:
'Remove source branch when merging'
optional
:squash
,
type:
Boolean
,
desc:
'Squash commits when merging'
end
end
end
end
...
@@ -153,7 +154,7 @@ module API
...
@@ -153,7 +154,7 @@ module API
use
:optional_params
use
:optional_params
at_least_one_of
:title
,
:target_branch
,
:description
,
:assignee_id
,
at_least_one_of
:title
,
:target_branch
,
:description
,
:assignee_id
,
:milestone_id
,
:labels
,
:state_event
,
:approvals_before_merge
,
:milestone_id
,
:labels
,
:state_event
,
:approvals_before_merge
,
:remove_source_branch
:remove_source_branch
,
:squash
end
end
put
path
do
put
path
do
merge_request
=
find_merge_request_with_access
(
params
.
delete
(
:merge_request_id
),
:update_merge_request
)
merge_request
=
find_merge_request_with_access
(
params
.
delete
(
:merge_request_id
),
:update_merge_request
)
...
@@ -180,6 +181,7 @@ module API
...
@@ -180,6 +181,7 @@ module API
optional
:merge_when_build_succeeds
,
type:
Boolean
,
optional
:merge_when_build_succeeds
,
type:
Boolean
,
desc:
'When true, this merge request will be merged when the pipeline succeeds'
desc:
'When true, this merge request will be merged when the pipeline succeeds'
optional
:sha
,
type:
String
,
desc:
'When present, must have the HEAD SHA of the source branch'
optional
:sha
,
type:
String
,
desc:
'When present, must have the HEAD SHA of the source branch'
optional
:squash
,
type:
Boolean
,
desc:
'When true, the commits will be squashed into a single commit on merge'
end
end
put
"
#{
path
}
/merge"
do
put
"
#{
path
}
/merge"
do
merge_request
=
find_project_merge_request
(
params
[
:merge_request_id
])
merge_request
=
find_project_merge_request
(
params
[
:merge_request_id
])
...
@@ -196,6 +198,10 @@ module API
...
@@ -196,6 +198,10 @@ module API
render_api_error!
(
"SHA does not match HEAD of source branch:
#{
merge_request
.
diff_head_sha
}
"
,
409
)
render_api_error!
(
"SHA does not match HEAD of source branch:
#{
merge_request
.
diff_head_sha
}
"
,
409
)
end
end
if
params
[
:squash
]
merge_request
.
update
(
squash:
params
[
:squash
])
end
merge_params
=
{
merge_params
=
{
commit_message:
params
[
:merge_commit_message
],
commit_message:
params
[
:merge_commit_message
],
should_remove_source_branch:
params
[
:should_remove_source_branch
]
should_remove_source_branch:
params
[
:should_remove_source_branch
]
...
...
spec/controllers/projects/merge_requests_controller_spec.rb
View file @
f141afab
...
@@ -492,6 +492,7 @@ describe Projects::MergeRequestsController do
...
@@ -492,6 +492,7 @@ describe Projects::MergeRequestsController do
namespace_id:
project
.
namespace
.
path
,
namespace_id:
project
.
namespace
.
path
,
project_id:
project
.
path
,
project_id:
project
.
path
,
id:
merge_request
.
iid
,
id:
merge_request
.
iid
,
squash:
false
,
format:
'raw'
format:
'raw'
}
}
end
end
...
@@ -529,8 +530,26 @@ describe Projects::MergeRequestsController do
...
@@ -529,8 +530,26 @@ describe Projects::MergeRequestsController do
end
end
context
'when the sha parameter matches the source SHA'
do
context
'when the sha parameter matches the source SHA'
do
def
merge_with_sha
def
merge_with_sha
(
params
=
{})
post
:merge
,
base_params
.
merge
(
sha:
merge_request
.
diff_head_sha
)
post
:merge
,
base_params
.
merge
(
sha:
merge_request
.
diff_head_sha
).
merge
(
params
)
end
context
'when squash is passed as 1'
do
it
'updates the squash attribute on the MR to true'
do
merge_request
.
update
(
squash:
false
)
merge_with_sha
(
squash:
'1'
)
expect
(
merge_request
.
reload
.
squash
).
to
be_truthy
end
end
context
'when squash is passed as 1'
do
it
'updates the squash attribute on the MR to false'
do
merge_request
.
update
(
squash:
true
)
merge_with_sha
(
squash:
'0'
)
expect
(
merge_request
.
reload
.
squash
).
to
be_falsey
end
end
end
it
'returns :success'
do
it
'returns :success'
do
...
...
spec/features/merge_requests/squash_spec.rb
0 → 100644
View file @
f141afab
require
'spec_helper'
feature
'Squashing merge requests'
,
js:
true
,
feature:
true
do
include
WaitForAjax
let
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:project
)
}
let
(
:source_branch
)
{
'csv'
}
let!
(
:original_head
)
{
project
.
repository
.
commit
(
'master'
)
}
shared_examples
'squash'
do
it
'squashes the commits into a single commit, and adds a merge commit'
do
latest_master_commits
=
project
.
repository
.
commits_between
(
original_head
.
sha
,
'master'
).
map
(
&
:raw
)
last_mr_commit
=
project
.
repository
.
commit
(
source_branch
)
squash_commit
=
an_object_having_attributes
(
sha:
a_string_matching
(
/\h{40}/
),
message:
"
#{
last_mr_commit
.
message
}
\n
"
,
author_name:
last_mr_commit
.
author_name
,
committer_name:
user
.
name
)
merge_commit
=
an_object_having_attributes
(
sha:
a_string_matching
(
/\h{40}/
),
message:
a_string_starting_with
(
"Merge branch 'csv' into 'master'"
),
author_name:
user
.
name
,
committer_name:
user
.
name
)
expect
(
project
.
repository
).
not_to
be_merged_to_root_ref
(
source_branch
)
expect
(
latest_master_commits
).
to
match
([
squash_commit
,
merge_commit
])
end
end
shared_examples
'no squash'
do
it
'accepts the merge request without squashing'
do
expect
(
project
.
repository
).
to
be_merged_to_root_ref
(
source_branch
)
end
end
def
accept_mr
click_on
'Accept Merge Request'
wait_for_ajax
end
before
do
project
.
team
<<
[
user
,
:master
]
login_as
user
end
context
'when squash is enabled on merge request creation'
do
before
do
visit
new_namespace_project_merge_request_path
(
project
.
namespace
,
project
,
merge_request:
{
source_branch:
source_branch
})
check
'merge_request[squash]'
click_on
'Submit merge request'
wait_for_ajax
end
it
'shows the squash checkbox as checked'
do
expect
(
page
).
to
have_checked_field
(
'squash'
)
end
context
'when accepting with squash checked'
do
before
do
accept_mr
end
include_examples
'squash'
end
context
'when accepting and unchecking squash'
do
before
do
uncheck
'squash'
accept_mr
end
include_examples
'no squash'
end
end
context
'when squash is not enabled on merge request creation'
do
before
do
visit
new_namespace_project_merge_request_path
(
project
.
namespace
,
project
,
merge_request:
{
source_branch:
source_branch
})
click_on
'Submit merge request'
wait_for_ajax
end
it
'shows the squash checkbox as unchecked'
do
expect
(
page
).
to
have_unchecked_field
(
'squash'
)
end
context
'when accepting and checking squash'
do
before
do
check
'squash'
accept_mr
end
include_examples
'squash'
end
context
'when accepting with squash unchecked'
do
before
do
accept_mr
end
include_examples
'no squash'
end
end
end
spec/lib/gitlab/diff/position_tracer_spec.rb
View file @
f141afab
...
@@ -1640,7 +1640,9 @@ describe Gitlab::Diff::PositionTracer, lib: true do
...
@@ -1640,7 +1640,9 @@ describe Gitlab::Diff::PositionTracer, lib: true do
}
}
merge_request
=
create
(
:merge_request
,
source_branch:
second_create_file_commit
.
sha
,
target_branch:
branch_name
,
source_project:
project
)
merge_request
=
create
(
:merge_request
,
source_branch:
second_create_file_commit
.
sha
,
target_branch:
branch_name
,
source_project:
project
)
repository
.
merge
(
current_user
,
merge_request
,
options
)
repository
.
merge
(
current_user
,
merge_request
.
diff_head_sha
,
merge_request
,
options
)
project
.
commit
(
branch_name
)
project
.
commit
(
branch_name
)
end
end
...
...
spec/lib/gitlab/import_export/safe_model_attributes.yml
View file @
f141afab
...
@@ -154,6 +154,7 @@ MergeRequest:
...
@@ -154,6 +154,7 @@ MergeRequest:
-
approvals_before_merge
-
approvals_before_merge
-
rebase_commit_sha
-
rebase_commit_sha
-
time_estimate
-
time_estimate
-
squash
MergeRequestDiff
:
MergeRequestDiff
:
-
id
-
id
-
state
-
state
...
...
spec/models/merge_request_spec.rb
View file @
f141afab
...
@@ -788,36 +788,67 @@ describe MergeRequest, models: true do
...
@@ -788,36 +788,67 @@ describe MergeRequest, models: true do
end
end
describe
'#rebase_in_progress?'
do
describe
'#rebase_in_progress?'
do
it
'returns true'
do
it
'returns true
when there is a current rebase directory
'
do
allow
(
File
).
to
receive
(
:exist?
).
and_return
(
true
)
allow
(
File
).
to
receive
(
:exist?
).
and_return
(
true
)
allow
(
File
).
to
receive
(
:
new
).
and_return
(
double
(
:file
,
mtime:
Time
.
now
)
)
allow
(
File
).
to
receive
(
:
mtime
).
and_return
(
Time
.
now
)
expect
(
subject
.
rebase_in_progress?
).
to
be_truthy
expect
(
subject
.
rebase_in_progress?
).
to
be_truthy
end
end
it
'returns false'
do
it
'returns false
when there is no rebase directory
'
do
allow
(
File
).
to
receive
(
:exist?
).
with
(
subject
.
rebase_dir_path
).
and_return
(
false
)
allow
(
File
).
to
receive
(
:exist?
).
with
(
subject
.
rebase_dir_path
).
and_return
(
false
)
expect
(
subject
.
rebase_in_progress?
).
to
be_falsey
expect
(
subject
.
rebase_in_progress?
).
to
be_falsey
end
end
it
'returns false
if temporary file exists by i
s expired'
do
it
'returns false
when the rebase directory ha
s expired'
do
allow
(
File
).
to
receive
(
:exist?
).
and_return
(
true
)
allow
(
File
).
to
receive
(
:exist?
).
and_return
(
true
)
allow
(
File
).
to
receive
(
:
new
).
and_return
(
double
(
:file
,
mtime:
Time
.
now
-
2
.
hours
)
)
allow
(
File
).
to
receive
(
:
mtime
).
and_return
(
20
.
minutes
.
ago
)
expect
(
subject
.
rebase_in_progress?
).
to
be_falsey
expect
(
subject
.
rebase_in_progress?
).
to
be_falsey
end
end
it
'returns false
if source_project is
removed'
do
it
'returns false
when the source project has been
removed'
do
allow
(
subject
).
to
receive
(
:source_project
).
and_return
(
nil
)
allow
(
subject
).
to
receive
(
:source_project
).
and_return
(
nil
)
allow
(
File
).
to
receive
(
:exist?
).
and_return
(
true
)
allow
(
File
).
to
receive
(
:exist?
).
and_return
(
true
)
allow
(
File
).
to
receive
(
:
new
).
and_return
(
double
(
:file
,
mtime:
Time
.
now
)
)
allow
(
File
).
to
receive
(
:
mtime
).
and_return
(
Time
.
now
)
expect
(
File
).
not_to
have_received
(
:exist?
)
expect
(
File
).
not_to
have_received
(
:exist?
)
expect
(
subject
.
rebase_in_progress?
).
to
be_falsey
expect
(
subject
.
rebase_in_progress?
).
to
be_falsey
end
end
end
end
describe
'#squash_in_progress?'
do
it
'returns true when there is a current squash directory'
do
allow
(
File
).
to
receive
(
:exist?
).
and_return
(
true
)
allow
(
File
).
to
receive
(
:mtime
).
and_return
(
Time
.
now
)
expect
(
subject
.
squash_in_progress?
).
to
be_truthy
end
it
'returns false when there is no squash directory'
do
allow
(
File
).
to
receive
(
:exist?
).
with
(
subject
.
squash_dir_path
).
and_return
(
false
)
expect
(
subject
.
squash_in_progress?
).
to
be_falsey
end
it
'returns false when the squash directory has expired'
do
allow
(
File
).
to
receive
(
:exist?
).
and_return
(
true
)
allow
(
File
).
to
receive
(
:mtime
).
and_return
(
20
.
minutes
.
ago
)
expect
(
subject
.
squash_in_progress?
).
to
be_falsey
end
it
'returns false when the source project has been removed'
do
allow
(
subject
).
to
receive
(
:source_project
).
and_return
(
nil
)
allow
(
File
).
to
receive
(
:exist?
).
and_return
(
true
)
allow
(
File
).
to
receive
(
:mtime
).
and_return
(
Time
.
now
)
expect
(
File
).
not_to
have_received
(
:exist?
)
expect
(
subject
.
squash_in_progress?
).
to
be_falsey
end
end
describe
'#commits_sha'
do
describe
'#commits_sha'
do
before
do
before
do
allow
(
subject
.
merge_request_diff
).
to
receive
(
:commits_sha
).
allow
(
subject
.
merge_request_diff
).
to
receive
(
:commits_sha
).
...
...
spec/models/repository_spec.rb
View file @
f141afab
...
@@ -15,7 +15,12 @@ describe Repository, models: true do
...
@@ -15,7 +15,12 @@ describe Repository, models: true do
let
(
:merge_commit
)
do
let
(
:merge_commit
)
do
merge_request
=
create
(
:merge_request
,
source_branch:
'feature'
,
target_branch:
'master'
,
source_project:
project
)
merge_request
=
create
(
:merge_request
,
source_branch:
'feature'
,
target_branch:
'master'
,
source_project:
project
)
merge_commit_id
=
repository
.
merge
(
user
,
merge_request
,
commit_options
)
merge_commit_id
=
repository
.
merge
(
user
,
merge_request
.
diff_head_sha
,
merge_request
,
commit_options
)
repository
.
commit
(
merge_commit_id
)
repository
.
commit
(
merge_commit_id
)
end
end
...
@@ -1009,8 +1014,11 @@ describe Repository, models: true do
...
@@ -1009,8 +1014,11 @@ describe Repository, models: true do
it
'sets the `in_progress_merge_commit_sha` flag for the given merge request'
do
it
'sets the `in_progress_merge_commit_sha` flag for the given merge request'
do
merge_request
=
create
(
:merge_request
,
source_branch:
'feature'
,
target_branch:
'master'
,
source_project:
project
)
merge_request
=
create
(
:merge_request
,
source_branch:
'feature'
,
target_branch:
'master'
,
source_project:
project
)
merge_commit_id
=
repository
.
merge
(
user
,
merge_request
,
commit_options
)
repository
.
commit
(
merge_commit_id
)
merge_commit_id
=
repository
.
merge
(
user
,
merge_request
.
diff_head_sha
,
merge_request
,
commit_options
)
expect
(
merge_request
.
in_progress_merge_commit_sha
).
to
eq
(
merge_commit_id
)
expect
(
merge_request
.
in_progress_merge_commit_sha
).
to
eq
(
merge_commit_id
)
end
end
...
...
spec/requests/api/merge_requests_spec.rb
View file @
f141afab
...
@@ -39,6 +39,7 @@ describe API::MergeRequests, api: true do
...
@@ -39,6 +39,7 @@ describe API::MergeRequests, api: true do
expect
(
json_response
.
first
[
'sha'
]).
to
eq
(
merge_request_merged
.
diff_head_sha
)
expect
(
json_response
.
first
[
'sha'
]).
to
eq
(
merge_request_merged
.
diff_head_sha
)
expect
(
json_response
.
first
[
'merge_commit_sha'
]).
not_to
be_nil
expect
(
json_response
.
first
[
'merge_commit_sha'
]).
not_to
be_nil
expect
(
json_response
.
first
[
'merge_commit_sha'
]).
to
eq
(
merge_request_merged
.
merge_commit_sha
)
expect
(
json_response
.
first
[
'merge_commit_sha'
]).
to
eq
(
merge_request_merged
.
merge_commit_sha
)
expect
(
json_response
.
first
[
'squash'
]).
to
eq
(
merge_request_merged
.
squash
)
end
end
it
"returns an array of all merge_requests"
do
it
"returns an array of all merge_requests"
do
...
@@ -233,13 +234,15 @@ describe API::MergeRequests, api: true do
...
@@ -233,13 +234,15 @@ describe API::MergeRequests, api: true do
author:
user
,
author:
user
,
labels:
'label, label2'
,
labels:
'label, label2'
,
milestone_id:
milestone
.
id
,
milestone_id:
milestone
.
id
,
remove_source_branch:
true
remove_source_branch:
true
,
squash:
true
expect
(
response
).
to
have_http_status
(
201
)
expect
(
response
).
to
have_http_status
(
201
)
expect
(
json_response
[
'title'
]).
to
eq
(
'Test merge_request'
)
expect
(
json_response
[
'title'
]).
to
eq
(
'Test merge_request'
)
expect
(
json_response
[
'labels'
]).
to
eq
([
'label'
,
'label2'
])
expect
(
json_response
[
'labels'
]).
to
eq
([
'label'
,
'label2'
])
expect
(
json_response
[
'milestone'
][
'id'
]).
to
eq
(
milestone
.
id
)
expect
(
json_response
[
'milestone'
][
'id'
]).
to
eq
(
milestone
.
id
)
expect
(
json_response
[
'force_remove_source_branch'
]).
to
be_truthy
expect
(
json_response
[
'force_remove_source_branch'
]).
to
be_truthy
expect
(
json_response
[
'squash'
]).
to
be_truthy
end
end
it
"returns 422 when source_branch equals target_branch"
do
it
"returns 422 when source_branch equals target_branch"
do
...
@@ -526,6 +529,14 @@ describe API::MergeRequests, api: true do
...
@@ -526,6 +529,14 @@ describe API::MergeRequests, api: true do
expect
(
response
).
to
have_http_status
(
200
)
expect
(
response
).
to
have_http_status
(
200
)
end
end
it
"updates the MR's squash attribute"
do
expect
do
put
api
(
"/projects/
#{
project
.
id
}
/merge_requests/
#{
merge_request
.
id
}
/merge"
,
user
),
squash:
true
end
.
to
change
{
merge_request
.
reload
.
squash
}
expect
(
response
).
to
have_http_status
(
200
)
end
it
"enables merge when pipeline succeeds if the pipeline is active"
do
it
"enables merge when pipeline succeeds if the pipeline is active"
do
allow_any_instance_of
(
MergeRequest
).
to
receive
(
:head_pipeline
).
and_return
(
pipeline
)
allow_any_instance_of
(
MergeRequest
).
to
receive
(
:head_pipeline
).
and_return
(
pipeline
)
allow
(
pipeline
).
to
receive
(
:active?
).
and_return
(
true
)
allow
(
pipeline
).
to
receive
(
:active?
).
and_return
(
true
)
...
@@ -566,6 +577,13 @@ describe API::MergeRequests, api: true do
...
@@ -566,6 +577,13 @@ describe API::MergeRequests, api: true do
expect
(
json_response
[
'milestone'
][
'id'
]).
to
eq
(
milestone
.
id
)
expect
(
json_response
[
'milestone'
][
'id'
]).
to
eq
(
milestone
.
id
)
end
end
it
"updates squash and returns merge_request"
do
put
api
(
"/projects/
#{
project
.
id
}
/merge_requests/
#{
merge_request
.
id
}
"
,
user
),
squash:
true
expect
(
response
).
to
have_http_status
(
200
)
expect
(
json_response
[
'squash'
]).
to
be_truthy
end
it
"returns merge_request with renamed target_branch"
do
it
"returns merge_request with renamed target_branch"
do
put
api
(
"/projects/
#{
project
.
id
}
/merge_requests/
#{
merge_request
.
id
}
"
,
user
),
target_branch:
"wiki"
put
api
(
"/projects/
#{
project
.
id
}
/merge_requests/
#{
merge_request
.
id
}
"
,
user
),
target_branch:
"wiki"
expect
(
response
).
to
have_http_status
(
200
)
expect
(
response
).
to
have_http_status
(
200
)
...
...
spec/services/merge_requests/refresh_service_spec.rb
View file @
f141afab
...
@@ -101,7 +101,8 @@ describe MergeRequests::RefreshService, services: true do
...
@@ -101,7 +101,8 @@ describe MergeRequests::RefreshService, services: true do
# Merge master -> feature branch
# Merge master -> feature branch
author
=
{
email:
'test@gitlab.com'
,
time:
Time
.
now
,
name:
"Me"
}
author
=
{
email:
'test@gitlab.com'
,
time:
Time
.
now
,
name:
"Me"
}
commit_options
=
{
message:
'Test message'
,
committer:
author
,
author:
author
}
commit_options
=
{
message:
'Test message'
,
committer:
author
,
author:
author
}
@project
.
repository
.
merge
(
@user
,
@merge_request
,
commit_options
)
@project
.
repository
.
merge
(
@user
,
@merge_request
.
diff_head_sha
,
@merge_request
,
commit_options
)
commit
=
@project
.
repository
.
commit
(
'feature'
)
commit
=
@project
.
repository
.
commit
(
'feature'
)
service
.
new
(
@project
,
@user
).
execute
(
@oldrev
,
commit
.
id
,
'refs/heads/feature'
)
service
.
new
(
@project
,
@user
).
execute
(
@oldrev
,
commit
.
id
,
'refs/heads/feature'
)
reload_mrs
reload_mrs
...
...
spec/services/merge_requests/squash_service_spec.rb
0 → 100644
View file @
f141afab
require
'spec_helper'
describe
MergeRequests
::
SquashService
do
let
(
:service
)
{
described_class
.
new
(
project
,
user
,
{})
}
let
(
:user
)
{
project
.
owner
}
let
(
:project
)
{
create
(
:project
)
}
let
(
:merge_request
)
do
create
(
:merge_request
,
source_branch:
'fix'
,
source_project:
project
,
target_branch:
'master'
,
target_project:
project
)
end
let
(
:merge_request_with_one_commit
)
do
create
(
:merge_request
,
source_branch:
'feature.custom-highlighting'
,
source_project:
project
,
target_branch:
'master'
,
target_project:
project
)
end
def
git_command
(
command
)
a_collection_starting_with
([
Gitlab
.
config
.
git
.
bin_path
,
command
])
end
shared_examples
'the squashed commit'
do
end
describe
'#execute'
do
context
'when there is only one commit in the merge request'
do
it
'returns that commit SHA'
do
result
=
service
.
execute
(
merge_request_with_one_commit
)
expect
(
result
).
to
match
(
status: :success
,
squash_sha:
merge_request_with_one_commit
.
diff_head_sha
)
end
it
'does not perform any git actions'
do
expect
(
service
).
not_to
receive
(
:run_git_command
)
service
.
execute
(
merge_request_with_one_commit
)
end
end
context
'when the squash succeeds'
do
it
'returns the squashed commit SHA'
do
result
=
service
.
execute
(
merge_request
)
expect
(
result
).
to
match
(
status: :success
,
squash_sha:
a_string_matching
(
/\h{40}/
))
expect
(
result
[
:squash_sha
]).
not_to
eq
(
merge_request
.
diff_head_sha
)
end
it
'cleans up the temporary directory'
do
expect
(
service
).
to
receive
(
:clean_dir
).
and_call_original
service
.
execute
(
merge_request
)
end
it
'does not keep the branch push event'
do
expect
{
service
.
execute
(
merge_request
)
}.
not_to
change
{
Event
.
count
}
end
context
'the squashed commit'
do
let
(
:squash_sha
)
{
service
.
execute
(
merge_request
)[
:squash_sha
]
}
let
(
:squash_commit
)
{
project
.
repository
.
commit
(
squash_sha
)
}
it
'copies the author info and message from the last commit in the source branch'
do
diff_head_commit
=
merge_request
.
diff_head_commit
expect
(
squash_commit
.
author_name
).
to
eq
(
diff_head_commit
.
author_name
)
expect
(
squash_commit
.
author_email
).
to
eq
(
diff_head_commit
.
author_email
)
expect
(
squash_commit
.
message
).
to
eq
(
diff_head_commit
.
message
)
end
it
'sets the current user as the committer'
do
expect
(
squash_commit
.
committer_name
).
to
eq
(
user
.
name
.
chomp
(
'.'
))
expect
(
squash_commit
.
committer_email
).
to
eq
(
user
.
email
)
end
it
'has the same diff as the merge request, but a different SHA'
do
rugged
=
project
.
repository
.
rugged
mr_diff
=
rugged
.
diff
(
merge_request
.
diff_base_sha
,
merge_request
.
diff_head_sha
)
squash_diff
=
rugged
.
diff
(
merge_request
.
diff_start_sha
,
squash_sha
)
expect
(
squash_diff
.
patch
).
to
eq
(
mr_diff
.
patch
)
expect
(
squash_commit
.
sha
).
not_to
eq
(
merge_request
.
diff_head_sha
)
end
end
end
stages
=
{
'add worktree for squash'
=>
'worktree'
,
'apply patch'
=>
'apply'
,
'commit squashed changes'
=>
'commit'
,
'get SHA of squashed commit'
=>
'rev-parse'
}
stages
.
each
do
|
stage
,
command
|
context
"when the
#{
stage
}
stage fails"
do
let
(
:error
)
{
'A test error'
}
before
do
allow
(
service
).
to
receive
(
:popen
).
and_return
([
''
,
0
])
allow
(
service
).
to
receive
(
:popen
).
with
(
git_command
(
command
),
anything
,
anything
).
and_return
([
error
,
1
])
end
it
'logs the stage and output'
do
expect
(
service
).
to
receive
(
:log_error
).
with
(
a_string_including
(
stage
))
expect
(
service
).
to
receive
(
:log_error
).
with
(
error
)
service
.
execute
(
merge_request
)
end
it
'returns an error'
do
expect
(
service
.
execute
(
merge_request
)).
to
match
(
status: :error
,
message:
a_string_including
(
'squash'
))
end
it
'cleans up the temporary directory'
do
expect
(
service
).
to
receive
(
:clean_dir
).
and_call_original
service
.
execute
(
merge_request
)
end
end
end
context
'when any other exception is thrown'
do
let
(
:error
)
{
'A test error'
}
before
do
allow
(
merge_request
).
to
receive
(
:commits_count
).
and_raise
(
error
)
end
it
'logs the MR reference and exception'
do
expect
(
service
).
to
receive
(
:log_error
).
with
(
a_string_including
(
"
#{
project
.
path_with_namespace
}#{
merge_request
.
to_reference
}
"
))
expect
(
service
).
to
receive
(
:log_error
).
with
(
error
)
service
.
execute
(
merge_request
)
end
it
'returns an error'
do
expect
(
service
.
execute
(
merge_request
)).
to
match
(
status: :error
,
message:
a_string_including
(
'squash'
))
end
it
'cleans up the temporary directory'
do
expect
(
service
).
to
receive
(
:clean_dir
).
and_call_original
service
.
execute
(
merge_request
)
end
end
end
end
spec/support/test_env.rb
View file @
f141afab
...
@@ -37,7 +37,8 @@ module TestEnv
...
@@ -37,7 +37,8 @@ module TestEnv
'conflict-non-utf8'
=>
'd0a293c'
,
'conflict-non-utf8'
=>
'd0a293c'
,
'conflict-too-large'
=>
'39fa04f'
,
'conflict-too-large'
=>
'39fa04f'
,
'deleted-image-test'
=>
'6c17798'
,
'deleted-image-test'
=>
'6c17798'
,
'wip'
=>
'b9238ee'
'wip'
=>
'b9238ee'
,
'csv'
=>
'3dd0896'
}
}
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment