Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce
This commit is contained in:
commit
c0bf026ed8
|
@ -1,6 +1,8 @@
|
||||||
Please view this file on the master branch, on stable branches it's out of date.
|
Please view this file on the master branch, on stable branches it's out of date.
|
||||||
|
|
||||||
v 7.14.0 (unreleased)
|
v 7.14.0 (unreleased)
|
||||||
|
- Disable turbolinks when linking to Bitbucket import status (Stan Hu)
|
||||||
|
- Fix broken code import and display error messages if something went wrong with creating project (Stan Hu)
|
||||||
- Fix corrupted binary files when using API files endpoint (Stan Hu)
|
- Fix corrupted binary files when using API files endpoint (Stan Hu)
|
||||||
- Show incompatible projects in Bitbucket import status (Stan Hu)
|
- Show incompatible projects in Bitbucket import status (Stan Hu)
|
||||||
- Fix coloring of diffs on MR Discussion-tab (Gert Goet)
|
- Fix coloring of diffs on MR Discussion-tab (Gert Goet)
|
||||||
|
@ -43,10 +45,7 @@ v 7.14.0 (unreleased)
|
||||||
- Remove redis-store TTL monkey patch
|
- Remove redis-store TTL monkey patch
|
||||||
- Add support for CI skipped status
|
- Add support for CI skipped status
|
||||||
- Fetch code from forks to refs/merge-requests/:id/head when merge request created
|
- Fetch code from forks to refs/merge-requests/:id/head when merge request created
|
||||||
- Remove satellites
|
|
||||||
- Remove comments and email addresses when publicly exposing ssh keys (Zeger-Jan van de Weg)
|
- Remove comments and email addresses when publicly exposing ssh keys (Zeger-Jan van de Weg)
|
||||||
- Improve MR merge widget text and UI consistency.
|
|
||||||
- Improve text in MR "How To Merge" modal.
|
|
||||||
- Cache all events
|
- Cache all events
|
||||||
- Order commits by date when comparing branches
|
- Order commits by date when comparing branches
|
||||||
- Fix bug causing error when the target branch of a symbolic ref was deleted
|
- Fix bug causing error when the target branch of a symbolic ref was deleted
|
||||||
|
@ -197,7 +196,6 @@ v 7.12.0
|
||||||
- Add SAML support as an omniauth provider
|
- Add SAML support as an omniauth provider
|
||||||
- Allow to configure a URL to show after sign out
|
- Allow to configure a URL to show after sign out
|
||||||
- Add an option to automatically sign-in with an Omniauth provider
|
- Add an option to automatically sign-in with an Omniauth provider
|
||||||
- Better performance for web editor (switched from satellites to rugged)
|
|
||||||
- GitLab CI service sends .gitlab-ci.yml in each push call
|
- GitLab CI service sends .gitlab-ci.yml in each push call
|
||||||
- When remove project - move repository and schedule it removal
|
- When remove project - move repository and schedule it removal
|
||||||
- Improve group removing logic
|
- Improve group removing logic
|
||||||
|
|
|
@ -875,4 +875,4 @@ DEPENDENCIES
|
||||||
wikicloth (= 0.8.1)
|
wikicloth (= 0.8.1)
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.10.4
|
1.10.5
|
||||||
|
|
|
@ -19,7 +19,7 @@ class @MergeRequestWidget
|
||||||
when 'merged'
|
when 'merged'
|
||||||
location.reload()
|
location.reload()
|
||||||
else
|
else
|
||||||
setTimeout(merge_request_widget.mergeInProgress, 2000)
|
setTimeout(merge_request_widget.mergeInProgress, 3000)
|
||||||
dataType: 'json'
|
dataType: 'json'
|
||||||
|
|
||||||
getMergeStatus: ->
|
getMergeStatus: ->
|
||||||
|
@ -49,8 +49,10 @@ class @MergeRequestWidget
|
||||||
@setMergeButtonClass('btn-danger')
|
@setMergeButtonClass('btn-danger')
|
||||||
|
|
||||||
showCiCoverage: (coverage) ->
|
showCiCoverage: (coverage) ->
|
||||||
text = 'Coverage ' + coverage + '%'
|
cov_html = $('<span>')
|
||||||
$('.ci_widget:visible .ci-coverage').text(text)
|
cov_html.addClass('ci-coverage')
|
||||||
|
cov_html.text('Coverage ' + coverage + '%')
|
||||||
|
$('.ci_widget:visible').append(cov_html)
|
||||||
|
|
||||||
setMergeButtonClass: (css_class) ->
|
setMergeButtonClass: (css_class) ->
|
||||||
$('.accept_merge_request').removeClass("btn-create").addClass(css_class)
|
$('.accept_merge_request').removeClass("btn-create").addClass(css_class)
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
/**
|
|
||||||
* MR -> show: Automerge widget
|
/**
|
||||||
|
* MR -> show: Automerge widget
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
.mr-state-widget {
|
.mr-state-widget {
|
||||||
background: #FAFAFA;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
color: #666;
|
|
||||||
border: 1px solid #e5e5e5;
|
|
||||||
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
|
|
||||||
@include border-radius(3px);
|
|
||||||
|
|
||||||
form {
|
form {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
.clearfix {
|
.clearfix {
|
||||||
|
@ -26,67 +20,16 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
padding: 5px;
|
padding: 10px 0;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
.remove_source_checkbox {
|
.remove_source_checkbox {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ci_widget {
|
|
||||||
border-bottom: 1px solid #EEE;
|
|
||||||
|
|
||||||
i {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ci-success {
|
|
||||||
color: $gl-success;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ci-skipped {
|
|
||||||
background-color: #eee;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ci-pending,
|
|
||||||
&.ci-running {
|
|
||||||
color: $gl-warning;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ci-failed,
|
|
||||||
&.ci-canceled,
|
|
||||||
&.ci-error {
|
|
||||||
color: $gl-danger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mr-widget-body,
|
|
||||||
.ci_widget,
|
|
||||||
.mr-widget-footer {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mr-widget-body {
|
|
||||||
h4 {
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mr-widget-footer {
|
|
||||||
border-top: 1px solid #EEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ci-coverage {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(min-width: $screen-sm-max) {
|
@media(min-width: $screen-sm-max) {
|
||||||
|
@ -118,10 +61,23 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-branch {
|
.label-branch {
|
||||||
color: #222;
|
@include border-radius(4px);
|
||||||
|
padding: 3px 4px;
|
||||||
|
border: none;
|
||||||
|
background: $hover;
|
||||||
|
color: #333;
|
||||||
font-family: $monospace_font;
|
font-family: $monospace_font;
|
||||||
font-weight: bold;
|
font-weight: normal;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
.label-project {
|
||||||
|
@include border-radius-left(4px);
|
||||||
|
padding: 3px 4px;
|
||||||
|
background: #279;
|
||||||
|
position: relative;
|
||||||
|
left: -4px;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mr-list {
|
.mr-list {
|
||||||
|
@ -168,6 +124,64 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mr-state-widget {
|
||||||
|
font-size: 13px;
|
||||||
|
background: #FAFAFA;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #666;
|
||||||
|
border: 1px solid #e5e5e5;
|
||||||
|
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
|
||||||
|
@include border-radius(3px);
|
||||||
|
|
||||||
|
.ci_widget {
|
||||||
|
padding: 10px 15px;
|
||||||
|
font-size: 15px;
|
||||||
|
border-bottom: 1px solid #EEE;
|
||||||
|
|
||||||
|
&.ci-success {
|
||||||
|
color: $gl-success;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ci-skipped {
|
||||||
|
background-color: #eee;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ci-pending,
|
||||||
|
&.ci-running {
|
||||||
|
color: $gl-warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ci-failed,
|
||||||
|
&.ci-canceled,
|
||||||
|
&.ci-error {
|
||||||
|
color: $gl-danger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-widget-body {
|
||||||
|
padding: 10px 15px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-widget-footer {
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-top: 1px solid #EEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ci-coverage {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.merge-request-show-labels {
|
.merge-request-show-labels {
|
||||||
a {
|
a {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
@ -182,7 +196,3 @@
|
||||||
.merge-request-form .select2-container {
|
.merge-request-form .select2-container {
|
||||||
width: 250px !important;
|
width: 250px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#modal_merge_info .modal-dialog {
|
|
||||||
width: 600px;
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,20 +13,27 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
before_action :commit, except: [:new, :create]
|
before_action :commit, except: [:new, :create]
|
||||||
before_action :blob, except: [:new, :create]
|
before_action :blob, except: [:new, :create]
|
||||||
before_action :from_merge_request, only: [:edit, :update]
|
before_action :from_merge_request, only: [:edit, :update]
|
||||||
before_action :require_branch_head, only: [:edit, :update]
|
|
||||||
before_action :editor_variables, except: [:show, :preview, :diff]
|
|
||||||
before_action :after_edit_path, only: [:edit, :update]
|
before_action :after_edit_path, only: [:edit, :update]
|
||||||
|
before_action :require_branch_head, only: [:edit, :update]
|
||||||
|
|
||||||
def new
|
def new
|
||||||
commit unless @repository.empty?
|
commit unless @repository.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
result = Files::CreateService.new(@project, current_user, @commit_params).execute
|
file_path = File.join(@path, File.basename(params[:file_name]))
|
||||||
|
result = Files::CreateService.new(
|
||||||
|
@project,
|
||||||
|
current_user,
|
||||||
|
params.merge(new_branch: sanitized_new_branch_name),
|
||||||
|
@ref,
|
||||||
|
file_path
|
||||||
|
).execute
|
||||||
|
|
||||||
if result[:status] == :success
|
if result[:status] == :success
|
||||||
flash[:notice] = "Your changes have been successfully committed"
|
flash[:notice] = "Your changes have been successfully committed"
|
||||||
redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path))
|
ref = sanitized_new_branch_name.presence || @ref
|
||||||
|
redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(ref, file_path))
|
||||||
else
|
else
|
||||||
flash[:alert] = result[:message]
|
flash[:alert] = result[:message]
|
||||||
render :new
|
render :new
|
||||||
|
@ -41,10 +48,22 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
result = Files::UpdateService.new(@project, current_user, @commit_params).execute
|
result = Files::UpdateService.
|
||||||
|
new(
|
||||||
|
@project,
|
||||||
|
current_user,
|
||||||
|
params.merge(new_branch: sanitized_new_branch_name),
|
||||||
|
@ref,
|
||||||
|
@path
|
||||||
|
).execute
|
||||||
|
|
||||||
if result[:status] == :success
|
if result[:status] == :success
|
||||||
flash[:notice] = "Your changes have been successfully committed"
|
flash[:notice] = "Your changes have been successfully committed"
|
||||||
|
|
||||||
|
if from_merge_request
|
||||||
|
from_merge_request.reload_code
|
||||||
|
end
|
||||||
|
|
||||||
redirect_to after_edit_path
|
redirect_to after_edit_path
|
||||||
else
|
else
|
||||||
flash[:alert] = result[:message]
|
flash[:alert] = result[:message]
|
||||||
|
@ -61,11 +80,12 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
result = Files::DeleteService.new(@project, current_user, @commit_params).execute
|
result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute
|
||||||
|
|
||||||
if result[:status] == :success
|
if result[:status] == :success
|
||||||
flash[:notice] = "Your changes have been successfully committed"
|
flash[:notice] = "Your changes have been successfully committed"
|
||||||
redirect_to namespace_project_tree_path(@project.namespace, @project, @target_branch)
|
redirect_to namespace_project_tree_path(@project.namespace, @project,
|
||||||
|
@ref)
|
||||||
else
|
else
|
||||||
flash[:alert] = result[:message]
|
flash[:alert] = result[:message]
|
||||||
render :show
|
render :show
|
||||||
|
@ -115,6 +135,7 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
@id = params[:id]
|
@id = params[:id]
|
||||||
@ref, @path = extract_ref(@id)
|
@ref, @path = extract_ref(@id)
|
||||||
|
|
||||||
|
|
||||||
rescue InvalidPathError
|
rescue InvalidPathError
|
||||||
not_found!
|
not_found!
|
||||||
end
|
end
|
||||||
|
@ -124,8 +145,8 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
if from_merge_request
|
if from_merge_request
|
||||||
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
|
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
|
||||||
"#file-path-#{hexdigest(@path)}"
|
"#file-path-#{hexdigest(@path)}"
|
||||||
elsif @target_branch.present?
|
elsif sanitized_new_branch_name.present?
|
||||||
namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
|
namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path))
|
||||||
else
|
else
|
||||||
namespace_project_blob_path(@project.namespace, @project, @id)
|
namespace_project_blob_path(@project.namespace, @project, @id)
|
||||||
end
|
end
|
||||||
|
@ -139,25 +160,4 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
def sanitized_new_branch_name
|
def sanitized_new_branch_name
|
||||||
@new_branch ||= sanitize(strip_tags(params[:new_branch]))
|
@new_branch ||= sanitize(strip_tags(params[:new_branch]))
|
||||||
end
|
end
|
||||||
|
|
||||||
def editor_variables
|
|
||||||
@current_branch = @ref
|
|
||||||
@target_branch = (sanitized_new_branch_name || @ref)
|
|
||||||
|
|
||||||
@file_path =
|
|
||||||
if action_name.to_s == 'create'
|
|
||||||
File.join(@path, File.basename(params[:file_name]))
|
|
||||||
else
|
|
||||||
@path
|
|
||||||
end
|
|
||||||
|
|
||||||
@commit_params = {
|
|
||||||
file_path: @file_path,
|
|
||||||
current_branch: @current_branch,
|
|
||||||
target_branch: @target_branch,
|
|
||||||
commit_message: params[:commit_message],
|
|
||||||
file_content: params[:content],
|
|
||||||
file_content_encoding: params[:encoding]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,8 +13,13 @@ class Projects::CompareController < Projects::ApplicationController
|
||||||
base_ref = Addressable::URI.unescape(params[:from])
|
base_ref = Addressable::URI.unescape(params[:from])
|
||||||
@ref = head_ref = Addressable::URI.unescape(params[:to])
|
@ref = head_ref = Addressable::URI.unescape(params[:to])
|
||||||
|
|
||||||
compare_result = CompareService.new.
|
compare_result = CompareService.new.execute(
|
||||||
execute(@project, head_ref, @project, base_ref)
|
current_user,
|
||||||
|
@project,
|
||||||
|
head_ref,
|
||||||
|
@project,
|
||||||
|
base_ref
|
||||||
|
)
|
||||||
|
|
||||||
@commits = compare_result.commits
|
@commits = compare_result.commits
|
||||||
@diffs = compare_result.diffs
|
@diffs = compare_result.diffs
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
require 'gitlab/satellite/satellite'
|
||||||
|
|
||||||
class Projects::MergeRequestsController < Projects::ApplicationController
|
class Projects::MergeRequestsController < Projects::ApplicationController
|
||||||
before_action :module_enabled
|
before_action :module_enabled
|
||||||
before_action :merge_request, only: [
|
before_action :merge_request, only: [
|
||||||
:edit, :update, :show, :diffs, :commits, :merge, :merge_check,
|
:edit, :update, :show, :diffs, :commits, :automerge, :automerge_check,
|
||||||
:ci_status, :toggle_subscription
|
:ci_status, :toggle_subscription
|
||||||
]
|
]
|
||||||
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits]
|
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits]
|
||||||
|
@ -135,7 +137,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge_check
|
def automerge_check
|
||||||
if @merge_request.unchecked?
|
if @merge_request.unchecked?
|
||||||
@merge_request.check_if_can_be_merged
|
@merge_request.check_if_can_be_merged
|
||||||
end
|
end
|
||||||
|
@ -145,11 +147,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||||
render partial: "projects/merge_requests/widget/show.html.haml", layout: false
|
render partial: "projects/merge_requests/widget/show.html.haml", layout: false
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge
|
def automerge
|
||||||
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
|
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
|
||||||
|
|
||||||
if @merge_request.mergeable?
|
if @merge_request.automergeable?
|
||||||
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
|
AutoMergeWorker.perform_async(@merge_request.id, current_user.id, params)
|
||||||
@status = true
|
@status = true
|
||||||
else
|
else
|
||||||
@status = false
|
@status = false
|
||||||
|
|
|
@ -61,14 +61,4 @@ module MergeRequestsHelper
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def source_branch_with_namespace(merge_request)
|
|
||||||
if merge_request.for_fork?
|
|
||||||
namespace = link_to(merge_request.source_project_namespace,
|
|
||||||
project_path(merge_request.source_project))
|
|
||||||
namespace + ":#{merge_request.source_branch}"
|
|
||||||
else
|
|
||||||
merge_request.source_branch
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -231,37 +231,20 @@ module ProjectsHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def readme_path(project)
|
||||||
|
filename_path(project, :readme)
|
||||||
|
end
|
||||||
|
|
||||||
def changelog_path(project)
|
def changelog_path(project)
|
||||||
if project && changelog = project.repository.changelog
|
filename_path(project, :changelog)
|
||||||
namespace_project_blob_path(
|
|
||||||
project.namespace,
|
|
||||||
project,
|
|
||||||
tree_join(project.default_branch,
|
|
||||||
changelog.name)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def license_path(project)
|
def license_path(project)
|
||||||
if project && license = project.repository.license
|
filename_path(project, :license)
|
||||||
namespace_project_blob_path(
|
|
||||||
project.namespace,
|
|
||||||
project,
|
|
||||||
tree_join(project.default_branch,
|
|
||||||
license.name)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def version_path(project)
|
def version_path(project)
|
||||||
if project && version = project.repository.version
|
filename_path(project, :version)
|
||||||
namespace_project_blob_path(
|
|
||||||
project.namespace,
|
|
||||||
project,
|
|
||||||
tree_join(project.default_branch,
|
|
||||||
version.name)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def hidden_pass_url(original_url)
|
def hidden_pass_url(original_url)
|
||||||
|
@ -331,4 +314,17 @@ module ProjectsHelper
|
||||||
count
|
count
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def filename_path(project, filename)
|
||||||
|
if project && blob = project.repository.send(filename)
|
||||||
|
namespace_project_blob_path(
|
||||||
|
project.namespace,
|
||||||
|
project,
|
||||||
|
tree_join(project.default_branch,
|
||||||
|
blob.name)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,6 +41,8 @@ class MergeRequest < ActiveRecord::Base
|
||||||
|
|
||||||
delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil
|
delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil
|
||||||
|
|
||||||
|
attr_accessor :should_remove_source_branch
|
||||||
|
|
||||||
# When this attribute is true some MR validation is ignored
|
# When this attribute is true some MR validation is ignored
|
||||||
# It allows us to close or modify broken merge requests
|
# It allows us to close or modify broken merge requests
|
||||||
attr_accessor :allow_broken
|
attr_accessor :allow_broken
|
||||||
|
@ -55,7 +57,7 @@ class MergeRequest < ActiveRecord::Base
|
||||||
transition [:reopened, :opened] => :closed
|
transition [:reopened, :opened] => :closed
|
||||||
end
|
end
|
||||||
|
|
||||||
event :mark_as_merged do
|
event :merge do
|
||||||
transition [:reopened, :opened, :locked] => :merged
|
transition [:reopened, :opened, :locked] => :merged
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -203,10 +205,7 @@ class MergeRequest < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_if_can_be_merged
|
def check_if_can_be_merged
|
||||||
can_be_merged =
|
if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
|
||||||
project.repository.can_be_merged?(source_sha, target_branch)
|
|
||||||
|
|
||||||
if can_be_merged
|
|
||||||
mark_as_mergeable
|
mark_as_mergeable
|
||||||
else
|
else
|
||||||
mark_as_unmergeable
|
mark_as_unmergeable
|
||||||
|
@ -221,6 +220,18 @@ class MergeRequest < ActiveRecord::Base
|
||||||
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
|
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def automerge!(current_user, commit_message = nil)
|
||||||
|
return unless automergeable?
|
||||||
|
|
||||||
|
MergeRequests::AutoMergeService.
|
||||||
|
new(target_project, current_user).
|
||||||
|
execute(self, commit_message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_source_branch?
|
||||||
|
self.should_remove_source_branch && !self.source_project.root_ref?(self.source_branch) && !self.for_fork?
|
||||||
|
end
|
||||||
|
|
||||||
def open?
|
def open?
|
||||||
opened? || reopened?
|
opened? || reopened?
|
||||||
end
|
end
|
||||||
|
@ -229,11 +240,11 @@ class MergeRequest < ActiveRecord::Base
|
||||||
title =~ /\A\[?WIP\]?:? /i
|
title =~ /\A\[?WIP\]?:? /i
|
||||||
end
|
end
|
||||||
|
|
||||||
def mergeable?
|
def automergeable?
|
||||||
open? && !work_in_progress? && can_be_merged?
|
open? && !work_in_progress? && can_be_merged?
|
||||||
end
|
end
|
||||||
|
|
||||||
def gitlab_merge_status
|
def automerge_status
|
||||||
if work_in_progress?
|
if work_in_progress?
|
||||||
"work_in_progress"
|
"work_in_progress"
|
||||||
else
|
else
|
||||||
|
@ -260,14 +271,14 @@ class MergeRequest < ActiveRecord::Base
|
||||||
#
|
#
|
||||||
# see "git diff"
|
# see "git diff"
|
||||||
def to_diff(current_user)
|
def to_diff(current_user)
|
||||||
target_project.repository.diff_text(target_branch, source_sha)
|
Gitlab::Satellite::MergeAction.new(current_user, self).diff_in_satellite
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the commit as a series of email patches.
|
# Returns the commit as a series of email patches.
|
||||||
#
|
#
|
||||||
# see "git format-patch"
|
# see "git format-patch"
|
||||||
def to_patch(current_user)
|
def to_patch(current_user)
|
||||||
target_project.repository.format_patch(target_branch, source_sha)
|
Gitlab::Satellite::MergeAction.new(current_user, self).format_patch
|
||||||
end
|
end
|
||||||
|
|
||||||
def hook_attrs
|
def hook_attrs
|
||||||
|
@ -418,30 +429,4 @@ class MergeRequest < ActiveRecord::Base
|
||||||
"Open"
|
"Open"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def target_sha
|
|
||||||
@target_sha ||= target_project.
|
|
||||||
repository.commit(target_branch).sha
|
|
||||||
end
|
|
||||||
|
|
||||||
def source_sha
|
|
||||||
commits.first.sha
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_ref
|
|
||||||
target_project.repository.fetch_ref(
|
|
||||||
source_project.repository.path_to_repo,
|
|
||||||
"refs/heads/#{source_branch}",
|
|
||||||
"refs/merge-requests/#{iid}/head"
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def in_locked_state
|
|
||||||
begin
|
|
||||||
lock_mr
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
unlock_mr if locked?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,8 +16,9 @@ require Rails.root.join("app/models/commit")
|
||||||
class MergeRequestDiff < ActiveRecord::Base
|
class MergeRequestDiff < ActiveRecord::Base
|
||||||
include Sortable
|
include Sortable
|
||||||
|
|
||||||
# Prevent store of diff if commits amount more then 500
|
# Prevent store of diff
|
||||||
COMMITS_SAFE_SIZE = 500
|
# if commits amount more then 200
|
||||||
|
COMMITS_SAFE_SIZE = 200
|
||||||
|
|
||||||
attr_reader :commits, :diffs
|
attr_reader :commits, :diffs
|
||||||
|
|
||||||
|
@ -123,12 +124,12 @@ class MergeRequestDiff < ActiveRecord::Base
|
||||||
if new_diffs.any?
|
if new_diffs.any?
|
||||||
if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES
|
if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES
|
||||||
self.state = :overflow_diff_files_limit
|
self.state = :overflow_diff_files_limit
|
||||||
new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES]
|
new_diffs = []
|
||||||
end
|
end
|
||||||
|
|
||||||
if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES
|
if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES
|
||||||
self.state = :overflow_diff_lines_limit
|
self.state = :overflow_diff_lines_limit
|
||||||
new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES]
|
new_diffs = []
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -159,21 +160,12 @@ class MergeRequestDiff < ActiveRecord::Base
|
||||||
private
|
private
|
||||||
|
|
||||||
def compare_result
|
def compare_result
|
||||||
@compare_result ||=
|
@compare_result ||= CompareService.new.execute(
|
||||||
begin
|
merge_request.author,
|
||||||
# Update ref for merge request
|
merge_request.source_project,
|
||||||
merge_request.fetch_ref
|
merge_request.source_branch,
|
||||||
|
merge_request.target_project,
|
||||||
# Get latest sha of branch from source project
|
merge_request.target_branch,
|
||||||
source_sha = merge_request.source_project.commit(source_branch).sha
|
)
|
||||||
|
|
||||||
Gitlab::CompareResult.new(
|
|
||||||
Gitlab::Git::Compare.new(
|
|
||||||
merge_request.target_project.repository.raw_repository,
|
|
||||||
merge_request.target_branch,
|
|
||||||
source_sha,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -118,11 +118,12 @@ class Namespace < ActiveRecord::Base
|
||||||
gitlab_shell.add_namespace(path_was)
|
gitlab_shell.add_namespace(path_was)
|
||||||
|
|
||||||
if gitlab_shell.mv_namespace(path_was, path)
|
if gitlab_shell.mv_namespace(path_was, path)
|
||||||
# If repositories moved successfully we need to
|
# If repositories moved successfully we need to remove old satellites
|
||||||
# send update instructions to users.
|
# and send update instructions to users.
|
||||||
# However we cannot allow rollback since we moved namespace dir
|
# However we cannot allow rollback since we moved namespace dir
|
||||||
# So we basically we mute exceptions in next actions
|
# So we basically we mute exceptions in next actions
|
||||||
begin
|
begin
|
||||||
|
gitlab_shell.rm_satellites(path_was)
|
||||||
send_update_instructions
|
send_update_instructions
|
||||||
rescue
|
rescue
|
||||||
# Returning false does not rollback after_* transaction but gives
|
# Returning false does not rollback after_* transaction but gives
|
||||||
|
|
|
@ -520,6 +520,14 @@ class Project < ActiveRecord::Base
|
||||||
!repository.exists? || repository.empty?
|
!repository.exists? || repository.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ensure_satellite_exists
|
||||||
|
self.satellite.create unless self.satellite.exists?
|
||||||
|
end
|
||||||
|
|
||||||
|
def satellite
|
||||||
|
@satellite ||= Gitlab::Satellite::Satellite.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
def repo
|
def repo
|
||||||
repository.raw
|
repository.raw
|
||||||
end
|
end
|
||||||
|
@ -589,11 +597,14 @@ class Project < ActiveRecord::Base
|
||||||
new_path_with_namespace = File.join(namespace_dir, path)
|
new_path_with_namespace = File.join(namespace_dir, path)
|
||||||
|
|
||||||
if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
|
if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
|
||||||
# If repository moved successfully we need to send update instructions to users.
|
# If repository moved successfully we need to remove old satellite
|
||||||
|
# and send update instructions to users.
|
||||||
# However we cannot allow rollback since we moved repository
|
# However we cannot allow rollback since we moved repository
|
||||||
# So we basically we mute exceptions in next actions
|
# So we basically we mute exceptions in next actions
|
||||||
begin
|
begin
|
||||||
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
|
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
|
||||||
|
gitlab_shell.rm_satellites(old_path_with_namespace)
|
||||||
|
ensure_satellite_exists
|
||||||
send_move_instructions
|
send_move_instructions
|
||||||
reset_events_cache
|
reset_events_cache
|
||||||
rescue
|
rescue
|
||||||
|
@ -691,6 +702,7 @@ class Project < ActiveRecord::Base
|
||||||
def create_repository
|
def create_repository
|
||||||
if forked?
|
if forked?
|
||||||
if gitlab_shell.fork_repository(forked_from_project.path_with_namespace, self.namespace.path)
|
if gitlab_shell.fork_repository(forked_from_project.path_with_namespace, self.namespace.path)
|
||||||
|
ensure_satellite_exists
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
errors.add(:base, 'Failed to fork repository via gitlab-shell')
|
errors.add(:base, 'Failed to fork repository via gitlab-shell')
|
||||||
|
|
|
@ -74,8 +74,6 @@ class GitlabCiService < CiService
|
||||||
else
|
else
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
rescue Errno::ECONNREFUSED
|
|
||||||
:error
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def fork_registration(new_project, private_token)
|
def fork_registration(new_project, private_token)
|
||||||
|
@ -105,8 +103,6 @@ class GitlabCiService < CiService
|
||||||
if response.code == 200 and response["coverage"]
|
if response.code == 200 and response["coverage"]
|
||||||
response["coverage"]
|
response["coverage"]
|
||||||
end
|
end
|
||||||
rescue Errno::ECONNREFUSED
|
|
||||||
nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_page(sha, ref)
|
def build_page(sha, ref)
|
||||||
|
|
|
@ -364,83 +364,6 @@ class Repository
|
||||||
@root_ref ||= raw_repository.root_ref
|
@root_ref ||= raw_repository.root_ref
|
||||||
end
|
end
|
||||||
|
|
||||||
def commit_file(user, path, content, message, ref)
|
|
||||||
path[0] = '' if path[0] == '/'
|
|
||||||
|
|
||||||
committer = user_to_comitter(user)
|
|
||||||
options = {}
|
|
||||||
options[:committer] = committer
|
|
||||||
options[:author] = committer
|
|
||||||
options[:commit] = {
|
|
||||||
message: message,
|
|
||||||
branch: ref
|
|
||||||
}
|
|
||||||
|
|
||||||
options[:file] = {
|
|
||||||
content: content,
|
|
||||||
path: path
|
|
||||||
}
|
|
||||||
|
|
||||||
Gitlab::Git::Blob.commit(raw_repository, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_file(user, path, message, ref)
|
|
||||||
path[0] = '' if path[0] == '/'
|
|
||||||
|
|
||||||
committer = user_to_comitter(user)
|
|
||||||
options = {}
|
|
||||||
options[:committer] = committer
|
|
||||||
options[:author] = committer
|
|
||||||
options[:commit] = {
|
|
||||||
message: message,
|
|
||||||
branch: ref
|
|
||||||
}
|
|
||||||
|
|
||||||
options[:file] = {
|
|
||||||
path: path
|
|
||||||
}
|
|
||||||
|
|
||||||
Gitlab::Git::Blob.remove(raw_repository, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def user_to_comitter(user)
|
|
||||||
{
|
|
||||||
email: user.email,
|
|
||||||
name: user.name,
|
|
||||||
time: Time.now
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_be_merged?(source_sha, target_branch)
|
|
||||||
our_commit = rugged.branches[target_branch].target
|
|
||||||
their_commit = rugged.lookup(source_sha)
|
|
||||||
|
|
||||||
if our_commit && their_commit
|
|
||||||
!rugged.merge_commits(our_commit, their_commit).conflicts?
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def merge(source_sha, target_branch, options = {})
|
|
||||||
our_commit = rugged.branches[target_branch].target
|
|
||||||
their_commit = rugged.lookup(source_sha)
|
|
||||||
|
|
||||||
raise "Invalid merge target" if our_commit.nil?
|
|
||||||
raise "Invalid merge source" if their_commit.nil?
|
|
||||||
|
|
||||||
merge_index = rugged.merge_commits(our_commit, their_commit)
|
|
||||||
return false if merge_index.conflicts?
|
|
||||||
|
|
||||||
actual_options = options.merge(
|
|
||||||
parents: [our_commit, their_commit],
|
|
||||||
tree: merge_index.write_tree(rugged),
|
|
||||||
update_ref: "refs/heads/#{target_branch}"
|
|
||||||
)
|
|
||||||
|
|
||||||
Rugged::Commit.create(rugged, actual_options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def search_files(query, ref)
|
def search_files(query, ref)
|
||||||
offset = 2
|
offset = 2
|
||||||
args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
|
args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
|
||||||
|
@ -474,11 +397,6 @@ class Repository
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_ref(source_path, source_ref, target_ref)
|
|
||||||
args = %W(git fetch #{source_path} #{source_ref}:#{target_ref})
|
|
||||||
Gitlab::Popen.popen(args, path_to_repo)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def cache
|
def cache
|
||||||
|
|
|
@ -31,10 +31,6 @@ class BaseService
|
||||||
SystemHooksService.new
|
SystemHooksService.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def repository
|
|
||||||
project.repository
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add an error to the specified model for restricted visibility levels
|
# Add an error to the specified model for restricted visibility levels
|
||||||
def deny_visibility_level(model, denied_visibility_level = nil)
|
def deny_visibility_level(model, denied_visibility_level = nil)
|
||||||
denied_visibility_level ||= model.visibility_level
|
denied_visibility_level ||= model.visibility_level
|
||||||
|
|
|
@ -1,28 +1,27 @@
|
||||||
require 'securerandom'
|
|
||||||
|
|
||||||
# Compare 2 branches for one repo or between repositories
|
# Compare 2 branches for one repo or between repositories
|
||||||
# and return Gitlab::CompareResult object that responds to commits and diffs
|
# and return Gitlab::CompareResult object that responds to commits and diffs
|
||||||
class CompareService
|
class CompareService
|
||||||
def execute(source_project, source_branch, target_project, target_branch)
|
def execute(current_user, source_project, source_branch, target_project, target_branch)
|
||||||
source_sha = source_project.commit(source_branch).sha
|
# Try to compare branches to get commits list and diffs
|
||||||
|
#
|
||||||
# If compare with other project we need to fetch ref first
|
# Note: Use satellite only when need to compare between two repos
|
||||||
unless target_project == source_project
|
# because satellites are slower than operations on bare repo
|
||||||
random_string = SecureRandom.hex
|
if target_project == source_project
|
||||||
|
Gitlab::CompareResult.new(
|
||||||
target_project.repository.fetch_ref(
|
Gitlab::Git::Compare.new(
|
||||||
source_project.repository.path_to_repo,
|
target_project.repository.raw_repository,
|
||||||
"refs/heads/#{source_branch}",
|
target_branch,
|
||||||
"refs/tmp/#{random_string}/head"
|
source_branch,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
end
|
else
|
||||||
|
Gitlab::Satellite::CompareAction.new(
|
||||||
Gitlab::CompareResult.new(
|
current_user,
|
||||||
Gitlab::Git::Compare.new(
|
target_project,
|
||||||
target_project.repository.raw_repository,
|
|
||||||
target_branch,
|
target_branch,
|
||||||
source_sha,
|
source_project,
|
||||||
)
|
source_branch
|
||||||
)
|
).result
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,80 +1,17 @@
|
||||||
module Files
|
module Files
|
||||||
class BaseService < ::BaseService
|
class BaseService < ::BaseService
|
||||||
class ValidationError < StandardError; end
|
attr_reader :ref, :path
|
||||||
|
|
||||||
def execute
|
def initialize(project, user, params, ref, path = nil)
|
||||||
@current_branch = params[:current_branch]
|
@project, @current_user, @params = project, user, params.dup
|
||||||
@target_branch = params[:target_branch]
|
@ref = ref
|
||||||
@commit_message = params[:commit_message]
|
@path = path
|
||||||
@file_path = params[:file_path]
|
|
||||||
@file_content = if params[:file_content_encoding] == 'base64'
|
|
||||||
Base64.decode64(params[:file_content])
|
|
||||||
else
|
|
||||||
params[:file_content]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Validate parameters
|
|
||||||
validate
|
|
||||||
|
|
||||||
# Create new branch if it different from current_branch
|
|
||||||
if @target_branch != @current_branch
|
|
||||||
create_target_branch
|
|
||||||
end
|
|
||||||
|
|
||||||
if sha = commit
|
|
||||||
after_commit(sha, @target_branch)
|
|
||||||
success
|
|
||||||
else
|
|
||||||
error("Something went wrong. Your changes were not committed")
|
|
||||||
end
|
|
||||||
rescue ValidationError => ex
|
|
||||||
error(ex.message)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def after_commit(sha, branch)
|
def repository
|
||||||
PostCommitService.new(project, current_user).execute(sha, branch)
|
project.repository
|
||||||
end
|
|
||||||
|
|
||||||
def current_branch
|
|
||||||
@current_branch ||= params[:current_branch]
|
|
||||||
end
|
|
||||||
|
|
||||||
def target_branch
|
|
||||||
@target_branch ||= params[:target_branch]
|
|
||||||
end
|
|
||||||
|
|
||||||
def raise_error(message)
|
|
||||||
raise ValidationError.new(message)
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate
|
|
||||||
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
|
|
||||||
|
|
||||||
unless allowed
|
|
||||||
raise_error("You are not allowed to push into this branch")
|
|
||||||
end
|
|
||||||
|
|
||||||
unless project.empty_repo?
|
|
||||||
unless repository.branch_names.include?(@current_branch)
|
|
||||||
raise_error("You can only create files if you are on top of a branch")
|
|
||||||
end
|
|
||||||
|
|
||||||
if @current_branch != @target_branch
|
|
||||||
if repository.branch_names.include?(@target_branch)
|
|
||||||
raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_target_branch
|
|
||||||
result = CreateBranchService.new(project, current_user).execute(@target_branch, @current_branch)
|
|
||||||
|
|
||||||
unless result[:status] == :success
|
|
||||||
raise_error("Something went wrong when we tried to create #{@target_branch} for you")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,30 +1,52 @@
|
||||||
require_relative "base_service"
|
require_relative "base_service"
|
||||||
|
|
||||||
module Files
|
module Files
|
||||||
class CreateService < Files::BaseService
|
class CreateService < BaseService
|
||||||
def commit
|
def execute
|
||||||
repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
|
allowed = Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
|
||||||
end
|
|
||||||
|
|
||||||
def validate
|
unless allowed
|
||||||
super
|
return error("You are not allowed to create file in this branch")
|
||||||
|
end
|
||||||
|
|
||||||
file_name = File.basename(@file_path)
|
file_name = File.basename(path)
|
||||||
|
file_path = path
|
||||||
|
|
||||||
unless file_name =~ Gitlab::Regex.file_name_regex
|
unless file_name =~ Gitlab::Regex.file_name_regex
|
||||||
raise_error(
|
return error(
|
||||||
'Your changes could not be committed, because the file name ' +
|
'Your changes could not be committed, because the file name ' +
|
||||||
Gitlab::Regex.file_name_regex_message
|
Gitlab::Regex.file_name_regex_message
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
unless project.empty_repo?
|
if project.empty_repo?
|
||||||
blob = repository.blob_at_branch(@current_branch, @file_path)
|
# everything is ok because repo does not have a commits yet
|
||||||
|
else
|
||||||
|
unless repository.branch_names.include?(ref)
|
||||||
|
return error("You can only create files if you are on top of a branch")
|
||||||
|
end
|
||||||
|
|
||||||
|
blob = repository.blob_at_branch(ref, file_path)
|
||||||
|
|
||||||
if blob
|
if blob
|
||||||
raise_error("Your changes could not be committed, because file with such name exists")
|
return error("Your changes could not be committed, because file with such name exists")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path)
|
||||||
|
created_successfully = new_file_action.commit!(
|
||||||
|
params[:content],
|
||||||
|
params[:commit_message],
|
||||||
|
params[:encoding],
|
||||||
|
params[:new_branch]
|
||||||
|
)
|
||||||
|
|
||||||
|
if created_successfully
|
||||||
|
success
|
||||||
|
else
|
||||||
|
error("Your changes could not be committed, because the file has been changed")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,36 @@
|
||||||
require_relative "base_service"
|
require_relative "base_service"
|
||||||
|
|
||||||
module Files
|
module Files
|
||||||
class DeleteService < Files::BaseService
|
class DeleteService < BaseService
|
||||||
def commit
|
def execute
|
||||||
repository.remove_file(current_user, @file_path, @commit_message, @target_branch)
|
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
|
||||||
|
|
||||||
|
unless allowed
|
||||||
|
return error("You are not allowed to push into this branch")
|
||||||
|
end
|
||||||
|
|
||||||
|
unless repository.branch_names.include?(ref)
|
||||||
|
return error("You can only create files if you are on top of a branch")
|
||||||
|
end
|
||||||
|
|
||||||
|
blob = repository.blob_at_branch(ref, path)
|
||||||
|
|
||||||
|
unless blob
|
||||||
|
return error("You can only edit text files")
|
||||||
|
end
|
||||||
|
|
||||||
|
delete_file_action = Gitlab::Satellite::DeleteFileAction.new(current_user, project, ref, path)
|
||||||
|
|
||||||
|
deleted_successfully = delete_file_action.commit!(
|
||||||
|
nil,
|
||||||
|
params[:commit_message]
|
||||||
|
)
|
||||||
|
|
||||||
|
if deleted_successfully
|
||||||
|
success
|
||||||
|
else
|
||||||
|
error("Your changes could not be committed, because the file has been changed")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,39 @@
|
||||||
require_relative "base_service"
|
require_relative "base_service"
|
||||||
|
|
||||||
module Files
|
module Files
|
||||||
class UpdateService < Files::BaseService
|
class UpdateService < BaseService
|
||||||
def commit
|
def execute
|
||||||
repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
|
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
|
||||||
|
|
||||||
|
unless allowed
|
||||||
|
return error("You are not allowed to push into this branch")
|
||||||
|
end
|
||||||
|
|
||||||
|
unless repository.branch_names.include?(ref)
|
||||||
|
return error("You can only create files if you are on top of a branch")
|
||||||
|
end
|
||||||
|
|
||||||
|
blob = repository.blob_at_branch(ref, path)
|
||||||
|
|
||||||
|
unless blob
|
||||||
|
return error("You can only edit text files")
|
||||||
|
end
|
||||||
|
|
||||||
|
edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path)
|
||||||
|
edit_file_action.commit!(
|
||||||
|
params[:content],
|
||||||
|
params[:commit_message],
|
||||||
|
params[:encoding],
|
||||||
|
params[:new_branch]
|
||||||
|
)
|
||||||
|
|
||||||
|
success
|
||||||
|
rescue Gitlab::Satellite::CheckoutFailed => ex
|
||||||
|
error("Your changes could not be committed because ref '#{ref}' could not be checked out", 400)
|
||||||
|
rescue Gitlab::Satellite::CommitFailed => ex
|
||||||
|
error("Your changes could not be committed. Maybe there was nothing to commit?", 409)
|
||||||
|
rescue Gitlab::Satellite::PushFailed => ex
|
||||||
|
error("Your changes could not be committed. Maybe the file was changed by another process?", 409)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,14 +10,16 @@ class GitPushService
|
||||||
#
|
#
|
||||||
# Next, this method:
|
# Next, this method:
|
||||||
# 1. Creates the push event
|
# 1. Creates the push event
|
||||||
# 2. Updates merge requests
|
# 2. Ensures that the project satellite exists
|
||||||
# 3. Recognizes cross-references from commit messages
|
# 3. Updates merge requests
|
||||||
# 4. Executes the project's web hooks
|
# 4. Recognizes cross-references from commit messages
|
||||||
# 5. Executes the project's services
|
# 5. Executes the project's web hooks
|
||||||
|
# 6. Executes the project's services
|
||||||
#
|
#
|
||||||
def execute(project, user, oldrev, newrev, ref)
|
def execute(project, user, oldrev, newrev, ref)
|
||||||
@project, @user = project, user
|
@project, @user = project, user
|
||||||
|
|
||||||
|
project.ensure_satellite_exists
|
||||||
project.repository.expire_cache
|
project.repository.expire_cache
|
||||||
|
|
||||||
if push_remove_branch?(ref, newrev)
|
if push_remove_branch?(ref, newrev)
|
||||||
|
@ -131,8 +133,7 @@ class GitPushService
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_default_branch?(ref)
|
def is_default_branch?(ref)
|
||||||
Gitlab::Git.branch_ref?(ref) &&
|
Gitlab::Git.branch_ref?(ref) && Gitlab::Git.ref_name(ref) == project.default_branch
|
||||||
(Gitlab::Git.ref_name(ref) == project.default_branch || project.default_branch.nil?)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def commit_user(commit)
|
def commit_user(commit)
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
module MergeRequests
|
||||||
|
# AutoMergeService class
|
||||||
|
#
|
||||||
|
# Do git merge in satellite and in case of success
|
||||||
|
# mark merge request as merged and execute all hooks and notifications
|
||||||
|
# Called when you do merge via GitLab UI
|
||||||
|
class AutoMergeService < BaseMergeService
|
||||||
|
def execute(merge_request, commit_message)
|
||||||
|
merge_request.lock_mr
|
||||||
|
|
||||||
|
if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
|
||||||
|
merge_request.merge
|
||||||
|
|
||||||
|
create_merge_event(merge_request, current_user)
|
||||||
|
create_note(merge_request)
|
||||||
|
notification_service.merge_mr(merge_request, current_user)
|
||||||
|
execute_hooks(merge_request, 'merge')
|
||||||
|
|
||||||
|
true
|
||||||
|
else
|
||||||
|
merge_request.unlock_mr
|
||||||
|
false
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
merge_request.unlock_mr if merge_request.locked?
|
||||||
|
merge_request.mark_as_unmergeable
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,10 @@
|
||||||
|
module MergeRequests
|
||||||
|
class BaseMergeService < MergeRequests::BaseService
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_merge_event(merge_request, current_user)
|
||||||
|
EventCreateService.new.merge_mr(merge_request, current_user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -12,16 +12,12 @@ module MergeRequests
|
||||||
merge_request.target_project ||= (project.forked_from_project || project)
|
merge_request.target_project ||= (project.forked_from_project || project)
|
||||||
merge_request.target_branch ||= merge_request.target_project.default_branch
|
merge_request.target_branch ||= merge_request.target_project.default_branch
|
||||||
|
|
||||||
if merge_request.target_branch.blank? || merge_request.source_branch.blank?
|
unless merge_request.target_branch && merge_request.source_branch
|
||||||
message =
|
return build_failed(merge_request, nil)
|
||||||
if params[:source_branch] || params[:target_branch]
|
|
||||||
"You must select source and target branch"
|
|
||||||
end
|
|
||||||
|
|
||||||
return build_failed(merge_request, message)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
compare_result = CompareService.new.execute(
|
compare_result = CompareService.new.execute(
|
||||||
|
current_user,
|
||||||
merge_request.source_project,
|
merge_request.source_project,
|
||||||
merge_request.source_branch,
|
merge_request.source_branch,
|
||||||
merge_request.target_project,
|
merge_request.target_project,
|
||||||
|
@ -44,6 +40,7 @@ module MergeRequests
|
||||||
merge_request.compare_diffs = diffs
|
merge_request.compare_diffs = diffs
|
||||||
|
|
||||||
elsif diffs == false
|
elsif diffs == false
|
||||||
|
# satellite timeout return false
|
||||||
merge_request.can_be_created = false
|
merge_request.can_be_created = false
|
||||||
merge_request.compare_failed = true
|
merge_request.compare_failed = true
|
||||||
end
|
end
|
||||||
|
@ -62,6 +59,9 @@ module MergeRequests
|
||||||
end
|
end
|
||||||
|
|
||||||
merge_request
|
merge_request
|
||||||
|
|
||||||
|
rescue Gitlab::Satellite::BranchesWithoutParent
|
||||||
|
return build_failed(merge_request, "Selected branches have no common commit so they cannot be merged.")
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_failed(merge_request, message)
|
def build_failed(merge_request, message)
|
||||||
|
|
|
@ -1,57 +1,22 @@
|
||||||
module MergeRequests
|
module MergeRequests
|
||||||
# MergeService class
|
# MergeService class
|
||||||
#
|
#
|
||||||
# Do git merge and in case of success
|
# Mark existing merge request as merged
|
||||||
# mark merge request as merged and execute all hooks and notifications
|
# and execute all hooks and notifications
|
||||||
# Executed when you do merge via GitLab UI
|
# Called when you do merge via command line and push code
|
||||||
#
|
# to target branch
|
||||||
class MergeService < MergeRequests::BaseService
|
class MergeService < BaseMergeService
|
||||||
attr_reader :merge_request, :commit_message
|
|
||||||
|
|
||||||
def execute(merge_request, commit_message)
|
def execute(merge_request, commit_message)
|
||||||
@commit_message = commit_message
|
merge_request.merge
|
||||||
@merge_request = merge_request
|
|
||||||
|
|
||||||
unless @merge_request.mergeable?
|
create_merge_event(merge_request, current_user)
|
||||||
return error('Merge request is not mergeable')
|
create_note(merge_request)
|
||||||
end
|
notification_service.merge_mr(merge_request, current_user)
|
||||||
|
execute_hooks(merge_request, 'merge')
|
||||||
|
|
||||||
merge_request.in_locked_state do
|
true
|
||||||
if merge_changes
|
rescue
|
||||||
after_merge
|
false
|
||||||
success
|
|
||||||
else
|
|
||||||
error('Can not merge changes')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def merge_changes
|
|
||||||
if sha = commit
|
|
||||||
after_commit(sha, merge_request.target_branch)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def commit
|
|
||||||
committer = repository.user_to_comitter(current_user)
|
|
||||||
|
|
||||||
options = {
|
|
||||||
message: commit_message,
|
|
||||||
author: committer,
|
|
||||||
committer: committer
|
|
||||||
}
|
|
||||||
|
|
||||||
repository.merge(merge_request.source_sha, merge_request.target_branch, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def after_commit(sha, branch)
|
|
||||||
PostCommitService.new(project, current_user).execute(sha, branch)
|
|
||||||
end
|
|
||||||
|
|
||||||
def after_merge
|
|
||||||
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
module MergeRequests
|
|
||||||
# PostMergeService class
|
|
||||||
#
|
|
||||||
# Mark existing merge request as merged
|
|
||||||
# and execute all hooks and notifications
|
|
||||||
#
|
|
||||||
class PostMergeService < MergeRequests::BaseService
|
|
||||||
def execute(merge_request)
|
|
||||||
merge_request.mark_as_merged
|
|
||||||
create_merge_event(merge_request, current_user)
|
|
||||||
create_note(merge_request)
|
|
||||||
notification_service.merge_mr(merge_request, current_user)
|
|
||||||
execute_hooks(merge_request, 'merge')
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def create_merge_event(merge_request, current_user)
|
|
||||||
EventCreateService.new.merge_mr(merge_request, current_user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -33,9 +33,9 @@ module MergeRequests
|
||||||
|
|
||||||
|
|
||||||
merge_requests.uniq.select(&:source_project).each do |merge_request|
|
merge_requests.uniq.select(&:source_project).each do |merge_request|
|
||||||
MergeRequests::PostMergeService.
|
MergeRequests::MergeService.
|
||||||
new(merge_request.target_project, @current_user).
|
new(merge_request.target_project, @current_user).
|
||||||
execute(merge_request)
|
execute(merge_request, nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
class PostCommitService < BaseService
|
|
||||||
include Gitlab::Popen
|
|
||||||
|
|
||||||
attr_reader :changes, :repo_path
|
|
||||||
|
|
||||||
def execute(sha, branch)
|
|
||||||
commit = repository.commit(sha)
|
|
||||||
full_ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
|
|
||||||
old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA
|
|
||||||
@changes = "#{old_sha} #{sha} #{full_ref}"
|
|
||||||
@repo_path = repository.path_to_repo
|
|
||||||
|
|
||||||
post_receive
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def post_receive
|
|
||||||
hook = hook_file('post-receive', repo_path)
|
|
||||||
return true if hook.nil?
|
|
||||||
call_receive_hook(hook)
|
|
||||||
end
|
|
||||||
|
|
||||||
def call_receive_hook(hook)
|
|
||||||
# function will return true if succesful
|
|
||||||
exit_status = false
|
|
||||||
|
|
||||||
vars = {
|
|
||||||
'GL_ID' => Gitlab::ShellEnv.gl_id(current_user),
|
|
||||||
'PWD' => repo_path
|
|
||||||
}
|
|
||||||
|
|
||||||
options = {
|
|
||||||
chdir: repo_path
|
|
||||||
}
|
|
||||||
|
|
||||||
# we combine both stdout and stderr as we don't know what stream
|
|
||||||
# will be used by the custom hook
|
|
||||||
Open3.popen2e(vars, hook, options) do |stdin, stdout_stderr, wait_thr|
|
|
||||||
exit_status = true
|
|
||||||
stdin.sync = true
|
|
||||||
|
|
||||||
# in git, pre- and post- receive hooks may just exit without
|
|
||||||
# reading stdin. We catch the exception to avoid a broken pipe
|
|
||||||
# warning
|
|
||||||
begin
|
|
||||||
# inject all the changes as stdin to the hook
|
|
||||||
changes.lines do |line|
|
|
||||||
stdin.puts line
|
|
||||||
end
|
|
||||||
rescue Errno::EPIPE
|
|
||||||
end
|
|
||||||
|
|
||||||
# need to close stdin before reading stdout
|
|
||||||
stdin.close
|
|
||||||
|
|
||||||
# only output stdut_stderr if scripts doesn't return 0
|
|
||||||
unless wait_thr.value == 0
|
|
||||||
exit_status = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
exit_status
|
|
||||||
end
|
|
||||||
|
|
||||||
def hook_file(hook_type, repo_path)
|
|
||||||
hook_path = File.join(repo_path.strip, 'hooks')
|
|
||||||
hook_file = "#{hook_path}/#{hook_type}"
|
|
||||||
hook_file if File.exist?(hook_file)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -27,6 +27,7 @@ module Projects
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
project.satellite.destroy
|
||||||
log_info("Project \"#{project.name}\" was removed")
|
log_info("Project \"#{project.name}\" was removed")
|
||||||
system_hook_service.execute_hooks_for(project, :destroy)
|
system_hook_service.execute_hooks_for(project, :destroy)
|
||||||
true
|
true
|
||||||
|
|
|
@ -33,6 +33,9 @@ module Projects
|
||||||
raise TransferError.new("Project with same path in target namespace already exists")
|
raise TransferError.new("Project with same path in target namespace already exists")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Remove old satellite
|
||||||
|
project.satellite.destroy
|
||||||
|
|
||||||
# Apply new namespace id
|
# Apply new namespace id
|
||||||
project.namespace = new_namespace
|
project.namespace = new_namespace
|
||||||
project.save!
|
project.save!
|
||||||
|
@ -48,6 +51,9 @@ module Projects
|
||||||
# Move wiki repo also if present
|
# Move wiki repo also if present
|
||||||
gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki")
|
gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki")
|
||||||
|
|
||||||
|
# Create a new satellite (reload project from DB)
|
||||||
|
Project.find(project.id).ensure_satellite_exists
|
||||||
|
|
||||||
# clear project cached events
|
# clear project cached events
|
||||||
project.reset_events_cache
|
project.reset_events_cache
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,16 @@
|
||||||
:plain
|
:plain
|
||||||
job = $("tr#repo_#{@repo_id}")
|
job = $("tr#repo_#{@repo_id}")
|
||||||
job.find(".import-actions").html("<p class='alert alert-danger'>Access denied! Please verify you can add deploy keys to this repository.</p>")
|
job.find(".import-actions").html("<p class='alert alert-danger'>Access denied! Please verify you can add deploy keys to this repository.</p>")
|
||||||
- else
|
- elsif @project.persisted?
|
||||||
:plain
|
:plain
|
||||||
job = $("tr#repo_#{@repo_id}")
|
job = $("tr#repo_#{@repo_id}")
|
||||||
job.attr("id", "project_#{@project.id}")
|
job.attr("id", "project_#{@project.id}")
|
||||||
target_field = job.find(".import-target")
|
target_field = job.find(".import-target")
|
||||||
target_field.empty()
|
target_field.empty()
|
||||||
target_field.append('<strong>#{link_to @project.path_with_namespace, [@project.namespace.becomes(Namespace), @project]}</strong>')
|
target_field.append('<strong>#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}</strong>')
|
||||||
$("table.import-jobs tbody").prepend(job)
|
$("table.import-jobs tbody").prepend(job)
|
||||||
job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
|
job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
|
||||||
|
- else
|
||||||
|
:plain
|
||||||
|
job = $("tr#repo_#{@repo_id}")
|
||||||
|
job.find(".import-actions").html("<p class='alert alert-danger'>Error saving project: #{escape_javascript(@project.errors.messages.to_s)}</p>")
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
rather than Git. Please convert
|
rather than Git. Please convert
|
||||||
= link_to "them to Git,", "https://www.atlassian.com/git/tutorials/migrating-overview"
|
= link_to "them to Git,", "https://www.atlassian.com/git/tutorials/migrating-overview"
|
||||||
and go through the
|
and go through the
|
||||||
= link_to "import flow", status_import_bitbucket_path
|
= link_to "import flow", status_import_bitbucket_path, "data-no-turbolink" => "true"
|
||||||
again.
|
again.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
\/
|
\/
|
||||||
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
|
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
|
||||||
required: true, class: 'form-control new-file-name'
|
required: true, class: 'form-control new-file-name'
|
||||||
.pull-right
|
.pull-right
|
||||||
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
|
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
|
||||||
|
|
||||||
.file-content.code
|
.file-content.code
|
||||||
%pre.js-edit-mode-pane#editor
|
%pre.js-edit-mode-pane#editor
|
||||||
|
|
|
@ -6,12 +6,11 @@
|
||||||
= render 'shared/commit_message_container', params: params,
|
= render 'shared/commit_message_container', params: params,
|
||||||
placeholder: 'Add new file'
|
placeholder: 'Add new file'
|
||||||
|
|
||||||
- unless @project.empty_repo?
|
.form-group.branch
|
||||||
.form-group.branch
|
= label_tag 'branch', class: 'control-label' do
|
||||||
= label_tag 'branch', class: 'control-label' do
|
Branch
|
||||||
Branch
|
.col-sm-10
|
||||||
.col-sm-10
|
= text_field_tag 'new_branch', @ref, class: "form-control"
|
||||||
= text_field_tag 'new_branch', @ref, class: "form-control"
|
|
||||||
|
|
||||||
= hidden_field_tag 'content', '', id: 'file-content'
|
= hidden_field_tag 'content', '', id: 'file-content'
|
||||||
= render 'projects/commit_button', ref: @ref,
|
= render 'projects/commit_button', ref: @ref,
|
||||||
|
|
|
@ -43,6 +43,8 @@
|
||||||
cd existing_folder
|
cd existing_folder
|
||||||
git init
|
git init
|
||||||
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
|
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
|
||||||
|
git add .
|
||||||
|
git commit
|
||||||
git push -u origin master
|
git push -u origin master
|
||||||
|
|
||||||
- if can? current_user, :remove_project, @project
|
- if can? current_user, :remove_project, @project
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
- if @merge_request.compare_failed
|
- if @merge_request.compare_failed
|
||||||
.alert.alert-danger
|
.alert.alert-danger
|
||||||
%h4 Compare failed
|
%h4 Compare failed
|
||||||
%p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches.
|
%p We can't compare selected branches. It may be because of huge diff or satellite timeout. Please try again or select different branches.
|
||||||
- else
|
- else
|
||||||
.light-well
|
.light-well
|
||||||
.center
|
.center
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
= icon('history')
|
= icon('history')
|
||||||
Commits
|
Commits
|
||||||
%span.badge= @commits.size
|
%span.badge= @commits.size
|
||||||
%li.diffs-tab.active
|
%li.diffs-tab
|
||||||
= link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
|
= link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
|
||||||
= icon('list-alt')
|
= icon('list-alt')
|
||||||
Changes
|
Changes
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
.tab-content
|
.tab-content
|
||||||
#commits.commits.tab-pane
|
#commits.commits.tab-pane
|
||||||
= render "projects/commits/commits", project: @project
|
= render "projects/commits/commits", project: @project
|
||||||
#diffs.diffs.tab-pane.active
|
#diffs.diffs.tab-pane
|
||||||
- if @diffs.present?
|
- if @diffs.present?
|
||||||
= render "projects/diffs/diffs", diffs: @diffs, project: @project
|
= render "projects/diffs/diffs", diffs: @diffs, project: @project
|
||||||
- elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
|
- elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
|
||||||
|
|
|
@ -6,25 +6,40 @@
|
||||||
= render "projects/merge_requests/show/mr_box"
|
= render "projects/merge_requests/show/mr_box"
|
||||||
%hr
|
%hr
|
||||||
.append-bottom-20
|
.append-bottom-20
|
||||||
- if @merge_request.open?
|
.slead
|
||||||
.btn-group.btn-group-sm.pull-right
|
%span From
|
||||||
%a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} }
|
- if @merge_request.for_fork?
|
||||||
= icon('download')
|
%strong.label-branch<
|
||||||
Download as
|
- if @merge_request.source_project
|
||||||
%span.caret
|
= link_to @merge_request.source_project_namespace, namespace_project_path(@merge_request.source_project.namespace, @merge_request.source_project)
|
||||||
%ul.dropdown-menu
|
- else
|
||||||
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
|
\ #{@merge_request.source_project_namespace}
|
||||||
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
|
\:#{@merge_request.source_branch}
|
||||||
.light
|
|
||||||
%div
|
|
||||||
%span From
|
|
||||||
%span.label-branch #{source_branch_with_namespace(@merge_request)}
|
|
||||||
%span into
|
%span into
|
||||||
%span.label-branch #{@merge_request.target_branch}
|
%strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
|
||||||
- if @merge_request.open? && !@merge_request.branch_missing?
|
- else
|
||||||
%div
|
%strong.label-branch #{@merge_request.source_branch}
|
||||||
If you want to try or merge this request manually, you can use the
|
%span into
|
||||||
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
|
%strong.label-branch #{@merge_request.target_branch}
|
||||||
|
- if @merge_request.open?
|
||||||
|
.btn-group.btn-group-sm.pull-right
|
||||||
|
%a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} }
|
||||||
|
= icon('download')
|
||||||
|
Download as
|
||||||
|
%span.caret
|
||||||
|
%ul.dropdown-menu
|
||||||
|
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
|
||||||
|
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
|
||||||
|
|
||||||
|
- if @merge_request.open? and @merge_request.source_branch_exists?
|
||||||
|
.append-bottom-20
|
||||||
|
.slead
|
||||||
|
%span
|
||||||
|
Fetch the branch with
|
||||||
|
%strong.label-branch<
|
||||||
|
git fetch
|
||||||
|
\ #{@merge_request.source_project.http_url_to_repo}
|
||||||
|
\ #{@merge_request.source_branch}
|
||||||
|
|
||||||
= render "projects/merge_requests/show/how_to_merge"
|
= render "projects/merge_requests/show/how_to_merge"
|
||||||
= render "projects/merge_requests/widget/show.html.haml"
|
= render "projects/merge_requests/widget/show.html.haml"
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
= render "projects/commits/commits", project: @merge_request.project
|
= render "projects/commits/commits", project: @merge_request.source_project
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- if @merge_request_diff.collected?
|
- if @merge_request_diff.collected?
|
||||||
= render "projects/diffs/diffs", diffs: @merge_request.diffs, project: @merge_request.project
|
= render "projects/diffs/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project
|
||||||
- elsif @merge_request_diff.empty?
|
- elsif @merge_request_diff.empty?
|
||||||
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
|
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
|
||||||
- else
|
- else
|
||||||
|
|
|
@ -3,45 +3,42 @@
|
||||||
.modal-content
|
.modal-content
|
||||||
.modal-header
|
.modal-header
|
||||||
%a.close{href: "#", "data-dismiss" => "modal"} ×
|
%a.close{href: "#", "data-dismiss" => "modal"} ×
|
||||||
%h3 Check out, review and merge locally
|
%h3 How to merge
|
||||||
.modal-body
|
.modal-body
|
||||||
%p
|
- if @merge_request.for_fork?
|
||||||
%strong Step 1.
|
- source_remote = @merge_request.source_project.namespace.nil? ? "source" :@merge_request.source_project.namespace.path
|
||||||
Fetch and check out the branch for this merge request
|
- target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path
|
||||||
%pre.dark
|
%p
|
||||||
- if @merge_request.for_fork?
|
%strong Step 1.
|
||||||
|
Fetch the code and create a new branch pointing to it
|
||||||
|
%pre.dark
|
||||||
:preserve
|
:preserve
|
||||||
git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
|
git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
|
||||||
git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD
|
git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD
|
||||||
- else
|
%p
|
||||||
:preserve
|
%strong Step 2.
|
||||||
git fetch origin
|
Merge the branch and push the changes to GitLab
|
||||||
git checkout -b #{@merge_request.source_branch} origin/#{@merge_request.source_branch}
|
%pre.dark
|
||||||
%p
|
|
||||||
%strong Step 2.
|
|
||||||
Review the changes locally
|
|
||||||
|
|
||||||
%p
|
|
||||||
%strong Step 3.
|
|
||||||
Merge the branch and fix any conflicts that come up
|
|
||||||
%pre.dark
|
|
||||||
- if @merge_request.for_fork?
|
|
||||||
:preserve
|
:preserve
|
||||||
git checkout #{@merge_request.target_branch}
|
git checkout #{@merge_request.target_branch}
|
||||||
git merge --no-ff #{@merge_request.source_project_path}-#{@merge_request.source_branch}
|
git merge --no-ff #{@merge_request.source_project_path}-#{@merge_request.source_branch}
|
||||||
- else
|
git push origin #{@merge_request.target_branch}
|
||||||
|
- else
|
||||||
|
%p
|
||||||
|
%strong Step 1.
|
||||||
|
Update the repo and checkout the branch we are going to merge
|
||||||
|
%pre.dark
|
||||||
|
:preserve
|
||||||
|
git fetch origin
|
||||||
|
git checkout -b #{@merge_request.source_branch} origin/#{@merge_request.source_branch}
|
||||||
|
%p
|
||||||
|
%strong Step 2.
|
||||||
|
Merge the branch and push the changes to GitLab
|
||||||
|
%pre.dark
|
||||||
:preserve
|
:preserve
|
||||||
git checkout #{@merge_request.target_branch}
|
git checkout #{@merge_request.target_branch}
|
||||||
git merge --no-ff #{@merge_request.source_branch}
|
git merge --no-ff #{@merge_request.source_branch}
|
||||||
%p
|
git push origin #{@merge_request.target_branch}
|
||||||
%strong Step 4.
|
|
||||||
Push the result of the merge to GitLab
|
|
||||||
%pre.dark
|
|
||||||
:preserve
|
|
||||||
git push origin #{@merge_request.target_branch}
|
|
||||||
- unless @merge_request.can_be_merged_by?(current_user)
|
|
||||||
%p
|
|
||||||
Note that pushing to GitLab requires write access to this repository.
|
|
||||||
|
|
||||||
:javascript
|
:javascript
|
||||||
$(function(){
|
$(function(){
|
||||||
|
|
|
@ -6,7 +6,4 @@
|
||||||
- if @merge_request.closed_event
|
- if @merge_request.closed_event
|
||||||
by #{link_to_member(@project, @merge_request.closed_event.author, avatar: true)}
|
by #{link_to_member(@project, @merge_request.closed_event.author, avatar: true)}
|
||||||
#{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
|
#{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
|
||||||
%p
|
%p Changes were not merged into target branch
|
||||||
= succeed '.' do
|
|
||||||
The changes were not merged into
|
|
||||||
%span.label-branch= @merge_request.target_branch
|
|
||||||
|
|
|
@ -1,28 +1,49 @@
|
||||||
- if @merge_request.has_ci?
|
- if @merge_request.has_ci?
|
||||||
.mr-widget-heading
|
.mr-widget-heading
|
||||||
- [:success, :skipped, :canceled, :failed, :running, :pending].each do |status|
|
.ci_widget.ci-success{style: "display:none"}
|
||||||
|
= icon("check")
|
||||||
|
%span CI build passed
|
||||||
|
for #{@merge_request.last_commit_short_sha}.
|
||||||
|
= link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
|
||||||
|
|
||||||
|
.ci_widget.ci-skipped{style: "display:none"}
|
||||||
|
= icon("check")
|
||||||
|
%span CI build skipped
|
||||||
|
for #{@merge_request.last_commit_short_sha}.
|
||||||
|
= link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
|
||||||
|
|
||||||
|
.ci_widget.ci-failed{style: "display:none"}
|
||||||
|
= icon("times")
|
||||||
|
%span CI build failed
|
||||||
|
for #{@merge_request.last_commit_short_sha}.
|
||||||
|
= link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
|
||||||
|
|
||||||
|
- [:running, :pending].each do |status|
|
||||||
.ci_widget{class: "ci-#{status}", style: "display:none"}
|
.ci_widget{class: "ci-#{status}", style: "display:none"}
|
||||||
- if status == :success
|
= icon("clock-o")
|
||||||
- status = "passed"
|
|
||||||
= icon("check-circle")
|
|
||||||
- else
|
|
||||||
= icon("circle")
|
|
||||||
%span CI build #{status}
|
%span CI build #{status}
|
||||||
for #{@merge_request.last_commit_short_sha}.
|
for #{@merge_request.last_commit_short_sha}.
|
||||||
%span.ci-coverage
|
= link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
|
||||||
= link_to "View build details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
|
|
||||||
|
|
||||||
.ci_widget
|
.ci_widget
|
||||||
= icon("spinner spin")
|
= icon("spinner spin")
|
||||||
Checking CI status for #{@merge_request.last_commit_short_sha}…
|
Checking for CI status for #{@merge_request.last_commit_short_sha}
|
||||||
|
|
||||||
.ci_widget.ci-not_found{style: "display:none"}
|
.ci_widget.ci-not_found{style: "display:none"}
|
||||||
= icon("times-circle")
|
= icon("times")
|
||||||
Could not find CI status for #{@merge_request.last_commit_short_sha}.
|
%span Can not find commit in the CI server
|
||||||
|
for #{@merge_request.last_commit_short_sha}.
|
||||||
|
|
||||||
|
|
||||||
|
.ci_widget.ci-canceled{style: "display:none"}
|
||||||
|
= icon("times")
|
||||||
|
%span CI build canceled
|
||||||
|
for #{@merge_request.last_commit_short_sha}.
|
||||||
|
= link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
|
||||||
|
|
||||||
.ci_widget.ci-error{style: "display:none"}
|
.ci_widget.ci-error{style: "display:none"}
|
||||||
= icon("times-circle")
|
= icon("times")
|
||||||
Could not connect to the CI server. Please check your settings and try again.
|
%span Cannot connect to the CI server. Please check your settings and try again.
|
||||||
|
|
||||||
:coffeescript
|
:coffeescript
|
||||||
$ ->
|
$ ->
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
= render 'projects/merge_requests/widget/heading'
|
= render 'projects/merge_requests/widget/heading'
|
||||||
.mr-widget-body
|
.mr-widget-body
|
||||||
%h4
|
%h4
|
||||||
= icon("spinner spin")
|
Merge in progress...
|
||||||
Merge in progress…
|
|
||||||
%p
|
%p
|
||||||
This merge request is in the process of being merged, during which time it is locked and cannot be closed.
|
Merging is in progress. While merging this request is locked and cannot be closed.
|
||||||
|
|
||||||
|
|
|
@ -7,31 +7,23 @@
|
||||||
by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)}
|
by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)}
|
||||||
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
|
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
|
||||||
%div
|
%div
|
||||||
- if !@merge_request.source_branch_exists?
|
- if @source_branch.blank?
|
||||||
= succeed '.' do
|
Source branch has been removed
|
||||||
The changes were merged into
|
|
||||||
%span.label-branch= @merge_request.target_branch
|
|
||||||
The source branch has been removed.
|
|
||||||
|
|
||||||
- elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch)
|
- elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && @merge_request.merged?
|
||||||
.remove_source_branch_widget
|
.remove_source_branch_widget
|
||||||
%p
|
%p Changes merged into #{@merge_request.target_branch}. You can remove source branch now
|
||||||
= succeed '.' do
|
|
||||||
The changes were merged into
|
|
||||||
%span.label-branch= @merge_request.target_branch
|
|
||||||
You can remove the source branch now.
|
|
||||||
= link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
|
= link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
|
||||||
%i.fa.fa-times
|
%i.fa.fa-times
|
||||||
Remove Source Branch
|
Remove Source Branch
|
||||||
|
|
||||||
.remove_source_branch_widget.failed.hide
|
.remove_source_branch_widget.failed.hide
|
||||||
%p
|
Failed to remove source branch '#{@merge_request.source_branch}'
|
||||||
Failed to remove source branch '#{@merge_request.source_branch}'.
|
|
||||||
|
|
||||||
.remove_source_branch_in_progress.hide
|
.remove_source_branch_in_progress.hide
|
||||||
%p
|
%i.fa.fa-spinner.fa-spin
|
||||||
= icon('spinner spin')
|
|
||||||
Removing source branch '#{@merge_request.source_branch}'. Please wait. This page will be automatically reload.
|
Removing source branch '#{@merge_request.source_branch}'. Please wait. Page will be automatically reloaded.
|
||||||
|
|
||||||
:coffeescript
|
:coffeescript
|
||||||
$('.remove_source_branch').on 'click', ->
|
$('.remove_source_branch').on 'click', ->
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
.mr-widget-body
|
.mr-widget-body
|
||||||
- if @project.archived?
|
- if @project.archived?
|
||||||
= render 'projects/merge_requests/widget/open/archived'
|
= render 'projects/merge_requests/widget/open/archived'
|
||||||
|
- elsif !@project.satellite.exists?
|
||||||
|
= render 'projects/merge_requests/widget/open/no_satellite'
|
||||||
- elsif @merge_request.commits.blank?
|
- elsif @merge_request.commits.blank?
|
||||||
= render 'projects/merge_requests/widget/open/nothing'
|
= render 'projects/merge_requests/widget/open/nothing'
|
||||||
- elsif @merge_request.branch_missing?
|
- elsif @merge_request.branch_missing?
|
||||||
|
@ -22,6 +24,6 @@
|
||||||
.mr-widget-footer
|
.mr-widget-footer
|
||||||
%span
|
%span
|
||||||
%i.fa.fa-check
|
%i.fa.fa-check
|
||||||
Accepting this merge request will close #{"issue".pluralize(@closes_issues.size)}
|
Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
|
||||||
= succeed '.' do
|
= succeed '.' do
|
||||||
!= gfm(issues_sentence(@closes_issues))
|
!= gfm(issues_sentence(@closes_issues))
|
||||||
|
|
|
@ -11,10 +11,10 @@
|
||||||
var merge_request_widget;
|
var merge_request_widget;
|
||||||
|
|
||||||
merge_request_widget = new MergeRequestWidget({
|
merge_request_widget = new MergeRequestWidget({
|
||||||
url_to_automerge_check: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
|
url_to_automerge_check: "#{automerge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
|
||||||
check_enable: #{@merge_request.unchecked? ? "true" : "false"},
|
check_enable: #{@merge_request.unchecked? ? "true" : "false"},
|
||||||
url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
|
url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
|
||||||
ci_enable: #{@project.ci_service ? "true" : "false"},
|
ci_enable: #{@project.ci_service ? "true" : "false"},
|
||||||
current_status: "#{@merge_request.gitlab_merge_status}",
|
current_status: "#{@merge_request.automerge_status}",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
= form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f|
|
= form_for [:automerge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f|
|
||||||
= hidden_field_tag :authenticity_token, form_authenticity_token
|
= hidden_field_tag :authenticity_token, form_authenticity_token
|
||||||
.accept-merge-holder.clearfix.js-toggle-container
|
.accept-merge-holder.clearfix.js-toggle-container
|
||||||
.accept-action
|
.accept-action
|
||||||
|
@ -8,16 +8,22 @@
|
||||||
.accept-control.checkbox
|
.accept-control.checkbox
|
||||||
= label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
|
= label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
|
||||||
= check_box_tag :should_remove_source_branch
|
= check_box_tag :should_remove_source_branch
|
||||||
Remove source branch
|
Remove source-branch
|
||||||
.accept-control
|
.accept-control
|
||||||
= link_to "#", class: "modify-merge-commit-link js-toggle-button" do
|
= link_to "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" do
|
||||||
= icon('edit')
|
%i.fa.fa-edit
|
||||||
Modify commit message
|
Modify commit message
|
||||||
.js-toggle-content.hide.prepend-top-20
|
.js-toggle-content.hide.prepend-top-20
|
||||||
= render 'shared/commit_message_container', params: params,
|
= render 'shared/commit_message_container', params: params,
|
||||||
text: @merge_request.merge_commit_message,
|
text: @merge_request.merge_commit_message,
|
||||||
rows: 14, hint: true
|
rows: 14, hint: true
|
||||||
|
|
||||||
|
%br
|
||||||
|
.light
|
||||||
|
If you want to merge this request manually, you can use the
|
||||||
|
%strong
|
||||||
|
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
|
||||||
|
|
||||||
:coffeescript
|
:coffeescript
|
||||||
$('.accept-mr-form').on 'ajax:before', ->
|
$('.accept-mr-form').on 'ajax:before', ->
|
||||||
btn = $('.accept_merge_request')
|
btn = $('.accept_merge_request')
|
||||||
|
|
|
@ -1,4 +1,2 @@
|
||||||
%h4
|
|
||||||
Project is archived
|
|
||||||
%p
|
%p
|
||||||
This merge request cannot be merged because archived projects cannot be written to.
|
%strong Archived projects do not provide commit access.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
%strong
|
%strong
|
||||||
= icon("spinner spin")
|
%i.fa.fa-spinner.fa-spin
|
||||||
Checking ability to merge automatically…
|
Checking automatic merge…
|
||||||
|
|
||||||
:coffeescript
|
:coffeescript
|
||||||
$ ->
|
$ ->
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
%h4
|
- if @merge_request.can_be_merged_by?(current_user)
|
||||||
= icon("exclamation-triangle")
|
%h4
|
||||||
This merge request contains merge conflicts
|
This merge request contains merge conflicts that must be resolved.
|
||||||
|
%p
|
||||||
%p
|
You can merge it manually using the
|
||||||
Please resolve these conflicts or
|
%strong
|
||||||
- if @merge_request.can_be_merged_by?(current_user)
|
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
|
||||||
#{link_to "merge this request manually", "#modal_merge_info", class: "how_to_merge_link vlink", "data-toggle" => "modal"}.
|
- else
|
||||||
- else
|
%strong This merge request contains merge conflicts that must be resolved.
|
||||||
ask someone with write access to this repository to merge this request manually.
|
Only those with write access to this repository can merge merge requests.
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
- unless @merge_request.source_branch_exists?
|
%h4
|
||||||
%h4
|
Can't be merged
|
||||||
= icon("exclamation-triangle")
|
%p
|
||||||
Source branch
|
This merge request can not be accepted because branch
|
||||||
%span.label-branch= source_branch_with_namespace(@merge_request)
|
- unless @merge_request.source_branch_exists?
|
||||||
does not exist
|
%span.label.label-inverse= @merge_request.source_branch
|
||||||
%p
|
does not exist in
|
||||||
Please restore the source branch or close this merge request and open a new merge request with a different source branch.
|
%span.label.label-info= @merge_request.source_project_path
|
||||||
- else
|
%br
|
||||||
%h4
|
%strong Please close this merge request and open a new merge request to change source branches.
|
||||||
= icon("exclamation-triangle")
|
- else
|
||||||
Target branch
|
%span.label.label-inverse= @merge_request.target_branch
|
||||||
%span.label-branch= @merge_request.target_branch
|
does not exist in
|
||||||
does not exist
|
%span.label.label-info= @merge_request.target_project_path
|
||||||
%p
|
%br
|
||||||
Please restore the target branch or use a different target branch.
|
%strong Please close this merge request or change to another target branch.
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
%p
|
||||||
|
%span
|
||||||
|
%strong This repository does not have a satellite. Please ask an administrator to fix this issue!
|
|
@ -1,4 +1,2 @@
|
||||||
%h4
|
%strong This request can be merged automatically.
|
||||||
Ready to be merged automatically
|
Only those with write access to this repository can merge merge requests.
|
||||||
%p
|
|
||||||
Ask someone with write access to this repository to merge this request.
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
%h4
|
%h4 Nothing to merge
|
||||||
= icon("exclamation-triangle")
|
|
||||||
Nothing to merge from
|
|
||||||
%span.label-branch= source_branch_with_namespace(@merge_request)
|
|
||||||
into
|
|
||||||
%span.label-branch= @merge_request.target_branch
|
|
||||||
%p
|
%p
|
||||||
Please push new commits to the source branch or use a different target branch.
|
Nothing to merge from
|
||||||
|
%span.label-branch #{@merge_request.source_branch}
|
||||||
|
to
|
||||||
|
%span.label-branch #{@merge_request.target_branch}
|
||||||
|
%br
|
||||||
|
Try to use different branches or push new code.
|
||||||
|
|
|
@ -1,6 +1 @@
|
||||||
%h4
|
This merge request cannot be merged. Try to reload the page.
|
||||||
= icon("exclamation-triangle")
|
|
||||||
This merge request failed to be merged automatically
|
|
||||||
|
|
||||||
%p
|
|
||||||
Please reload the page to find out the reason.
|
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
%h4
|
- if @merge_request.can_be_merged_by?(current_user)
|
||||||
This merge request is currently a Work In Progress
|
%h4
|
||||||
|
This merge request cannot be accepted because it is marked as Work In Progress.
|
||||||
|
|
||||||
%p
|
%p
|
||||||
When this merge request is ready, remove the "WIP" prefix from the title to allow it to be merged.
|
%button.btn.disabled{:type => 'button'}
|
||||||
|
%i.fa.fa-warning
|
||||||
|
Accept Merge Request
|
||||||
|
|
||||||
|
When the merge request is ready, remove the "WIP" prefix from the title to allow it to be accepted.
|
||||||
|
- else
|
||||||
|
%strong This merge request is marked as Work In Progress.
|
||||||
|
Only those with write access to this repository can merge merge requests.
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
|
|
||||||
|
|
||||||
- if bitbucket_import_enabled?
|
- if bitbucket_import_enabled?
|
||||||
= link_to status_import_bitbucket_path, class: 'btn' do
|
= link_to status_import_bitbucket_path, class: 'btn', "data-no-turbolink" => "true" do
|
||||||
%i.fa.fa-bitbucket
|
%i.fa.fa-bitbucket
|
||||||
Bitbucket
|
Bitbucket
|
||||||
- else
|
- else
|
||||||
|
|
|
@ -23,18 +23,21 @@
|
||||||
= link_to namespace_project_tags_path(@project.namespace, @project) do
|
= link_to namespace_project_tags_path(@project.namespace, @project) do
|
||||||
= pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
|
= pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
|
||||||
|
|
||||||
|
- if !prefer_readme? && @repository.readme
|
||||||
|
%li
|
||||||
|
= link_to 'Readme', readme_path(@project)
|
||||||
|
|
||||||
- if @repository.changelog
|
- if @repository.changelog
|
||||||
%li
|
%li
|
||||||
= link_to changelog_path(@project) do
|
= link_to 'Changelog', changelog_path(@project)
|
||||||
Changelog
|
|
||||||
- if @repository.license
|
- if @repository.license
|
||||||
%li
|
%li
|
||||||
= link_to license_path(@project) do
|
= link_to 'License', license_path(@project)
|
||||||
License
|
|
||||||
- if @repository.contribution_guide
|
- if @repository.contribution_guide
|
||||||
%li
|
%li
|
||||||
= link_to contribution_guide_path(@project) do
|
= link_to 'Contribution guide', contribution_guide_path(@project)
|
||||||
Contribution guide
|
|
||||||
|
|
||||||
- if current_user && can_push_branch?(@project, @project.default_branch)
|
- if current_user && can_push_branch?(@project, @project.default_branch)
|
||||||
- unless @repository.changelog
|
- unless @repository.changelog
|
||||||
|
|
|
@ -16,10 +16,10 @@
|
||||||
%p.help-block
|
%p.help-block
|
||||||
- if issuable.work_in_progress?
|
- if issuable.work_in_progress?
|
||||||
Remove the <code>WIP</code> prefix from the title to allow this
|
Remove the <code>WIP</code> prefix from the title to allow this
|
||||||
<strong>Work In Progress</strong> merge request to be merged when it's ready.
|
<strong>Work In Progress</strong> merge request to be accepted when it's ready.
|
||||||
- else
|
- else
|
||||||
Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a
|
Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a
|
||||||
<strong>Work In Progress</strong> merge request from being merged before it's ready.
|
<strong>Work In Progress</strong> merge request from being accepted before it's ready.
|
||||||
.form-group.issuable-description
|
.form-group.issuable-description
|
||||||
= f.label :description, 'Description', class: 'control-label'
|
= f.label :description, 'Description', class: 'control-label'
|
||||||
.col-sm-10
|
.col-sm-10
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
class AutoMergeWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
sidekiq_options queue: :default
|
||||||
|
|
||||||
|
def perform(merge_request_id, current_user_id, params)
|
||||||
|
params = params.with_indifferent_access
|
||||||
|
current_user = User.find(current_user_id)
|
||||||
|
merge_request = MergeRequest.find(merge_request_id)
|
||||||
|
merge_request.should_remove_source_branch = params[:should_remove_source_branch]
|
||||||
|
merge_request.automerge!(current_user, params[:commit_message])
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,19 +0,0 @@
|
||||||
class MergeWorker
|
|
||||||
include Sidekiq::Worker
|
|
||||||
|
|
||||||
sidekiq_options queue: :default
|
|
||||||
|
|
||||||
def perform(merge_request_id, current_user_id, params)
|
|
||||||
params = params.with_indifferent_access
|
|
||||||
current_user = User.find(current_user_id)
|
|
||||||
merge_request = MergeRequest.find(merge_request_id)
|
|
||||||
|
|
||||||
result = MergeRequests::MergeService.new(merge_request.target_project, current_user).
|
|
||||||
execute(merge_request, params[:commit_message])
|
|
||||||
|
|
||||||
if result[:status] == :success && params[:should_remove_source_branch].present?
|
|
||||||
DeleteBranchService.new(merge_request.source_project, current_user).
|
|
||||||
execute(merge_request.source_branch)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -27,6 +27,7 @@ class RepositoryImportWorker
|
||||||
|
|
||||||
project.import_finish
|
project.import_finish
|
||||||
project.save
|
project.save
|
||||||
|
project.satellite.create unless project.satellite.exists?
|
||||||
ProjectCacheWorker.perform_async(project.id)
|
ProjectCacheWorker.perform_async(project.id)
|
||||||
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
|
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
|
||||||
end
|
end
|
||||||
|
|
|
@ -463,8 +463,8 @@ Gitlab::Application.routes.draw do
|
||||||
member do
|
member do
|
||||||
get :diffs
|
get :diffs
|
||||||
get :commits
|
get :commits
|
||||||
post :merge
|
post :automerge
|
||||||
get :merge_check
|
get :automerge_check
|
||||||
get :ci_status
|
get :ci_status
|
||||||
post :toggle_subscription
|
post :toggle_subscription
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Use Libravatar service with GitLab
|
# Use Libravatar service with GitLab
|
||||||
|
|
||||||
GitLab by default supports [Gravatar](gravatar.com) avatar service.
|
GitLab by default supports [Gravatar](https://gravatar.com) avatar service.
|
||||||
Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is
|
Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is
|
||||||
[heavily based on gravatar](http://wiki.libravatar.org/api/).
|
[heavily based on gravatar](http://wiki.libravatar.org/api/).
|
||||||
|
|
||||||
|
|
|
@ -56,9 +56,9 @@ To serve repositories over SSH there's an add-on application called gitlab-shell
|
||||||
|
|
||||||
A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs.
|
A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs.
|
||||||
|
|
||||||
The GitLab web app uses MySQL or PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository.
|
The GitLab web app uses MySQL or PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository. `/home/git/gitlab-satellites` keeps checked out repositories when performing actions such as a merge request, editing files in the web interface, etc.
|
||||||
|
|
||||||
When serving repositories over HTTP/HTTPS GitLab utilizes the GitLab API to resolve authorization and access as well as serving git objects.
|
The satellite repository is used by the web interface for editing repositories and the wiki which is also a git repository. When serving repositories over HTTP/HTTPS GitLab utilizes the GitLab API to resolve authorization and access as well as serving git objects.
|
||||||
|
|
||||||
The add-on component gitlab-shell serves repositories over SSH. It manages the SSH keys within `/home/git/.ssh/authorized_keys` which should not be manually edited. gitlab-shell accesses the bare repositories directly to serve git objects and communicates with redis to submit jobs to Sidekiq for GitLab to process. gitlab-shell queries the GitLab API to determine authorization and access.
|
The add-on component gitlab-shell serves repositories over SSH. It manages the SSH keys within `/home/git/.ssh/authorized_keys` which should not be manually edited. gitlab-shell accesses the bare repositories directly to serve git objects and communicates with redis to submit jobs to Sidekiq for GitLab to process. gitlab-shell queries the GitLab API to determine authorization and access.
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ Note: `/home/git/` is shorthand for `/home/git`.
|
||||||
|
|
||||||
gitlabhq (includes Unicorn and Sidekiq logs)
|
gitlabhq (includes Unicorn and Sidekiq logs)
|
||||||
|
|
||||||
- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `githost.log` and `unicorn.stderr.log` normally.
|
- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `githost.log`, `satellites.log`, and `unicorn.stderr.log` normally.
|
||||||
|
|
||||||
gitlab-shell
|
gitlab-shell
|
||||||
|
|
||||||
|
|
|
@ -216,6 +216,10 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
|
||||||
sudo chmod -R u+rwX,go-w log/
|
sudo chmod -R u+rwX,go-w log/
|
||||||
sudo chmod -R u+rwX tmp/
|
sudo chmod -R u+rwX tmp/
|
||||||
|
|
||||||
|
# Create directory for satellites
|
||||||
|
sudo -u git -H mkdir /home/git/gitlab-satellites
|
||||||
|
sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites
|
||||||
|
|
||||||
# Make sure GitLab can write to the tmp/pids/ and tmp/sockets/ directories
|
# Make sure GitLab can write to the tmp/pids/ and tmp/sockets/ directories
|
||||||
sudo chmod -R u+rwX tmp/pids/
|
sudo chmod -R u+rwX tmp/pids/
|
||||||
sudo chmod -R u+rwX tmp/sockets/
|
sudo chmod -R u+rwX tmp/sockets/
|
||||||
|
|
|
@ -40,7 +40,7 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
|
||||||
|
|
||||||
### Storage
|
### Storage
|
||||||
|
|
||||||
The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least as much free space as all your repos combined take up.
|
The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
|
||||||
|
|
||||||
If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
|
If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ To change the Unicorn workers when you have the Omnibus package please see [the
|
||||||
|
|
||||||
## Database
|
## Database
|
||||||
|
|
||||||
If you want to run the database separately, the **recommended** database size is **1 MB per user**.
|
If you want to run the database separately expect a size of about 1 MB per user.
|
||||||
|
|
||||||
## Redis and Sidekiq
|
## Redis and Sidekiq
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,16 @@ This is the directory structure you will end up with following the instructions
|
||||||
| |-- git
|
| |-- git
|
||||||
| |-- .ssh
|
| |-- .ssh
|
||||||
| |-- gitlab
|
| |-- gitlab
|
||||||
|
| |-- gitlab-satellites
|
||||||
| |-- gitlab-shell
|
| |-- gitlab-shell
|
||||||
| |-- repositories
|
| |-- repositories
|
||||||
|
|
||||||
* `/home/git/.ssh` - contains openssh settings. Specifically the `authorized_keys` file managed by gitlab-shell.
|
* `/home/git/.ssh` - contains openssh settings. Specifically the `authorized_keys` file managed by gitlab-shell.
|
||||||
* `/home/git/gitlab` - GitLab core software.
|
* `/home/git/gitlab` - GitLab core software.
|
||||||
|
* `/home/git/gitlab-satellites` - checked out repositories for merge requests and file editing from web UI. This can be treated as a temporary files directory.
|
||||||
* `/home/git/gitlab-shell` - Core add-on component of GitLab. Maintains SSH cloning and other functionality.
|
* `/home/git/gitlab-shell` - Core add-on component of GitLab. Maintains SSH cloning and other functionality.
|
||||||
* `/home/git/repositories` - bare repositories for all projects organized by namespace. This is where the git repositories which are pushed/pulled are maintained for all projects. **This area is critical data for projects. [Keep a backup](../raketasks/backup_restore.md)**
|
* `/home/git/repositories` - bare repositories for all projects organized by namespace. This is where the git repositories which are pushed/pulled are maintained for all projects. **This area is critical data for projects. [Keep a backup](../raketasks/backup_restore.md)**
|
||||||
|
|
||||||
*Note: the default locations for repositories can be configured in `config/gitlab.yml` of GitLab and `config.yml` of gitlab-shell.*
|
*Note: the default locations for gitlab-satellites and repositories can be configured in `config/gitlab.yml` of GitLab and `config.yml` of gitlab-shell.*
|
||||||
|
|
||||||
To see a more in-depth overview see the [GitLab architecture doc](../development/architecture.md).
|
To see a more in-depth overview see the [GitLab architecture doc](../development/architecture.md).
|
||||||
|
|
|
@ -51,6 +51,16 @@ December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/
|
||||||
error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git'
|
error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### satellites.log
|
||||||
|
This file lives in `/var/log/gitlab/gitlab-rails/satellites.log` for omnibus package or in `/home/git/gitlab/log/satellites.log` for installations from the source.
|
||||||
|
|
||||||
|
In some cases GitLab should perform write actions to git repository, for example when it is needed to merge the merge request or edit a file with online editor. If something went wrong you can look into this file to find out what exactly happened.
|
||||||
|
```
|
||||||
|
October 07, 2014 11:36: Failed to create satellite for Chesley Weimann III / project1817
|
||||||
|
October 07, 2014 11:36: PID: 1872: git clone /Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/repositories/conrad6841/gitlabhq.git /Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/conrad6841/gitlabhq
|
||||||
|
October 07, 2014 11:36: PID: 1872: -> fatal: repository '/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/repositories/conrad6841/gitlabhq.git' does not exist
|
||||||
|
```
|
||||||
|
|
||||||
#### sidekiq.log
|
#### sidekiq.log
|
||||||
This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for omnibus package or in `/home/git/gitlab/log/sidekiq.log` for installations from the source.
|
This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for omnibus package or in `/home/git/gitlab/log/sidekiq.log` for installations from the source.
|
||||||
|
|
||||||
|
@ -89,4 +99,4 @@ W, [2015-02-13T07:16:01.313000 #9094] WARN -- : Unicorn::WorkerKiller send SIGQ
|
||||||
I, [2015-02-13T07:16:01.530733 #9047] INFO -- : reaped #<Process::Status: pid 9094 exit 0> worker=1
|
I, [2015-02-13T07:16:01.530733 #9047] INFO -- : reaped #<Process::Status: pid 9094 exit 0> worker=1
|
||||||
I, [2015-02-13T07:16:01.534501 #13379] INFO -- : worker=1 spawned pid=13379
|
I, [2015-02-13T07:16:01.534501 #13379] INFO -- : worker=1 spawned pid=13379
|
||||||
I, [2015-02-13T07:16:01.534848 #13379] INFO -- : worker=1 ready
|
I, [2015-02-13T07:16:01.534848 #13379] INFO -- : worker=1 ready
|
||||||
```
|
```
|
|
@ -105,11 +105,24 @@ Log directory writable? ... yes
|
||||||
Tmp directory writable? ... yes
|
Tmp directory writable? ... yes
|
||||||
Init script exists? ... yes
|
Init script exists? ... yes
|
||||||
Init script up-to-date? ... yes
|
Init script up-to-date? ... yes
|
||||||
|
Projects have satellites? ... yes
|
||||||
Redis version >= 2.0.0? ... yes
|
Redis version >= 2.0.0? ... yes
|
||||||
|
|
||||||
Checking GitLab ... Finished
|
Checking GitLab ... Finished
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## (Re-)Create satellite repositories
|
||||||
|
|
||||||
|
This will create satellite repositories for all your projects.
|
||||||
|
|
||||||
|
If necessary, remove the `repo_satellites` directory and rerun the commands below.
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo -u git -H mkdir -p /home/git/gitlab-satellites
|
||||||
|
sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production
|
||||||
|
sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites
|
||||||
|
```
|
||||||
|
|
||||||
## Rebuild authorized_keys file
|
## Rebuild authorized_keys file
|
||||||
|
|
||||||
In some case it is necessary to rebuild the `authorized_keys` file.
|
In some case it is necessary to rebuild the `authorized_keys` file.
|
||||||
|
|
|
@ -13,5 +13,4 @@
|
||||||
- [Project users](add-user/add-user.md)
|
- [Project users](add-user/add-user.md)
|
||||||
- [Protected branches](protected_branches.md)
|
- [Protected branches](protected_branches.md)
|
||||||
- [Web Editor](web_editor.md)
|
- [Web Editor](web_editor.md)
|
||||||
- [Merge Requests](merge_requests.md)
|
|
||||||
- ["Work In Progress" Merge Requests](wip_merge_requests.md)
|
- ["Work In Progress" Merge Requests](wip_merge_requests.md)
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
# Merge Requests
|
|
||||||
|
|
||||||
Merge requests allow you to exchange changes you made to source code
|
|
||||||
|
|
||||||
## Checkout merge requests locally
|
|
||||||
|
|
||||||
Locate the section for your GitLab remote in the `.git/config` file. It looks like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
[remote "origin"]
|
|
||||||
url = https://gitlab.com/gitlab-org/gitlab-ce.git
|
|
||||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
|
||||||
```
|
|
||||||
|
|
||||||
Now add the line `fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*` to this section.
|
|
||||||
|
|
||||||
It should looks like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
[remote "origin"]
|
|
||||||
url = https://gitlab.com/gitlab-org/gitlab-ce.git
|
|
||||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
|
||||||
fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you can fetch all the merge requests requests:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git fetch origin
|
|
||||||
From https://gitlab.com/gitlab-org/gitlab-ce.git
|
|
||||||
* [new ref] refs/merge-requests/1/head -> origin/merge-requests/1
|
|
||||||
* [new ref] refs/merge-requests/2/head -> origin/merge-requests/2
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
To check out a particular merge request:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git checkout origin/merge-requests/1
|
|
||||||
```
|
|
|
@ -16,6 +16,6 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'Header "Rebuild project satellites" should have correct ids and links' do
|
step 'Header "Rebuild project satellites" should have correct ids and links' do
|
||||||
header_should_have_correct_id_and_link(2, 'Check GitLab configuration', 'check-gitlab-configuration', '.documentation')
|
header_should_have_correct_id_and_link(2, '(Re-)Create satellite repositories', 're-create-satellite-repositories', '.documentation')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -66,7 +66,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
|
||||||
|
|
||||||
def authored_merge_request
|
def authored_merge_request
|
||||||
@authored_merge_request ||= create :merge_request,
|
@authored_merge_request ||= create :merge_request,
|
||||||
source_branch: 'markdown',
|
source_branch: 'simple_merge_request',
|
||||||
author: current_user,
|
author: current_user,
|
||||||
target_project: project,
|
target_project: project,
|
||||||
source_project: project
|
source_project: project
|
||||||
|
@ -74,14 +74,14 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
|
||||||
|
|
||||||
def other_merge_request
|
def other_merge_request
|
||||||
@other_merge_request ||= create :merge_request,
|
@other_merge_request ||= create :merge_request,
|
||||||
source_branch: 'fix',
|
source_branch: '2_3_notes_fix',
|
||||||
target_project: project,
|
target_project: project,
|
||||||
source_project: project
|
source_project: project
|
||||||
end
|
end
|
||||||
|
|
||||||
def authored_merge_request_from_fork
|
def authored_merge_request_from_fork
|
||||||
@authored_merge_request_from_fork ||= create :merge_request,
|
@authored_merge_request_from_fork ||= create :merge_request,
|
||||||
source_branch: 'feature_conflict',
|
source_branch: 'basic_page',
|
||||||
author: current_user,
|
author: current_user,
|
||||||
target_project: public_project,
|
target_project: public_project,
|
||||||
source_project: forked_project
|
source_project: forked_project
|
||||||
|
@ -89,7 +89,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
|
||||||
|
|
||||||
def assigned_merge_request_from_fork
|
def assigned_merge_request_from_fork
|
||||||
@assigned_merge_request_from_fork ||= create :merge_request,
|
@assigned_merge_request_from_fork ||= create :merge_request,
|
||||||
source_branch: 'markdown',
|
source_branch: 'basic_page_fix',
|
||||||
assignee: current_user,
|
assignee: current_user,
|
||||||
target_project: public_project,
|
target_project: public_project,
|
||||||
source_project: forked_project
|
source_project: forked_project
|
||||||
|
|
|
@ -9,6 +9,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
|
||||||
@project = Project.find_by(name: "Shop")
|
@project = Project.find_by(name: "Shop")
|
||||||
@project ||= create(:project, name: "Shop")
|
@project ||= create(:project, name: "Shop")
|
||||||
@project.team << [@user, :reporter]
|
@project.team << [@user, :reporter]
|
||||||
|
@project.ensure_satellite_exists
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'I have a project forked off of "Shop" called "Forked Shop"' do
|
step 'I have a project forked off of "Shop" called "Forked Shop"' do
|
||||||
|
|
|
@ -198,10 +198,15 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'merge request "Bug NS-05" is mergeable' do
|
step 'merge request "Bug NS-05" is mergeable' do
|
||||||
|
merge_request.project.satellite.create
|
||||||
merge_request.mark_as_mergeable
|
merge_request.mark_as_mergeable
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'I accept this merge request' do
|
step 'I accept this merge request' do
|
||||||
|
Gitlab::Satellite::MergeAction.any_instance.stub(
|
||||||
|
merge!: true,
|
||||||
|
)
|
||||||
|
|
||||||
page.within '.mr-state-widget' do
|
page.within '.mr-state-widget' do
|
||||||
click_button "Accept Merge Request"
|
click_button "Accept Merge Request"
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,26 +3,6 @@ module API
|
||||||
class Files < Grape::API
|
class Files < Grape::API
|
||||||
before { authenticate! }
|
before { authenticate! }
|
||||||
|
|
||||||
helpers do
|
|
||||||
def commit_params(attrs)
|
|
||||||
{
|
|
||||||
file_path: attrs[:file_path],
|
|
||||||
current_branch: attrs[:branch_name],
|
|
||||||
target_branch: attrs[:branch_name],
|
|
||||||
commit_message: attrs[:commit_message],
|
|
||||||
file_content: attrs[:content],
|
|
||||||
file_content_encoding: attrs[:encoding]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def commit_response(attrs)
|
|
||||||
{
|
|
||||||
file_path: attrs[:file_path],
|
|
||||||
branch_name: attrs[:branch_name],
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
resource :projects do
|
resource :projects do
|
||||||
# Get file from repository
|
# Get file from repository
|
||||||
# File content is Base64 encoded
|
# File content is Base64 encoded
|
||||||
|
@ -93,11 +73,17 @@ module API
|
||||||
|
|
||||||
required_attributes! [:file_path, :branch_name, :content, :commit_message]
|
required_attributes! [:file_path, :branch_name, :content, :commit_message]
|
||||||
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
|
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
|
||||||
result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute
|
branch_name = attrs.delete(:branch_name)
|
||||||
|
file_path = attrs.delete(:file_path)
|
||||||
|
result = ::Files::CreateService.new(user_project, current_user, attrs, branch_name, file_path).execute
|
||||||
|
|
||||||
if result[:status] == :success
|
if result[:status] == :success
|
||||||
status(201)
|
status(201)
|
||||||
commit_response(attrs)
|
|
||||||
|
{
|
||||||
|
file_path: file_path,
|
||||||
|
branch_name: branch_name
|
||||||
|
}
|
||||||
else
|
else
|
||||||
render_api_error!(result[:message], 400)
|
render_api_error!(result[:message], 400)
|
||||||
end
|
end
|
||||||
|
@ -119,11 +105,17 @@ module API
|
||||||
|
|
||||||
required_attributes! [:file_path, :branch_name, :content, :commit_message]
|
required_attributes! [:file_path, :branch_name, :content, :commit_message]
|
||||||
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
|
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
|
||||||
result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute
|
branch_name = attrs.delete(:branch_name)
|
||||||
|
file_path = attrs.delete(:file_path)
|
||||||
|
result = ::Files::UpdateService.new(user_project, current_user, attrs, branch_name, file_path).execute
|
||||||
|
|
||||||
if result[:status] == :success
|
if result[:status] == :success
|
||||||
status(200)
|
status(200)
|
||||||
commit_response(attrs)
|
|
||||||
|
{
|
||||||
|
file_path: file_path,
|
||||||
|
branch_name: branch_name
|
||||||
|
}
|
||||||
else
|
else
|
||||||
http_status = result[:http_status] || 400
|
http_status = result[:http_status] || 400
|
||||||
render_api_error!(result[:message], http_status)
|
render_api_error!(result[:message], http_status)
|
||||||
|
@ -146,11 +138,17 @@ module API
|
||||||
|
|
||||||
required_attributes! [:file_path, :branch_name, :commit_message]
|
required_attributes! [:file_path, :branch_name, :commit_message]
|
||||||
attrs = attributes_for_keys [:file_path, :branch_name, :commit_message]
|
attrs = attributes_for_keys [:file_path, :branch_name, :commit_message]
|
||||||
result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute
|
branch_name = attrs.delete(:branch_name)
|
||||||
|
file_path = attrs.delete(:file_path)
|
||||||
|
result = ::Files::DeleteService.new(user_project, current_user, attrs, branch_name, file_path).execute
|
||||||
|
|
||||||
if result[:status] == :success
|
if result[:status] == :success
|
||||||
status(200)
|
status(200)
|
||||||
commit_response(attrs)
|
|
||||||
|
{
|
||||||
|
file_path: file_path,
|
||||||
|
branch_name: branch_name
|
||||||
|
}
|
||||||
else
|
else
|
||||||
render_api_error!(result[:message], 400)
|
render_api_error!(result[:message], 400)
|
||||||
end
|
end
|
||||||
|
|
|
@ -198,11 +198,7 @@ module API
|
||||||
|
|
||||||
if merge_request.open? && !merge_request.work_in_progress?
|
if merge_request.open? && !merge_request.work_in_progress?
|
||||||
if merge_request.can_be_merged?
|
if merge_request.can_be_merged?
|
||||||
commit_message = params[:merge_commit_message] || merge_request.merge_commit_message
|
merge_request.automerge!(current_user, params[:merge_commit_message] || merge_request.merge_commit_message)
|
||||||
|
|
||||||
::MergeRequests::MergeService.new(merge_request.target_project, current_user).
|
|
||||||
execute(merge_request, commit_message)
|
|
||||||
|
|
||||||
present merge_request, with: Entities::MergeRequest
|
present merge_request, with: Entities::MergeRequest
|
||||||
else
|
else
|
||||||
render_api_error!('Branch cannot be merged', 405)
|
render_api_error!('Branch cannot be merged', 405)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
require 'gitlab/git'
|
require 'gitlab/git'
|
||||||
|
|
||||||
module Gitlab
|
module Gitlab
|
||||||
|
autoload :Satellite, 'gitlab/satellite/satellite'
|
||||||
end
|
end
|
||||||
|
|
|
@ -217,6 +217,20 @@ module Gitlab
|
||||||
FileUtils.mv(full_path(old_name), full_path(new_name))
|
FileUtils.mv(full_path(old_name), full_path(new_name))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Remove GitLab Satellites for provided path (namespace or repo dir)
|
||||||
|
#
|
||||||
|
# Ex.
|
||||||
|
# rm_satellites("gitlab")
|
||||||
|
#
|
||||||
|
# rm_satellites("gitlab/gitlab-ci.git")
|
||||||
|
#
|
||||||
|
def rm_satellites(path)
|
||||||
|
raise ArgumentError.new("Path can't be blank") if path.blank?
|
||||||
|
|
||||||
|
satellites_path = File.join(Gitlab.config.satellites.path, path)
|
||||||
|
FileUtils.rm_r(satellites_path, force: true)
|
||||||
|
end
|
||||||
|
|
||||||
def url_to_repo(path)
|
def url_to_repo(path)
|
||||||
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
|
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
module Gitlab
|
||||||
|
module Satellite
|
||||||
|
class Action
|
||||||
|
DEFAULT_OPTIONS = { git_timeout: Gitlab.config.satellites.timeout.seconds }
|
||||||
|
|
||||||
|
attr_accessor :options, :project, :user
|
||||||
|
|
||||||
|
def initialize(user, project, options = {})
|
||||||
|
@options = DEFAULT_OPTIONS.merge(options)
|
||||||
|
@project = project
|
||||||
|
@user = user
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
# * Sets a 30s timeout for Git
|
||||||
|
# * Locks the satellite repo
|
||||||
|
# * Yields the prepared satellite repo
|
||||||
|
def in_locked_and_timed_satellite
|
||||||
|
Gitlab::ShellEnv.set_env(user)
|
||||||
|
|
||||||
|
Grit::Git.with_timeout(options[:git_timeout]) do
|
||||||
|
project.satellite.lock do
|
||||||
|
return yield project.satellite.repo
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue Errno::ENOMEM => ex
|
||||||
|
return handle_exception(ex)
|
||||||
|
rescue Grit::Git::GitTimeout => ex
|
||||||
|
return handle_exception(ex)
|
||||||
|
ensure
|
||||||
|
Gitlab::ShellEnv.reset_env
|
||||||
|
end
|
||||||
|
|
||||||
|
# * Recreates the satellite
|
||||||
|
# * Sets up Git variables for the user
|
||||||
|
#
|
||||||
|
# Note: use this within #in_locked_and_timed_satellite
|
||||||
|
def prepare_satellite!(repo)
|
||||||
|
project.satellite.clear_and_update!
|
||||||
|
|
||||||
|
if user
|
||||||
|
repo.config['user.name'] = user.name
|
||||||
|
repo.config['user.email'] = user.email
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_options(options = {})
|
||||||
|
{ raise: true, timeout: true }.merge(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_exception(exception)
|
||||||
|
Gitlab::GitLogger.error(exception.message)
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,44 @@
|
||||||
|
module Gitlab
|
||||||
|
module Satellite
|
||||||
|
class BranchesWithoutParent < StandardError; end
|
||||||
|
|
||||||
|
class CompareAction < Action
|
||||||
|
def initialize(user, target_project, target_branch, source_project, source_branch)
|
||||||
|
super user, target_project
|
||||||
|
|
||||||
|
@target_project, @target_branch = target_project, target_branch
|
||||||
|
@source_project, @source_branch = source_project, source_branch
|
||||||
|
end
|
||||||
|
|
||||||
|
# Compare 2 repositories and return Gitlab::CompareResult object
|
||||||
|
def result
|
||||||
|
in_locked_and_timed_satellite do |target_repo|
|
||||||
|
prepare_satellite!(target_repo)
|
||||||
|
update_satellite_source_and_target!(target_repo)
|
||||||
|
|
||||||
|
Gitlab::CompareResult.new(compare(target_repo))
|
||||||
|
end
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
raise BranchesWithoutParent
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for diffs
|
||||||
|
def update_satellite_source_and_target!(target_repo)
|
||||||
|
target_repo.remote_add('source', @source_project.repository.path_to_repo)
|
||||||
|
target_repo.remote_fetch('source')
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
handle_exception(ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
def compare(repo)
|
||||||
|
@compare ||= Gitlab::Git::Compare.new(
|
||||||
|
Gitlab::Git::Repository.new(repo.path),
|
||||||
|
"origin/#{@target_branch}",
|
||||||
|
"source/#{@source_branch}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,50 @@
|
||||||
|
require_relative 'file_action'
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Satellite
|
||||||
|
class DeleteFileAction < FileAction
|
||||||
|
# Deletes file and creates a new commit for it
|
||||||
|
#
|
||||||
|
# Returns false if committing the change fails
|
||||||
|
# Returns false if pushing from the satellite to bare repo failed or was rejected
|
||||||
|
# Returns true otherwise
|
||||||
|
def commit!(content, commit_message)
|
||||||
|
in_locked_and_timed_satellite do |repo|
|
||||||
|
prepare_satellite!(repo)
|
||||||
|
|
||||||
|
# create target branch in satellite at the corresponding commit from bare repo
|
||||||
|
repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
|
||||||
|
|
||||||
|
# update the file in the satellite's working dir
|
||||||
|
file_path_in_satellite = File.join(repo.working_dir, file_path)
|
||||||
|
|
||||||
|
# Prevent relative links
|
||||||
|
unless safe_path?(file_path_in_satellite)
|
||||||
|
Gitlab::GitLogger.error("FileAction: Relative path not allowed")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
File.delete(file_path_in_satellite)
|
||||||
|
|
||||||
|
# add removed file
|
||||||
|
repo.remove(file_path_in_satellite)
|
||||||
|
|
||||||
|
# commit the changes
|
||||||
|
# will raise CommandFailed when commit fails
|
||||||
|
repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
|
||||||
|
|
||||||
|
|
||||||
|
# push commit back to bare repo
|
||||||
|
# will raise CommandFailed when push fails
|
||||||
|
repo.git.push({ raise: true, timeout: true }, :origin, ref)
|
||||||
|
|
||||||
|
# everything worked
|
||||||
|
true
|
||||||
|
end
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
Gitlab::GitLogger.error(ex.message)
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,68 @@
|
||||||
|
require_relative 'file_action'
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Satellite
|
||||||
|
# GitLab server-side file update and commit
|
||||||
|
class EditFileAction < FileAction
|
||||||
|
# Updates the files content and creates a new commit for it
|
||||||
|
#
|
||||||
|
# Returns false if the ref has been updated while editing the file
|
||||||
|
# Returns false if committing the change fails
|
||||||
|
# Returns false if pushing from the satellite to bare repo failed or was rejected
|
||||||
|
# Returns true otherwise
|
||||||
|
def commit!(content, commit_message, encoding, new_branch = nil)
|
||||||
|
in_locked_and_timed_satellite do |repo|
|
||||||
|
prepare_satellite!(repo)
|
||||||
|
|
||||||
|
# create target branch in satellite at the corresponding commit from bare repo
|
||||||
|
begin
|
||||||
|
repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
log_and_raise(CheckoutFailed, ex.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
# update the file in the satellite's working dir
|
||||||
|
file_path_in_satellite = File.join(repo.working_dir, file_path)
|
||||||
|
|
||||||
|
# Prevent relative links
|
||||||
|
unless safe_path?(file_path_in_satellite)
|
||||||
|
Gitlab::GitLogger.error("FileAction: Relative path not allowed")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
# Write file
|
||||||
|
write_file(file_path_in_satellite, content, encoding)
|
||||||
|
|
||||||
|
# commit the changes
|
||||||
|
# will raise CommandFailed when commit fails
|
||||||
|
begin
|
||||||
|
repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
log_and_raise(CommitFailed, ex.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
target_branch = new_branch.present? ? "#{ref}:#{new_branch}" : ref
|
||||||
|
|
||||||
|
# push commit back to bare repo
|
||||||
|
# will raise CommandFailed when push fails
|
||||||
|
begin
|
||||||
|
repo.git.push({ raise: true, timeout: true }, :origin, target_branch)
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
log_and_raise(PushFailed, ex.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
# everything worked
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def log_and_raise(errorClass, message)
|
||||||
|
Gitlab::GitLogger.error(message)
|
||||||
|
raise(errorClass, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,25 @@
|
||||||
|
module Gitlab
|
||||||
|
module Satellite
|
||||||
|
class FileAction < Action
|
||||||
|
attr_accessor :file_path, :ref
|
||||||
|
|
||||||
|
def initialize(user, project, ref, file_path)
|
||||||
|
super user, project
|
||||||
|
@file_path = file_path
|
||||||
|
@ref = ref
|
||||||
|
end
|
||||||
|
|
||||||
|
def safe_path?(path)
|
||||||
|
File.absolute_path(path) == path
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_file(abs_file_path, content, file_encoding = 'text')
|
||||||
|
if file_encoding == 'base64'
|
||||||
|
File.open(abs_file_path, 'wb') { |f| f.write(Base64.decode64(content)) }
|
||||||
|
else
|
||||||
|
File.open(abs_file_path, 'w') { |f| f.write(content) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,67 @@
|
||||||
|
require_relative 'file_action'
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Satellite
|
||||||
|
class NewFileAction < FileAction
|
||||||
|
# Updates the files content and creates a new commit for it
|
||||||
|
#
|
||||||
|
# Returns false if the ref has been updated while editing the file
|
||||||
|
# Returns false if committing the change fails
|
||||||
|
# Returns false if pushing from the satellite to bare repo failed or was rejected
|
||||||
|
# Returns true otherwise
|
||||||
|
def commit!(content, commit_message, encoding, new_branch = nil)
|
||||||
|
in_locked_and_timed_satellite do |repo|
|
||||||
|
prepare_satellite!(repo)
|
||||||
|
|
||||||
|
# create target branch in satellite at the corresponding commit from bare repo
|
||||||
|
current_ref =
|
||||||
|
if @project.empty_repo?
|
||||||
|
# skip this step if we want to add first file to empty repo
|
||||||
|
Satellite::PARKING_BRANCH
|
||||||
|
else
|
||||||
|
repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
|
||||||
|
ref
|
||||||
|
end
|
||||||
|
|
||||||
|
file_path_in_satellite = File.join(repo.working_dir, file_path)
|
||||||
|
dir_name_in_satellite = File.dirname(file_path_in_satellite)
|
||||||
|
|
||||||
|
# Prevent relative links
|
||||||
|
unless safe_path?(file_path_in_satellite)
|
||||||
|
Gitlab::GitLogger.error("FileAction: Relative path not allowed")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create dir if not exists
|
||||||
|
FileUtils.mkdir_p(dir_name_in_satellite)
|
||||||
|
|
||||||
|
# Write file
|
||||||
|
write_file(file_path_in_satellite, content, encoding)
|
||||||
|
|
||||||
|
# add new file
|
||||||
|
repo.add(file_path_in_satellite)
|
||||||
|
|
||||||
|
# commit the changes
|
||||||
|
# will raise CommandFailed when commit fails
|
||||||
|
repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
|
||||||
|
|
||||||
|
target_branch = if new_branch.present? && !@project.empty_repo?
|
||||||
|
"#{ref}:#{new_branch}"
|
||||||
|
else
|
||||||
|
"#{current_ref}:#{ref}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# push commit back to bare repo
|
||||||
|
# will raise CommandFailed when push fails
|
||||||
|
repo.git.push({ raise: true, timeout: true }, :origin, target_branch)
|
||||||
|
|
||||||
|
# everything worked
|
||||||
|
true
|
||||||
|
end
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
Gitlab::GitLogger.error(ex.message)
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,13 @@
|
||||||
|
module Gitlab
|
||||||
|
module Satellite
|
||||||
|
class Logger < Gitlab::Logger
|
||||||
|
def self.file_name
|
||||||
|
'satellites.log'
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_message(severity, timestamp, progname, msg)
|
||||||
|
"#{timestamp.to_s(:long)}: #{msg}\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,146 @@
|
||||||
|
module Gitlab
|
||||||
|
module Satellite
|
||||||
|
# GitLab server-side merge
|
||||||
|
class MergeAction < Action
|
||||||
|
attr_accessor :merge_request
|
||||||
|
|
||||||
|
def initialize(user, merge_request)
|
||||||
|
super user, merge_request.target_project
|
||||||
|
@merge_request = merge_request
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks if a merge request can be executed without user interaction
|
||||||
|
def can_be_merged?
|
||||||
|
in_locked_and_timed_satellite do |merge_repo|
|
||||||
|
prepare_satellite!(merge_repo)
|
||||||
|
merge_in_satellite!(merge_repo)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Merges the source branch into the target branch in the satellite and
|
||||||
|
# pushes it back to the repository.
|
||||||
|
# It also removes the source branch if requested in the merge request (and this is permitted by the merge request).
|
||||||
|
#
|
||||||
|
# Returns false if the merge produced conflicts
|
||||||
|
# Returns false if pushing from the satellite to the repository failed or was rejected
|
||||||
|
# Returns true otherwise
|
||||||
|
def merge!(merge_commit_message = nil)
|
||||||
|
in_locked_and_timed_satellite do |merge_repo|
|
||||||
|
prepare_satellite!(merge_repo)
|
||||||
|
if merge_in_satellite!(merge_repo, merge_commit_message)
|
||||||
|
# push merge back to bare repo
|
||||||
|
# will raise CommandFailed when push fails
|
||||||
|
merge_repo.git.push(default_options, :origin, merge_request.target_branch)
|
||||||
|
|
||||||
|
# remove source branch
|
||||||
|
if merge_request.remove_source_branch?
|
||||||
|
# will raise CommandFailed when push fails
|
||||||
|
merge_repo.git.push(default_options, :origin, ":#{merge_request.source_branch}")
|
||||||
|
end
|
||||||
|
# merge, push and branch removal successful
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
handle_exception(ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
def diff_in_satellite
|
||||||
|
in_locked_and_timed_satellite do |merge_repo|
|
||||||
|
prepare_satellite!(merge_repo)
|
||||||
|
update_satellite_source_and_target!(merge_repo)
|
||||||
|
|
||||||
|
# Only show what is new in the source branch compared to the target branch, not the other way around.
|
||||||
|
# The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2)
|
||||||
|
# From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B"
|
||||||
|
common_commit = merge_repo.git.native(:merge_base, default_options, ["origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}"]).strip
|
||||||
|
merge_repo.git.native(:diff, default_options, common_commit, "source/#{merge_request.source_branch}")
|
||||||
|
end
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
handle_exception(ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
def diffs_between_satellite
|
||||||
|
in_locked_and_timed_satellite do |merge_repo|
|
||||||
|
prepare_satellite!(merge_repo)
|
||||||
|
update_satellite_source_and_target!(merge_repo)
|
||||||
|
if merge_request.for_fork?
|
||||||
|
repository = Gitlab::Git::Repository.new(merge_repo.path)
|
||||||
|
diffs = Gitlab::Git::Diff.between(
|
||||||
|
repository,
|
||||||
|
"source/#{merge_request.source_branch}",
|
||||||
|
"origin/#{merge_request.target_branch}"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
raise "Attempt to determine diffs between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
|
||||||
|
end
|
||||||
|
|
||||||
|
return diffs
|
||||||
|
end
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
handle_exception(ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get commit as an email patch
|
||||||
|
def format_patch
|
||||||
|
in_locked_and_timed_satellite do |merge_repo|
|
||||||
|
prepare_satellite!(merge_repo)
|
||||||
|
update_satellite_source_and_target!(merge_repo)
|
||||||
|
patch = merge_repo.git.format_patch(default_options({ stdout: true }), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}")
|
||||||
|
end
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
handle_exception(ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Retrieve an array of commits between the source and the target
|
||||||
|
def commits_between
|
||||||
|
in_locked_and_timed_satellite do |merge_repo|
|
||||||
|
prepare_satellite!(merge_repo)
|
||||||
|
update_satellite_source_and_target!(merge_repo)
|
||||||
|
if merge_request.for_fork?
|
||||||
|
repository = Gitlab::Git::Repository.new(merge_repo.path)
|
||||||
|
commits = Gitlab::Git::Commit.between(
|
||||||
|
repository,
|
||||||
|
"origin/#{merge_request.target_branch}",
|
||||||
|
"source/#{merge_request.source_branch}"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
raise "Attempt to determine commits between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
|
||||||
|
end
|
||||||
|
|
||||||
|
return commits
|
||||||
|
end
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
handle_exception(ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
# Merges the source_branch into the target_branch in the satellite.
|
||||||
|
#
|
||||||
|
# Note: it will clear out the satellite before doing anything
|
||||||
|
#
|
||||||
|
# Returns false if the merge produced conflicts
|
||||||
|
# Returns true otherwise
|
||||||
|
def merge_in_satellite!(repo, message = nil)
|
||||||
|
update_satellite_source_and_target!(repo)
|
||||||
|
|
||||||
|
message ||= "Merge branch '#{merge_request.source_branch}' into '#{merge_request.target_branch}'"
|
||||||
|
|
||||||
|
# merge the source branch into the satellite
|
||||||
|
# will raise CommandFailed when merge fails
|
||||||
|
repo.git.merge(default_options({ no_ff: true }), "-m#{message}", "source/#{merge_request.source_branch}")
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
handle_exception(ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for merges, diffs etc
|
||||||
|
def update_satellite_source_and_target!(repo)
|
||||||
|
repo.remote_add('source', merge_request.source_project.repository.path_to_repo)
|
||||||
|
repo.remote_fetch('source')
|
||||||
|
repo.git.checkout(default_options({ b: true }), merge_request.target_branch, "origin/#{merge_request.target_branch}")
|
||||||
|
rescue Grit::Git::CommandFailed => ex
|
||||||
|
handle_exception(ex)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,148 @@
|
||||||
|
module Gitlab
|
||||||
|
module Satellite
|
||||||
|
autoload :DeleteFileAction, 'gitlab/satellite/files/delete_file_action'
|
||||||
|
autoload :EditFileAction, 'gitlab/satellite/files/edit_file_action'
|
||||||
|
autoload :FileAction, 'gitlab/satellite/files/file_action'
|
||||||
|
autoload :NewFileAction, 'gitlab/satellite/files/new_file_action'
|
||||||
|
|
||||||
|
class CheckoutFailed < StandardError; end
|
||||||
|
class CommitFailed < StandardError; end
|
||||||
|
class PushFailed < StandardError; end
|
||||||
|
|
||||||
|
class Satellite
|
||||||
|
include Gitlab::Popen
|
||||||
|
|
||||||
|
PARKING_BRANCH = "__parking_branch"
|
||||||
|
|
||||||
|
attr_accessor :project
|
||||||
|
|
||||||
|
def initialize(project)
|
||||||
|
@project = project
|
||||||
|
end
|
||||||
|
|
||||||
|
def log(message)
|
||||||
|
Gitlab::Satellite::Logger.error(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_and_update!
|
||||||
|
project.ensure_satellite_exists
|
||||||
|
|
||||||
|
@repo = nil
|
||||||
|
clear_working_dir!
|
||||||
|
delete_heads!
|
||||||
|
remove_remotes!
|
||||||
|
update_from_source!
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
output, status = popen(%W(git clone -- #{project.repository.path_to_repo} #{path}),
|
||||||
|
Gitlab.config.satellites.path)
|
||||||
|
|
||||||
|
log("PID: #{project.id}: git clone #{project.repository.path_to_repo} #{path}")
|
||||||
|
log("PID: #{project.id}: -> #{output}")
|
||||||
|
|
||||||
|
if status.zero?
|
||||||
|
true
|
||||||
|
else
|
||||||
|
log("Failed to create satellite for #{project.name_with_namespace}")
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def exists?
|
||||||
|
File.exists? path
|
||||||
|
end
|
||||||
|
|
||||||
|
# * Locks the satellite
|
||||||
|
# * Changes the current directory to the satellite's working dir
|
||||||
|
# * Yields
|
||||||
|
def lock
|
||||||
|
project.ensure_satellite_exists
|
||||||
|
|
||||||
|
File.open(lock_file, "w+") do |f|
|
||||||
|
begin
|
||||||
|
f.flock File::LOCK_EX
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
f.flock File::LOCK_UN
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def lock_file
|
||||||
|
create_locks_dir unless File.exists?(lock_files_dir)
|
||||||
|
File.join(lock_files_dir, "satellite_#{project.id}.lock")
|
||||||
|
end
|
||||||
|
|
||||||
|
def path
|
||||||
|
File.join(Gitlab.config.satellites.path, project.path_with_namespace)
|
||||||
|
end
|
||||||
|
|
||||||
|
def repo
|
||||||
|
project.ensure_satellite_exists
|
||||||
|
|
||||||
|
@repo ||= Grit::Repo.new(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
FileUtils.rm_rf(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Clear the working directory
|
||||||
|
def clear_working_dir!
|
||||||
|
repo.git.reset(hard: true)
|
||||||
|
repo.git.clean(f: true, d: true, x: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Deletes all branches except the parking branch
|
||||||
|
#
|
||||||
|
# This ensures we have no name clashes or issues updating branches when
|
||||||
|
# working with the satellite.
|
||||||
|
def delete_heads!
|
||||||
|
heads = repo.heads.map(&:name)
|
||||||
|
|
||||||
|
# update or create the parking branch
|
||||||
|
repo.git.checkout(default_options({ B: true }), PARKING_BRANCH)
|
||||||
|
|
||||||
|
# remove the parking branch from the list of heads ...
|
||||||
|
heads.delete(PARKING_BRANCH)
|
||||||
|
# ... and delete all others
|
||||||
|
heads.each { |head| repo.git.branch(default_options({ D: true }), head) }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Deletes all remotes except origin
|
||||||
|
#
|
||||||
|
# This ensures we have no remote name clashes or issues updating branches when
|
||||||
|
# working with the satellite.
|
||||||
|
def remove_remotes!
|
||||||
|
remotes = repo.git.remote.split(' ')
|
||||||
|
remotes.delete('origin')
|
||||||
|
remotes.each { |name| repo.git.remote(default_options,'rm', name)}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Updates the satellite from bare repo
|
||||||
|
#
|
||||||
|
# Note: this will only update remote branches (i.e. origin/*)
|
||||||
|
def update_from_source!
|
||||||
|
repo.git.remote(default_options, 'set-url', :origin, project.repository.path_to_repo)
|
||||||
|
repo.git.fetch(default_options, :origin)
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_options(options = {})
|
||||||
|
{ raise: true, timeout: true }.merge(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create directory for storing
|
||||||
|
# satellites lock files
|
||||||
|
def create_locks_dir
|
||||||
|
FileUtils.mkdir_p(lock_files_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
def lock_files_dir
|
||||||
|
@lock_files_dir ||= File.join(Gitlab.config.satellites.path, "tmp")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -25,6 +25,7 @@ namespace :gitlab do
|
||||||
check_init_script_exists
|
check_init_script_exists
|
||||||
check_init_script_up_to_date
|
check_init_script_up_to_date
|
||||||
check_projects_have_namespace
|
check_projects_have_namespace
|
||||||
|
check_satellites_exist
|
||||||
check_redis_version
|
check_redis_version
|
||||||
check_ruby_version
|
check_ruby_version
|
||||||
check_git_version
|
check_git_version
|
||||||
|
@ -237,6 +238,37 @@ namespace :gitlab do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_satellites_exist
|
||||||
|
print "Projects have satellites? ... "
|
||||||
|
|
||||||
|
unless Project.count > 0
|
||||||
|
puts "can't check, you have no projects".magenta
|
||||||
|
return
|
||||||
|
end
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
Project.find_each(batch_size: 100) do |project|
|
||||||
|
print sanitized_message(project)
|
||||||
|
|
||||||
|
if project.satellite.exists?
|
||||||
|
puts "yes".green
|
||||||
|
elsif project.empty_repo?
|
||||||
|
puts "can't create, repository is empty".magenta
|
||||||
|
else
|
||||||
|
puts "no".red
|
||||||
|
try_fixing_it(
|
||||||
|
sudo_gitlab("bundle exec rake gitlab:satellites:create RAILS_ENV=production"),
|
||||||
|
"If necessary, remove the tmp/repo_satellites directory ...",
|
||||||
|
"... and rerun the above command"
|
||||||
|
)
|
||||||
|
for_more_information(
|
||||||
|
"doc/raketasks/maintenance.md "
|
||||||
|
)
|
||||||
|
fix_and_rerun
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def check_log_writable
|
def check_log_writable
|
||||||
print "Log directory writable? ... "
|
print "Log directory writable? ... "
|
||||||
|
|
||||||
|
@ -307,6 +339,7 @@ namespace :gitlab do
|
||||||
check_repo_base_is_not_symlink
|
check_repo_base_is_not_symlink
|
||||||
check_repo_base_user_and_group
|
check_repo_base_user_and_group
|
||||||
check_repo_base_permissions
|
check_repo_base_permissions
|
||||||
|
check_satellites_permissions
|
||||||
check_repos_hooks_directory_is_link
|
check_repos_hooks_directory_is_link
|
||||||
check_gitlab_shell_self_test
|
check_gitlab_shell_self_test
|
||||||
|
|
||||||
|
@ -384,6 +417,29 @@ namespace :gitlab do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_satellites_permissions
|
||||||
|
print "Satellites access is drwxr-x---? ... "
|
||||||
|
|
||||||
|
satellites_path = Gitlab.config.satellites.path
|
||||||
|
unless File.exists?(satellites_path)
|
||||||
|
puts "can't check because of previous errors".magenta
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if File.stat(satellites_path).mode.to_s(8).ends_with?("0750")
|
||||||
|
puts "yes".green
|
||||||
|
else
|
||||||
|
puts "no".red
|
||||||
|
try_fixing_it(
|
||||||
|
"sudo chmod u+rwx,g=rx,o-rwx #{satellites_path}",
|
||||||
|
)
|
||||||
|
for_more_information(
|
||||||
|
see_installation_guide_section "GitLab"
|
||||||
|
)
|
||||||
|
fix_and_rerun
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def check_repo_base_user_and_group
|
def check_repo_base_user_and_group
|
||||||
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
|
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
|
||||||
gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group
|
gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
namespace :gitlab do
|
||||||
|
namespace :satellites do
|
||||||
|
desc "GitLab | Create satellite repos"
|
||||||
|
task create: :environment do
|
||||||
|
create_satellites
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_satellites
|
||||||
|
warn_user_is_not_gitlab
|
||||||
|
|
||||||
|
print "Creating satellites for ..."
|
||||||
|
unless Project.count > 0
|
||||||
|
puts "skipping, because you have no projects".magenta
|
||||||
|
return
|
||||||
|
end
|
||||||
|
puts ""
|
||||||
|
|
||||||
|
Project.find_each(batch_size: 100) do |project|
|
||||||
|
print "#{project.name_with_namespace.yellow} ... "
|
||||||
|
|
||||||
|
unless project.repo_exists?
|
||||||
|
puts "skipping, because the repo is empty".magenta
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
if project.satellite.exists?
|
||||||
|
puts "exists already".green
|
||||||
|
else
|
||||||
|
print "\n... "
|
||||||
|
if project.satellite.create
|
||||||
|
puts "created".green
|
||||||
|
else
|
||||||
|
puts "error".red
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -25,7 +25,7 @@ describe Gitlab::ReferenceExtractor do
|
||||||
project.team << [@u_bar, :guest]
|
project.team << [@u_bar, :guest]
|
||||||
|
|
||||||
subject.analyze(%Q{
|
subject.analyze(%Q{
|
||||||
Inline code: `@foo`
|
Inline code: `@foo`
|
||||||
|
|
||||||
Code block:
|
Code block:
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ describe Gitlab::ReferenceExtractor do
|
||||||
@bar
|
@bar
|
||||||
```
|
```
|
||||||
|
|
||||||
Quote:
|
Quote:
|
||||||
|
|
||||||
> @offteam
|
> @offteam
|
||||||
})
|
})
|
||||||
|
@ -49,8 +49,8 @@ describe Gitlab::ReferenceExtractor do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'accesses valid merge requests' do
|
it 'accesses valid merge requests' do
|
||||||
@m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'markdown')
|
@m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'aaa')
|
||||||
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'feature_conflict')
|
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb')
|
||||||
|
|
||||||
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.")
|
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.")
|
||||||
expect(subject.merge_requests).to eq([@m1, @m0])
|
expect(subject.merge_requests).to eq([@m1, @m0])
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe 'Gitlab::Satellite::Action' do
|
||||||
|
let(:project) { create(:project) }
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
|
describe '#prepare_satellite!' do
|
||||||
|
it 'should be able to fetch timeout from conf' do
|
||||||
|
expect(Gitlab::Satellite::Action::DEFAULT_OPTIONS[:git_timeout]).to eq(30.seconds)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'create a repository with a parking branch and one remote: origin' do
|
||||||
|
repo = project.satellite.repo
|
||||||
|
|
||||||
|
#now lets dirty it up
|
||||||
|
|
||||||
|
starting_remote_count = repo.git.list_remotes.size
|
||||||
|
expect(starting_remote_count).to be >= 1
|
||||||
|
#kind of hookey way to add a second remote
|
||||||
|
origin_uri = repo.git.remote({ v: true }).split(" ")[1]
|
||||||
|
|
||||||
|
repo.git.remote({ raise: true }, 'add', 'another-remote', origin_uri)
|
||||||
|
repo.git.branch({ raise: true }, 'a-new-branch')
|
||||||
|
|
||||||
|
expect(repo.heads.size).to be > (starting_remote_count)
|
||||||
|
expect(repo.git.remote().split(" ").size).to be > (starting_remote_count)
|
||||||
|
|
||||||
|
repo.git.config({}, "user.name", "#{user.name} -- foo")
|
||||||
|
repo.git.config({}, "user.email", "#{user.email} -- foo")
|
||||||
|
expect(repo.config['user.name']).to eq("#{user.name} -- foo")
|
||||||
|
expect(repo.config['user.email']).to eq("#{user.email} -- foo")
|
||||||
|
|
||||||
|
|
||||||
|
#These must happen in the context of the satellite directory...
|
||||||
|
satellite_action = Gitlab::Satellite::Action.new(user, project)
|
||||||
|
project.satellite.lock do
|
||||||
|
#Now clean it up, use send to get around prepare_satellite! being protected
|
||||||
|
satellite_action.send(:prepare_satellite!, repo)
|
||||||
|
end
|
||||||
|
|
||||||
|
#verify it's clean
|
||||||
|
heads = repo.heads.map(&:name)
|
||||||
|
expect(heads.size).to eq(1)
|
||||||
|
expect(heads.include?(Gitlab::Satellite::Satellite::PARKING_BRANCH)).to eq(true)
|
||||||
|
remotes = repo.git.remote().split(' ')
|
||||||
|
expect(remotes.size).to eq(1)
|
||||||
|
expect(remotes.include?('origin')).to eq(true)
|
||||||
|
expect(repo.config['user.name']).to eq(user.name)
|
||||||
|
expect(repo.config['user.email']).to eq(user.email)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#in_locked_and_timed_satellite' do
|
||||||
|
|
||||||
|
it 'should make use of a lockfile' do
|
||||||
|
repo = project.satellite.repo
|
||||||
|
called = false
|
||||||
|
|
||||||
|
#set assumptions
|
||||||
|
FileUtils.rm_f(project.satellite.lock_file)
|
||||||
|
|
||||||
|
expect(File.exists?(project.satellite.lock_file)).to be_falsey
|
||||||
|
|
||||||
|
satellite_action = Gitlab::Satellite::Action.new(user, project)
|
||||||
|
satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
|
||||||
|
expect(repo).to eq(sat_repo)
|
||||||
|
expect(File.exists? project.satellite.lock_file).to be_truthy
|
||||||
|
called = true
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(called).to be_truthy
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should be able to use the satellite after locking' do
|
||||||
|
repo = project.satellite.repo
|
||||||
|
called = false
|
||||||
|
|
||||||
|
# Set base assumptions
|
||||||
|
if File.exists? project.satellite.lock_file
|
||||||
|
expect(FileLockStatusChecker.new(project.satellite.lock_file).flocked?).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
satellite_action = Gitlab::Satellite::Action.new(user, project)
|
||||||
|
satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
|
||||||
|
called = true
|
||||||
|
expect(repo).to eq(sat_repo)
|
||||||
|
expect(File.exists? project.satellite.lock_file).to be_truthy
|
||||||
|
expect(FileLockStatusChecker.new(project.satellite.lock_file).flocked?).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(called).to be_truthy
|
||||||
|
expect(FileLockStatusChecker.new(project.satellite.lock_file).flocked?).to be_falsey
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
class FileLockStatusChecker < File
|
||||||
|
def flocked?(&block)
|
||||||
|
status = flock LOCK_EX|LOCK_NB
|
||||||
|
case status
|
||||||
|
when false
|
||||||
|
return true
|
||||||
|
when 0
|
||||||
|
begin
|
||||||
|
block ? block.call : false
|
||||||
|
ensure
|
||||||
|
flock LOCK_UN
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise SystemCallError, status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,104 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe 'Gitlab::Satellite::MergeAction' do
|
||||||
|
include RepoHelpers
|
||||||
|
|
||||||
|
let(:project) { create(:project, namespace: create(:group)) }
|
||||||
|
let(:fork_project) { create(:project, namespace: create(:group), forked_from_project: project) }
|
||||||
|
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
|
||||||
|
let(:merge_request_fork) { create(:merge_request, source_project: fork_project, target_project: project) }
|
||||||
|
|
||||||
|
let(:merge_request_with_conflict) { create(:merge_request, :conflict, source_project: project, target_project: project) }
|
||||||
|
let(:merge_request_fork_with_conflict) { create(:merge_request, :conflict, source_project: project, target_project: project) }
|
||||||
|
|
||||||
|
describe '#commits_between' do
|
||||||
|
def verify_commits(commits, first_commit_sha, last_commit_sha)
|
||||||
|
commits.each { |commit| expect(commit.class).to eq(Gitlab::Git::Commit) }
|
||||||
|
expect(commits.first.id).to eq(first_commit_sha)
|
||||||
|
expect(commits.last.id).to eq(last_commit_sha)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on fork' do
|
||||||
|
it 'should get proper commits between' do
|
||||||
|
commits = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).commits_between
|
||||||
|
verify_commits(commits, sample_compare.commits.first, sample_compare.commits.last)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'between branches' do
|
||||||
|
it 'should raise exception -- not expected to be used by non forks' do
|
||||||
|
expect { Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#format_patch' do
|
||||||
|
def verify_content(patch)
|
||||||
|
sample_compare.commits.each do |commit|
|
||||||
|
expect(patch.include?(commit)).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on fork' do
|
||||||
|
it 'should build a format patch' do
|
||||||
|
patch = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).format_patch
|
||||||
|
verify_content(patch)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'between branches' do
|
||||||
|
it 'should build a format patch' do
|
||||||
|
patch = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request).format_patch
|
||||||
|
verify_content(patch)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#diffs_between_satellite tested against diff_in_satellite' do
|
||||||
|
def is_a_matching_diff(diff, diffs)
|
||||||
|
diff_count = diff.scan('diff --git').size
|
||||||
|
expect(diff_count).to be >= 1
|
||||||
|
expect(diffs.size).to eq(diff_count)
|
||||||
|
diffs.each do |a_diff|
|
||||||
|
expect(a_diff.class).to eq(Gitlab::Git::Diff)
|
||||||
|
expect(diff.include? a_diff.diff).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on fork' do
|
||||||
|
it 'should get proper diffs' do
|
||||||
|
diffs = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).diffs_between_satellite
|
||||||
|
diff = Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request_fork).diff_in_satellite
|
||||||
|
is_a_matching_diff(diff, diffs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'between branches' do
|
||||||
|
it 'should get proper diffs' do
|
||||||
|
expect{ Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).diffs_between_satellite }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#can_be_merged?' do
|
||||||
|
context 'on fork' do
|
||||||
|
it do
|
||||||
|
expect(Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).can_be_merged?).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(Gitlab::Satellite::MergeAction.new(merge_request_fork_with_conflict.author, merge_request_fork_with_conflict).can_be_merged?).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'between branches' do
|
||||||
|
it do
|
||||||
|
expect(Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).can_be_merged?).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(Gitlab::Satellite::MergeAction.new(merge_request_with_conflict.author, merge_request_with_conflict).can_be_merged?).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -165,7 +165,7 @@ describe MergeRequest do
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'an editable mentionable' do
|
it_behaves_like 'an editable mentionable' do
|
||||||
subject { create(:merge_request) }
|
subject { create(:merge_request, source_project: project) }
|
||||||
|
|
||||||
let(:backref_text) { "merge request #{subject.to_reference}" }
|
let(:backref_text) { "merge request #{subject.to_reference}" }
|
||||||
let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
|
let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
|
||||||
|
|
|
@ -67,7 +67,7 @@ describe SlackService do
|
||||||
opts = {
|
opts = {
|
||||||
title: 'Awesome merge_request',
|
title: 'Awesome merge_request',
|
||||||
description: 'please fix',
|
description: 'please fix',
|
||||||
source_branch: 'feature',
|
source_branch: 'stable',
|
||||||
target_branch: 'master'
|
target_branch: 'master'
|
||||||
}
|
}
|
||||||
merge_service = MergeRequests::CreateService.new(project,
|
merge_service = MergeRequests::CreateService.new(project,
|
||||||
|
|
|
@ -91,6 +91,7 @@ describe Project do
|
||||||
describe 'Respond to' do
|
describe 'Respond to' do
|
||||||
it { is_expected.to respond_to(:url_to_repo) }
|
it { is_expected.to respond_to(:url_to_repo) }
|
||||||
it { is_expected.to respond_to(:repo_exists?) }
|
it { is_expected.to respond_to(:repo_exists?) }
|
||||||
|
it { is_expected.to respond_to(:satellite) }
|
||||||
it { is_expected.to respond_to(:update_merge_requests) }
|
it { is_expected.to respond_to(:update_merge_requests) }
|
||||||
it { is_expected.to respond_to(:execute_hooks) }
|
it { is_expected.to respond_to(:execute_hooks) }
|
||||||
it { is_expected.to respond_to(:name_with_namespace) }
|
it { is_expected.to respond_to(:name_with_namespace) }
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue