Skip to content
Snippets Groups Projects
Commit 8cce7073 authored by Jan Provaznik's avatar Jan Provaznik Committed by Jan Provaznik
Browse files

Create merge request from email

* new merge request can be created by sending an email to the specific
email address (similar to creating issues by email)
* for the first iteration, source branch must be specified in the mail
subject, other merge request parameters can not be set yet
* user should enable "Receive notifications about your own activity" in
user settings to receive a notification about created merge request

Part of #32878
parent a1cd9be4
No related branches found
No related tags found
No related merge requests found
Showing
with 188 additions and 25 deletions
Loading
Loading
@@ -28,7 +28,7 @@ export default class IssuableIndex {
url: $('.incoming-email-token-reset').attr('href'),
dataType: 'json',
success(response) {
$('#issue_email').val(response.new_issue_address).focus();
$('#issuable_email').val(response.new_address).focus();
},
beforeSend() {
$('.incoming-email-token-reset').text('resetting...');
Loading
Loading
Loading
Loading
@@ -164,12 +164,7 @@ ul.related-merge-requests > li {
}
}
 
.issues-footer {
padding-top: $gl-padding;
padding-bottom: 37px;
}
.issue-email-modal-btn {
.issuable-email-modal-btn {
padding: 0;
color: $gl-link-color;
background-color: transparent;
Loading
Loading
Loading
Loading
@@ -1210,3 +1210,8 @@ pre.light-well {
border-color: $border-color;
}
}
.issuable-footer {
padding-top: $gl-padding;
padding-bottom: 37px;
}
Loading
Loading
@@ -133,11 +133,11 @@ class ProjectsController < Projects::ApplicationController
redirect_to edit_project_path(@project), status: 302, alert: ex.message
end
 
def new_issue_address
def new_issuable_address
return render_404 unless Gitlab::IncomingEmail.supports_issue_creation?
 
current_user.reset_incoming_email_token!
render json: { new_issue_address: @project.new_issue_address(current_user) }
render json: { new_address: @project.new_issuable_address(current_user, params[:issuable_type]) }
end
 
def archive
Loading
Loading
Loading
Loading
@@ -752,13 +752,14 @@ class Project < ActiveRecord::Base
Gitlab::Routing.url_helpers.project_url(self)
end
 
def new_issue_address(author)
def new_issuable_address(author, address_type)
return unless Gitlab::IncomingEmail.supports_issue_creation? && author
 
author.ensure_incoming_email_token!
 
suffix = address_type == 'merge_request' ? '+merge-request' : ''
Gitlab::IncomingEmail.reply_address(
"#{full_path}+#{author.incoming_email_token}")
"#{full_path}#{suffix}+#{author.incoming_email_token}")
end
 
def build_commit_note(commit)
Loading
Loading
Loading
Loading
@@ -10,8 +10,12 @@ module MergeRequests
merge_request.target_branch = find_target_branch
merge_request.can_be_created = branches_valid?
 
compare_branches if branches_present?
assign_title_and_description if merge_request.can_be_created
# compare branches only if branches are valid, otherwise
# compare_branches may raise an error
if merge_request.can_be_created
compare_branches
assign_title_and_description
end
 
merge_request
end
Loading
Loading
Loading
Loading
@@ -35,6 +35,12 @@ module MergeRequests
super
end
 
# expose issuable create method so it can be called from email
# handler CreateMergeRequestHandler
def create(merge_request)
super
end
private
 
def update_merge_requests_head_pipeline(merge_request)
Loading
Loading
.issues-footer.text-center
%button.issue-email-modal-btn{ type: "button", data: { toggle: "modal", target: "#issue-email-modal" } }
Email a new issue to this project
- name = issuable_type == 'issue' ? 'issue' : 'merge request'
 
#issue-email-modal.modal.fade{ tabindex: "-1", role: "dialog" }
.issuable-footer.text-center
%button.issuable-email-modal-btn{ type: "button", data: { toggle: "modal", target: "#issuable-email-modal" } }
Email a new #{name} to this project
#issuable-email-modal.modal.fade{ tabindex: "-1", role: "dialog" }
.modal-dialog{ role: "document" }
.modal-content
.modal-header
%button.close{ type: "button", data: { dismiss: "modal" }, aria: { label: "close" } }
%span{ aria: { hidden: "true" } }= icon("times")
%h4.modal-title
Create new issue by email
Create new #{name} by email
.modal-body
%p
You can create a new issue inside this project by sending an email to the following email address:
You can create a new #{name} inside this project by sending an email to the following email address:
.email-modal-input-group.input-group
= text_field_tag :issue_email, email, class: "monospace js-select-on-focus form-control", readonly: true
= text_field_tag :issuable_email, email, class: "monospace js-select-on-focus form-control", readonly: true
.input-group-btn
= clipboard_button(target: '#issue_email')
= clipboard_button(target: '#issuable_email')
%p
The subject will be used as the title of the new issue, and the message will be the description.
= link_to 'Quick actions', help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
and styling with
= link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1
are supported.
= render 'by_email_description'
%p
This is a private email address, generated just for you.
 
Anyone who gets ahold of it can create issues as if they were you.
Anyone who gets ahold of it can create issues or merge requests as if they were you.
You should
= link_to 'reset it', new_issue_address_project_path(@project), class: 'incoming-email-token-reset'
= link_to 'reset it', new_issuable_address_project_path(@project, issuable_type: issuable_type), class: 'incoming-email-token-reset'
if that ever happens.
The subject will be used as the title of the new issue, and the message will be the description.
= link_to 'Quick actions', help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
and styling with
= link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1
are supported.
Loading
Loading
@@ -2,7 +2,7 @@
- @can_bulk_update = can?(current_user, :admin_issue, @project)
 
- page_title "Issues"
- new_issue_email = @project.new_issue_address(current_user)
- new_issue_email = @project.new_issuable_address(current_user, 'issue')
 
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
Loading
Loading
@@ -25,6 +25,6 @@
.issues-holder
= render 'issues'
- if new_issue_email
= render 'issue_by_email', email: new_issue_email
= render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue'
- else
= render 'shared/empty_states/issues', button_path: new_project_issue_path(@project)
The subject will be used as the source branch name for the new merge request and the target branch will be the default branch for the project.
Loading
Loading
@@ -4,6 +4,7 @@
- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
 
- page_title "Merge Requests"
- new_merge_request_email = @project.new_issuable_address(current_user, 'merge_request')
 
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
Loading
Loading
@@ -25,5 +26,7 @@
 
.merge-requests-holder
= render 'merge_requests'
- if new_merge_request_email
= render 'projects/issuable_by_email', email: new_merge_request_email, issuable_type: 'merge_request'
- else
= render 'shared/empty_states/merge_requests', button_path: new_merge_request_path
Loading
Loading
@@ -39,8 +39,7 @@ class EmailReceiverWorker
"You are not allowed to perform this action. If you believe this is in error, contact a staff member."
when Gitlab::Email::NoteableNotFoundError
"The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
when Gitlab::Email::InvalidNoteError,
Gitlab::Email::InvalidIssueError
when Gitlab::Email::InvalidRecordError
can_retry = true
e.message
end
Loading
Loading
---
title: Allow creation of merge request from email
merge_request: 13817
author: janp
type: added
Loading
Loading
@@ -429,7 +429,7 @@ constraints(ProjectUrlConstrainer.new) do
get :download_export
get :activity
get :refs
put :new_issue_address
put :new_issuable_address
end
end
end
Loading
Loading
Loading
Loading
@@ -27,7 +27,7 @@ With GitLab merge requests, you can:
- [Resolve merge conflicts from the UI](#resolve-conflicts)
- Enable [fast-forward merge requests](#fast-forward-merge-requests)
- Enable [semi-linear history merge requests](#semi-linear-history-merge-requests) as another security layer to guarantee the pipeline is passing in the target branch
- [Create new merge requests by email](#create_by_email)
 
With **[GitLab Enterprise Edition][ee]**, you can also:
 
Loading
Loading
@@ -132,6 +132,14 @@ those conflicts in the GitLab UI.
 
[Learn more about resolving merge conflicts in the UI.](resolve_conflicts.md)
 
## Create new merge requests by email
You can create a new merge request by sending an email to a user-specific email
address. The address can be obtained on the merge requests page by clicking on
a **Email a new merge request to this project** button. The subject will be
used as the source branch name for the new merge request and the target branch
will be the default branch for the project.
## Revert changes
 
GitLab implements Git's powerful feature to revert any commit with introducing
Loading
Loading
require 'gitlab/email/handler/create_merge_request_handler'
require 'gitlab/email/handler/create_note_handler'
require 'gitlab/email/handler/create_issue_handler'
require 'gitlab/email/handler/unsubscribe_handler'
Loading
Loading
@@ -8,6 +9,7 @@ module Gitlab
HANDLERS = [
UnsubscribeHandler,
CreateNoteHandler,
CreateMergeRequestHandler,
CreateIssueHandler
].freeze
 
Loading
Loading
require 'gitlab/email/handler/base_handler'
require 'gitlab/email/handler/reply_processing'
module Gitlab
module Email
module Handler
class CreateMergeRequestHandler < BaseHandler
include ReplyProcessing
attr_reader :project_path, :incoming_email_token
def initialize(mail, mail_key)
super(mail, mail_key)
if m = /\A([^\+]*)\+merge-request\+(.*)/.match(mail_key.to_s)
@project_path, @incoming_email_token = m.captures
end
end
def can_handle?
@project_path && @incoming_email_token
end
def execute
raise ProjectNotFound unless project
validate_permission!(:create_merge_request)
verify_record!(
record: create_merge_request,
invalid_exception: InvalidMergeRequestError,
record_name: 'merge_request')
end
def author
@author ||= User.find_by(incoming_email_token: incoming_email_token)
end
def project
@project ||= Project.find_by_full_path(project_path)
end
def metrics_params
super.merge(project: project&.full_path)
end
private
def create_merge_request
merge_request = MergeRequests::BuildService.new(project, author, merge_request_params).execute
if merge_request.errors.any?
merge_request
else
MergeRequests::CreateService.new(project, author).create(merge_request)
end
end
def merge_request_params
{
source_project_id: project.id,
source_branch: mail.subject,
target_project_id: project.id
}
end
end
end
end
end
Loading
Loading
@@ -13,8 +13,10 @@ module Gitlab
UserBlockedError = Class.new(ProcessingError)
UserNotAuthorizedError = Class.new(ProcessingError)
NoteableNotFoundError = Class.new(ProcessingError)
InvalidNoteError = Class.new(ProcessingError)
InvalidIssueError = Class.new(ProcessingError)
InvalidRecordError = Class.new(ProcessingError)
InvalidNoteError = Class.new(InvalidRecordError)
InvalidIssueError = Class.new(InvalidRecordError)
InvalidMergeRequestError = Class.new(InvalidRecordError)
UnknownIncomingEmail = Class.new(ProcessingError)
 
class Receiver
Loading
Loading
Loading
Loading
@@ -405,11 +405,12 @@ describe ProjectsController do
end
end
 
describe 'PUT #new_issue_address' do
describe 'PUT #new_issuable_address for issue' do
subject do
put :new_issue_address,
put :new_issuable_address,
namespace_id: project.namespace,
id: project
id: project,
issuable_type: 'issue'
user.reload
end
 
Loading
Loading
@@ -428,7 +429,35 @@ describe ProjectsController do
end
 
it 'changes projects new issue address' do
expect { subject }.to change { project.new_issue_address(user) }
expect { subject }.to change { project.new_issuable_address(user, 'issue') }
end
end
describe 'PUT #new_issuable_address for merge request' do
subject do
put :new_issuable_address,
namespace_id: project.namespace,
id: project,
issuable_type: 'merge_request'
user.reload
end
before do
sign_in(user)
project.team << [user, :developer]
allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
end
it 'has http status 200' do
expect(response).to have_http_status(200)
end
it 'changes the user incoming email token' do
expect { subject }.to change { user.incoming_email_token }
end
it 'changes projects new merge request address' do
expect { subject }.to change { project.new_issuable_address(user, 'merge_request') }
end
end
 
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment