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
0
Merge Requests
0
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
Boxiang Sun
gitlab-ce
Commits
7ab3dd4b
Commit
7ab3dd4b
authored
Nov 24, 2016
by
Jarka Kadlecova
Committed by
Jarka Kadlecova
Jan 11, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
support `/merge` slash comand for MRs
parent
4404ea86
Changes
15
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
429 additions
and
8 deletions
+429
-8
app/controllers/projects/notes_controller.rb
app/controllers/projects/notes_controller.rb
+2
-1
app/models/merge_request.rb
app/models/merge_request.rb
+13
-1
app/services/merge_requests/update_service.rb
app/services/merge_requests/update_service.rb
+15
-0
app/services/notes/create_service.rb
app/services/notes/create_service.rb
+4
-1
app/services/notes/slash_commands_service.rb
app/services/notes/slash_commands_service.rb
+2
-2
app/services/slash_commands/interpret_service.rb
app/services/slash_commands/interpret_service.rb
+14
-2
app/views/projects/notes/_form.html.haml
app/views/projects/notes/_form.html.haml
+1
-0
changelogs/unreleased/24915_merge_slash_command.yml
changelogs/unreleased/24915_merge_slash_command.yml
+4
-0
doc/user/project/slash_commands.md
doc/user/project/slash_commands.md
+1
-0
spec/controllers/projects/notes_controller_spec.rb
spec/controllers/projects/notes_controller_spec.rb
+48
-0
spec/features/merge_requests/user_uses_slash_commands_spec.rb
.../features/merge_requests/user_uses_slash_commands_spec.rb
+57
-0
spec/models/merge_request_spec.rb
spec/models/merge_request_spec.rb
+96
-0
spec/services/merge_requests/update_service_spec.rb
spec/services/merge_requests/update_service_spec.rb
+93
-0
spec/services/notes/create_service_spec.rb
spec/services/notes/create_service_spec.rb
+11
-0
spec/services/slash_commands/interpret_service_spec.rb
spec/services/slash_commands/interpret_service_spec.rb
+68
-1
No files found.
app/controllers/projects/notes_controller.rb
View file @
7ab3dd4b
...
...
@@ -23,7 +23,8 @@ class Projects::NotesController < Projects::ApplicationController
end
def
create
@note
=
Notes
::
CreateService
.
new
(
project
,
current_user
,
note_params
).
execute
create_params
=
note_params
.
merge
(
merge_request_diff_head_sha:
params
[
:merge_request_diff_head_sha
])
@note
=
Notes
::
CreateService
.
new
(
project
,
current_user
,
create_params
).
execute
if
@note
.
is_a?
(
Note
)
Banzai
::
NoteRenderer
.
render
([
@note
],
@project
,
current_user
)
...
...
app/models/merge_request.rb
View file @
7ab3dd4b
...
...
@@ -898,10 +898,22 @@ class MergeRequest < ActiveRecord::Base
end
def
has_commits?
commits_count
>
0
merge_request_diff
&&
commits_count
>
0
end
def
has_no_commits?
!
has_commits?
end
def
mergeable_with_slash_command?
(
current_user
,
autocomplete_precheck:
false
,
last_diff_sha:
nil
)
return
false
unless
can_be_merged_by?
(
current_user
)
return
true
if
autocomplete_precheck
return
false
unless
mergeable?
(
skip_ci_check:
true
)
return
false
if
head_pipeline
&&
!
(
head_pipeline
.
success?
||
head_pipeline
.
active?
)
return
false
if
last_diff_sha
!=
diff_head_sha
true
end
end
app/services/merge_requests/update_service.rb
View file @
7ab3dd4b
...
...
@@ -7,6 +7,8 @@ module MergeRequests
params
.
except!
(
:target_project_id
)
params
.
except!
(
:source_branch
)
merge_from_slash_command
(
merge_request
)
if
params
[
:merge
]
if
merge_request
.
closed_without_fork?
params
.
except!
(
:target_branch
,
:force_remove_source_branch
)
end
...
...
@@ -69,6 +71,19 @@ module MergeRequests
end
end
def
merge_from_slash_command
(
merge_request
)
last_diff_sha
=
params
.
delete
(
:merge
)
return
unless
merge_request
.
mergeable_with_slash_command?
(
current_user
,
last_diff_sha:
last_diff_sha
)
merge_request
.
update
(
merge_error:
nil
)
if
merge_request
.
head_pipeline
&&
merge_request
.
head_pipeline
.
active?
MergeRequests
::
MergeWhenPipelineSucceedsService
.
new
(
project
,
current_user
).
execute
(
merge_request
)
else
MergeWorker
.
perform_async
(
merge_request
.
id
,
current_user
.
id
,
{})
end
end
def
reopen_service
MergeRequests
::
ReopenService
end
...
...
app/services/notes/create_service.rb
View file @
7ab3dd4b
module
Notes
class
CreateService
<
BaseService
def
execute
merge_request_diff_head_sha
=
params
.
delete
(
:merge_request_diff_head_sha
)
note
=
project
.
notes
.
new
(
params
)
note
.
author
=
current_user
note
.
system
=
false
...
...
@@ -19,7 +21,8 @@ module Notes
slash_commands_service
=
SlashCommandsService
.
new
(
project
,
current_user
)
if
slash_commands_service
.
supported?
(
note
)
content
,
command_params
=
slash_commands_service
.
extract_commands
(
note
)
options
=
{
merge_request_diff_head_sha:
merge_request_diff_head_sha
}
content
,
command_params
=
slash_commands_service
.
extract_commands
(
note
,
options
)
only_commands
=
content
.
empty?
...
...
app/services/notes/slash_commands_service.rb
View file @
7ab3dd4b
...
...
@@ -19,10 +19,10 @@ module Notes
self
.
class
.
supported?
(
note
,
current_user
)
end
def
extract_commands
(
note
)
def
extract_commands
(
note
,
options
=
{}
)
return
[
note
.
note
,
{}]
unless
supported?
(
note
)
SlashCommands
::
InterpretService
.
new
(
project
,
current_user
).
SlashCommands
::
InterpretService
.
new
(
project
,
current_user
,
options
).
execute
(
note
.
note
,
note
.
noteable
)
end
...
...
app/services/slash_commands/interpret_service.rb
View file @
7ab3dd4b
...
...
@@ -2,7 +2,7 @@ module SlashCommands
class
InterpretService
<
BaseService
include
Gitlab
::
SlashCommands
::
Dsl
attr_reader
:issuable
attr_reader
:issuable
,
:options
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and hash of changes to be applied to a record.
...
...
@@ -13,7 +13,8 @@ module SlashCommands
opts
=
{
issuable:
issuable
,
current_user:
current_user
,
project:
project
project:
project
,
params:
params
}
content
,
commands
=
extractor
.
extract_commands
(
content
,
opts
)
...
...
@@ -58,6 +59,17 @@ module SlashCommands
@updates
[
:state_event
]
=
'reopen'
end
desc
'Merge (when build succeeds)'
condition
do
last_diff_sha
=
params
.
to_h
[
:merge_request_diff_head_sha
]
issuable
.
is_a?
(
MergeRequest
)
&&
issuable
.
mergeable_with_slash_command?
(
current_user
,
autocomplete_precheck:
!
last_diff_sha
,
last_diff_sha:
last_diff_sha
)
&&
issuable
.
persisted?
end
command
:merge
do
@updates
[
:merge
]
=
params
[
:merge_request_diff_head_sha
]
end
desc
'Change title'
params
'<New title>'
condition
do
...
...
app/views/projects/notes/_form.html.haml
View file @
7ab3dd4b
...
...
@@ -3,6 +3,7 @@
=
form_for
[
@project
.
namespace
.
becomes
(
Namespace
),
@project
,
@note
],
remote:
true
,
html:
{
:'data-type'
=>
'json'
,
multipart:
true
,
id:
nil
,
class:
"new-note js-new-note-form js-quick-submit common-note-form"
,
"data-noteable-iid"
=>
@note
.
noteable
.
try
(
:iid
),
},
authenticity_token:
true
do
|
f
|
=
hidden_field_tag
:view
,
diff_view
=
hidden_field_tag
:line_type
=
hidden_field_tag
:merge_request_diff_head_sha
,
@note
.
noteable
.
try
(
:diff_head_sha
)
=
note_target_fields
(
@note
)
=
f
.
hidden_field
:commit_id
=
f
.
hidden_field
:line_code
...
...
changelogs/unreleased/24915_merge_slash_command.yml
0 → 100644
View file @
7ab3dd4b
---
title
:
Support slash comand `/merge` for merging merge requests.
merge_request
:
7746
author
:
Jarka Kadlecova
doc/user/project/slash_commands.md
View file @
7ab3dd4b
...
...
@@ -14,6 +14,7 @@ do.
|:---------------------------|:-------------|
|
`/close`
| Close the issue or merge request |
|
`/reopen`
| Reopen the issue or merge request |
|
`/merge`
| Merge (when build succeeds) |
|
`/title <New title>`
| Change title |
|
`/assign @username`
| Assign |
|
`/unassign`
| Remove assignee |
...
...
spec/controllers/projects/notes_controller_spec.rb
View file @
7ab3dd4b
...
...
@@ -14,6 +14,54 @@ describe Projects::NotesController do
}
end
describe
'POST create'
do
let
(
:merge_request
)
{
create
(
:merge_request
)
}
let
(
:request_params
)
do
{
note:
{
note:
'some note'
,
noteable_id:
merge_request
.
id
,
noteable_type:
'MergeRequest'
},
namespace_id:
project
.
namespace
,
project_id:
project
,
merge_request_diff_head_sha:
'sha'
}
end
before
do
sign_in
(
user
)
project
.
team
<<
[
user
,
:developer
]
end
it
"returns status 302 for html"
do
post
:create
,
request_params
expect
(
response
).
to
have_http_status
(
302
)
end
it
"returns status 200 for json"
do
post
:create
,
request_params
.
merge
(
format: :json
)
expect
(
response
).
to
have_http_status
(
200
)
end
context
'when merge_request_diff_head_sha present'
do
before
do
service_params
=
{
note:
'some note'
,
noteable_id:
merge_request
.
id
.
to_s
,
noteable_type:
'MergeRequest'
,
merge_request_diff_head_sha:
'sha'
}
expect
(
Notes
::
CreateService
).
to
receive
(
:new
).
with
(
project
,
user
,
service_params
).
and_return
(
double
(
execute:
true
))
end
it
"returns status 302 for html"
do
post
:create
,
request_params
expect
(
response
).
to
have_http_status
(
302
)
end
end
end
describe
'POST toggle_award_emoji'
do
before
do
sign_in
(
user
)
...
...
spec/features/merge_requests/user_uses_slash_commands_spec.rb
View file @
7ab3dd4b
...
...
@@ -68,6 +68,63 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
end
end
describe
'merging the MR from the note'
do
context
'when the current user can merge the MR'
do
it
'merges the MR'
do
write_note
(
"/merge"
)
expect
(
page
).
to
have_content
'Your commands have been executed!'
expect
(
merge_request
.
reload
).
to
be_merged
end
end
context
'when the head diff changes in the meanwhile'
do
before
do
path
=
File
.
expand_path
(
"
#{
project
.
repository_storage_path
}
/
#{
project
.
namespace
.
path
}
/
#{
project
.
path
}
/new_file.txt"
)
params
=
{
source_project:
merge_request
.
project
,
target_project:
merge_request
.
project
,
target_branch:
merge_request
.
source_branch
,
source_branch:
merge_request
.
source_branch
,
file_path:
path
,
file_content:
'some content'
,
commit_message:
'additional commit'
,
}
Files
::
UpdateService
.
new
(
project
,
user
,
params
).
execute
merge_request
.
reload_diff
end
it
'does not merge the MR'
do
write_note
(
"/merge"
)
expect
(
page
).
not_to
have_content
'Your commands have been executed!'
expect
(
merge_request
.
reload
).
not_to
be_merged
end
end
context
'when the current user cannot merge the MR'
do
let
(
:guest
)
{
create
(
:user
)
}
before
do
project
.
team
<<
[
guest
,
:guest
]
logout
login_with
(
guest
)
visit
namespace_project_merge_request_path
(
project
.
namespace
,
project
,
merge_request
)
end
it
'does not merge the MR'
do
write_note
(
"/merge"
)
expect
(
page
).
not_to
have_content
'Your commands have been executed!'
expect
(
merge_request
.
reload
).
not_to
be_merged
end
end
end
describe
'adding a due date from note'
do
it
'does not recognize the command nor create a note'
do
write_note
(
'/due 2016-08-28'
)
...
...
spec/models/merge_request_spec.rb
View file @
7ab3dd4b
...
...
@@ -1514,6 +1514,102 @@ describe MergeRequest, models: true do
end
end
describe
'#mergeable_with_slash_command?'
do
def
create_pipeline
(
status
)
create
(
:ci_pipeline_with_one_job
,
project:
project
,
ref:
merge_request
.
source_branch
,
sha:
merge_request
.
diff_head_sha
,
status:
status
)
end
let
(
:project
)
{
create
(
:project
,
:public
,
only_allow_merge_if_build_succeeds:
true
)
}
let
(
:developer
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
)
}
let
(
:mr_sha
)
{
merge_request
.
diff_head_sha
}
before
do
project
.
team
<<
[
developer
,
:developer
]
end
context
'when autocomplete_precheck is set to true'
do
it
'is mergeable by developer'
do
expect
(
merge_request
.
mergeable_with_slash_command?
(
developer
,
autocomplete_precheck:
true
)).
to
be_truthy
end
it
'is not mergeable by normal user'
do
expect
(
merge_request
.
mergeable_with_slash_command?
(
user
,
autocomplete_precheck:
true
)).
to
be_falsey
end
end
context
'when autocomplete_precheck is set to false'
do
it
'is mergeable by developer'
do
expect
(
merge_request
.
mergeable_with_slash_command?
(
developer
,
last_diff_sha:
mr_sha
)).
to
be_truthy
end
it
'is not mergeable by normal user'
do
expect
(
merge_request
.
mergeable_with_slash_command?
(
user
,
last_diff_sha:
mr_sha
)).
to
be_falsey
end
context
'closed MR'
do
before
do
merge_request
.
update_attribute
(
:state
,
:closed
)
end
it
'is not mergeable'
do
expect
(
merge_request
.
mergeable_with_slash_command?
(
developer
,
last_diff_sha:
mr_sha
)).
to
be_falsey
end
end
context
'MR with WIP'
do
before
do
merge_request
.
update_attribute
(
:title
,
'WIP: some MR'
)
end
it
'is not mergeable'
do
expect
(
merge_request
.
mergeable_with_slash_command?
(
developer
,
last_diff_sha:
mr_sha
)).
to
be_falsey
end
end
context
'sha differs from the MR diff_head_sha'
do
it
'is not mergeable'
do
expect
(
merge_request
.
mergeable_with_slash_command?
(
developer
,
last_diff_sha:
'some other sha'
)).
to
be_falsey
end
end
context
'with pipeline ok'
do
before
do
create_pipeline
(
:success
)
end
it
'is mergeable'
do
expect
(
merge_request
.
mergeable_with_slash_command?
(
developer
,
last_diff_sha:
mr_sha
)).
to
be_truthy
end
end
context
'with failing pipeline'
do
before
do
create_pipeline
(
:failed
)
end
it
'is not mergeable'
do
expect
(
merge_request
.
mergeable_with_slash_command?
(
developer
,
last_diff_sha:
mr_sha
)).
to
be_falsey
end
end
context
'with running pipeline'
do
before
do
create_pipeline
(
:running
)
end
it
'is mergeable'
do
expect
(
merge_request
.
mergeable_with_slash_command?
(
developer
,
last_diff_sha:
mr_sha
)).
to
be_truthy
end
end
end
end
describe
'#has_commits?'
do
before
do
allow
(
subject
.
merge_request_diff
).
to
receive
(
:commits_count
).
...
...
spec/services/merge_requests/update_service_spec.rb
View file @
7ab3dd4b
...
...
@@ -121,6 +121,99 @@ describe MergeRequests::UpdateService, services: true do
end
end
context
'merge'
do
let
(
:opts
)
do
{
merge:
merge_request
.
diff_head_sha
}
end
let
(
:service
)
{
MergeRequests
::
UpdateService
.
new
(
project
,
user
,
opts
)
}
context
'without pipeline'
do
before
do
merge_request
.
merge_error
=
'Error'
perform_enqueued_jobs
do
service
.
execute
(
merge_request
)
@merge_request
=
MergeRequest
.
find
(
merge_request
.
id
)
end
end
it
{
expect
(
@merge_request
).
to
be_valid
}
it
{
expect
(
@merge_request
.
state
).
to
eq
(
'merged'
)
}
it
{
expect
(
@merge_request
.
merge_error
).
to
be_nil
}
end
context
'with finished pipeline'
do
before
do
create
(
:ci_pipeline_with_one_job
,
project:
project
,
ref:
merge_request
.
source_branch
,
sha:
merge_request
.
diff_head_sha
,
status: :success
)
perform_enqueued_jobs
do
@merge_request
=
service
.
execute
(
merge_request
)
@merge_request
=
MergeRequest
.
find
(
merge_request
.
id
)
end
end
it
{
expect
(
@merge_request
).
to
be_valid
}
it
{
expect
(
@merge_request
.
state
).
to
eq
(
'merged'
)
}
end
context
'with active pipeline'
do
before
do
service_mock
=
double
create
(
:ci_pipeline_with_one_job
,
project:
project
,
ref:
merge_request
.
source_branch
,
sha:
merge_request
.
diff_head_sha
)
expect
(
MergeRequests
::
MergeWhenPipelineSucceedsService
).
to
receive
(
:new
).
with
(
project
,
user
).
and_return
(
service_mock
)
expect
(
service_mock
).
to
receive
(
:execute
).
with
(
merge_request
)
end
it
{
service
.
execute
(
merge_request
)
}
end
context
'MR can not be merged by non authorised user'
do
let
(
:visitor
)
{
create
(
:user
)
}
let
(
:service
)
{
MergeRequests
::
UpdateService
.
new
(
project
,
visitor
,
opts
)
}
before
do
merge_request
.
update_attribute
(
:merge_error
,
'Error'
)
perform_enqueued_jobs
do
@merge_request
=
service
.
execute
(
merge_request
)
@merge_request
=
MergeRequest
.
find
(
merge_request
.
id
)
end
end
it
{
expect
(
@merge_request
.
state
).
to
eq
(
'opened'
)
}
it
{
expect
(
@merge_request
.
merge_error
).
not_to
be_nil
}
end
context
'MR can not be merged when note sha != MR sha'
do
let
(
:opts
)
do
{
merge:
'other_commit'
}
end
before
do
perform_enqueued_jobs
do
@merge_request
=
service
.
execute
(
merge_request
)
@merge_request
=
MergeRequest
.
find
(
merge_request
.
id
)
end
end
it
{
expect
(
@merge_request
.
state
).
to
eq
(
'opened'
)
}
end
end
context
'todos'
do
let!
(
:pending_todo
)
{
create
(
:todo
,
:assigned
,
user:
user
,
project:
project
,
target:
merge_request
,
author:
user2
)
}
...
...
spec/services/notes/create_service_spec.rb
View file @
7ab3dd4b
...
...
@@ -63,6 +63,17 @@ describe Notes::CreateService, services: true do
expect
(
note
.
note
).
to
eq
"HELLO
\n
WORLD"
end
end
describe
'/merge with sha option'
do
let
(
:note_text
)
{
%(HELLO\n/merge\nWORLD)
}
let
(
:params
)
{
opts
.
merge
(
note:
note_text
,
merge_request_diff_head_sha:
'sha'
)
}
it
'saves the note and exectues merge command'
do
note
=
described_class
.
new
(
project
,
user
,
params
).
execute
expect
(
note
.
note
).
to
eq
"HELLO
\n
WORLD"
end
end
end
end
...
...
spec/services/slash_commands/interpret_service_spec.rb
View file @
7ab3dd4b
require
'spec_helper'
describe
SlashCommands
::
InterpretService
,
services:
true
do
let
(
:project
)
{
create
(
:
empty_
project
,
:public
)
}
let
(
:project
)
{
create
(
:project
,
:public
)
}
let
(
:developer
)
{
create
(
:user
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let
(
:milestone
)
{
create
(
:milestone
,
project:
project
,
title:
'9.10'
)
}
let
(
:inprogress
)
{
create
(
:label
,
project:
project
,
title:
'In Progress'
)
}
let
(
:bug
)
{
create
(
:label
,
project:
project
,
title:
'Bug'
)
}
let
(
:note
)
{
build
(
:note
,
commit_id:
merge_request
.
diff_head_sha
)
}
before
do
project
.
team
<<
[
developer
,
:developer
]
...
...
@@ -218,6 +219,14 @@ describe SlashCommands::InterpretService, services: true do
end
end
shared_examples
'merge command'
do
it
'runs merge command if content contains /merge'
do
_
,
updates
=
service
.
execute
(
content
,
issuable
)
expect
(
updates
).
to
eq
(
merge:
merge_request
.
diff_head_sha
)
end
end
it_behaves_like
'reopen command'
do
let
(
:content
)
{
'/reopen'
}
let
(
:issuable
)
{
issue
}
...
...
@@ -238,6 +247,64 @@ describe SlashCommands::InterpretService, services: true do
let
(
:issuable
)
{
merge_request
}
end
context
'merge command'
do
let
(
:service
)
{
described_class
.
new
(
project
,
developer
,
{
merge_request_diff_head_sha:
merge_request
.
diff_head_sha
})
}
it_behaves_like
'merge command'
do
let
(
:content
)
{
'/merge'
}
let
(
:issuable
)
{
merge_request
}
end
context
'can not be merged when logged user does not have permissions'
do
let
(
:service
)
{
described_class
.
new
(
project
,
create
(
:user
))
}
it_behaves_like
'empty command'
do
let
(
:content
)
{
"/merge"
}
let
(
:issuable
)
{
merge_request
}
end
end
context
'can not be merged when sha does not match'
do
let
(
:service
)
{
described_class
.
new
(
project
,
developer
,
{
merge_request_diff_head_sha:
'othersha'
})
}
it_behaves_like
'empty command'
do
let
(
:content
)
{
"/merge"
}
let
(
:issuable
)
{
merge_request
}
end
end
context
'when sha is missing'
do
let
(
:service
)
{
described_class
.
new
(
project
,
developer
,
{})
}
it
'precheck passes and returns merge command'
do
_
,
updates
=
service
.
execute
(
'/merge'
,
merge_request
)
expect
(
updates
).
to
eq
(
merge:
nil
)
end
end
context
'non merge request object cant be merged'
do
it_behaves_like
'empty command'
do
let
(
:content
)
{
"/merge"
}
let
(
:issuable
)
{
issue
}
end
end
context
'non persisted merge request cant be merged'
do
it_behaves_like
'empty command'
do
let
(
:content
)
{
"/merge"
}
let
(
:issuable
)
{
build
(
:merge_request
)
}
end
end
context
'not persisted merge request can not be merged'
do
it_behaves_like
'empty command'
do
let
(
:content
)
{
"/merge"
}
let
(
:issuable
)
{
build
(
:merge_request
,
source_project:
project
)
}
end
end
end
it_behaves_like
'title command'
do
let
(
:content
)
{
'/title A brand new title'
}
let
(
:issuable
)
{
issue
}
...
...
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