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.
|
||||
|
||||
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)
|
||||
- Show incompatible projects in Bitbucket import status (Stan Hu)
|
||||
- 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
|
||||
- Add support for CI skipped status
|
||||
- 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)
|
||||
- Improve MR merge widget text and UI consistency.
|
||||
- Improve text in MR "How To Merge" modal.
|
||||
- Cache all events
|
||||
- Order commits by date when comparing branches
|
||||
- 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
|
||||
- Allow to configure a URL to show after sign out
|
||||
- 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
|
||||
- When remove project - move repository and schedule it removal
|
||||
- Improve group removing logic
|
||||
|
|
|
@ -875,4 +875,4 @@ DEPENDENCIES
|
|||
wikicloth (= 0.8.1)
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.4
|
||||
1.10.5
|
||||
|
|
|
@ -19,7 +19,7 @@ class @MergeRequestWidget
|
|||
when 'merged'
|
||||
location.reload()
|
||||
else
|
||||
setTimeout(merge_request_widget.mergeInProgress, 2000)
|
||||
setTimeout(merge_request_widget.mergeInProgress, 3000)
|
||||
dataType: 'json'
|
||||
|
||||
getMergeStatus: ->
|
||||
|
@ -49,8 +49,10 @@ class @MergeRequestWidget
|
|||
@setMergeButtonClass('btn-danger')
|
||||
|
||||
showCiCoverage: (coverage) ->
|
||||
text = 'Coverage ' + coverage + '%'
|
||||
$('.ci_widget:visible .ci-coverage').text(text)
|
||||
cov_html = $('<span>')
|
||||
cov_html.addClass('ci-coverage')
|
||||
cov_html.text('Coverage ' + coverage + '%')
|
||||
$('.ci_widget:visible').append(cov_html)
|
||||
|
||||
setMergeButtonClass: (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 {
|
||||
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 {
|
||||
margin-bottom: 0;
|
||||
.clearfix {
|
||||
|
@ -26,67 +20,16 @@
|
|||
display: inline-block;
|
||||
margin: 0;
|
||||
margin-left: 20px;
|
||||
padding: 5px;
|
||||
padding: 10px 0;
|
||||
line-height: 20px;
|
||||
font-weight: bold;
|
||||
|
||||
.remove_source_checkbox {
|
||||
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) {
|
||||
|
@ -118,10 +61,23 @@
|
|||
}
|
||||
|
||||
.label-branch {
|
||||
color: #222;
|
||||
@include border-radius(4px);
|
||||
padding: 3px 4px;
|
||||
border: none;
|
||||
background: $hover;
|
||||
color: #333;
|
||||
font-family: $monospace_font;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
overflow: hidden;
|
||||
|
||||
.label-project {
|
||||
@include border-radius-left(4px);
|
||||
padding: 3px 4px;
|
||||
background: #279;
|
||||
position: relative;
|
||||
left: -4px;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.mr-list {
|
||||
|
@ -168,6 +124,64 @@
|
|||
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 {
|
||||
a {
|
||||
margin-right: 5px;
|
||||
|
@ -182,7 +196,3 @@
|
|||
.merge-request-form .select2-container {
|
||||
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 :blob, except: [:new, :create]
|
||||
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 :require_branch_head, only: [:edit, :update]
|
||||
|
||||
def new
|
||||
commit unless @repository.empty?
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
flash[:alert] = result[:message]
|
||||
render :new
|
||||
|
@ -41,10 +48,22 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
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
|
||||
flash[:notice] = "Your changes have been successfully committed"
|
||||
|
||||
if from_merge_request
|
||||
from_merge_request.reload_code
|
||||
end
|
||||
|
||||
redirect_to after_edit_path
|
||||
else
|
||||
flash[:alert] = result[:message]
|
||||
|
@ -61,11 +80,12 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
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
|
||||
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
|
||||
flash[:alert] = result[:message]
|
||||
render :show
|
||||
|
@ -115,6 +135,7 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
@id = params[:id]
|
||||
@ref, @path = extract_ref(@id)
|
||||
|
||||
|
||||
rescue InvalidPathError
|
||||
not_found!
|
||||
end
|
||||
|
@ -124,8 +145,8 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
if 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)}"
|
||||
elsif @target_branch.present?
|
||||
namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
|
||||
elsif sanitized_new_branch_name.present?
|
||||
namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path))
|
||||
else
|
||||
namespace_project_blob_path(@project.namespace, @project, @id)
|
||||
end
|
||||
|
@ -139,25 +160,4 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
def sanitized_new_branch_name
|
||||
@new_branch ||= sanitize(strip_tags(params[:new_branch]))
|
||||
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
|
||||
|
|
|
@ -13,8 +13,13 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
base_ref = Addressable::URI.unescape(params[:from])
|
||||
@ref = head_ref = Addressable::URI.unescape(params[:to])
|
||||
|
||||
compare_result = CompareService.new.
|
||||
execute(@project, head_ref, @project, base_ref)
|
||||
compare_result = CompareService.new.execute(
|
||||
current_user,
|
||||
@project,
|
||||
head_ref,
|
||||
@project,
|
||||
base_ref
|
||||
)
|
||||
|
||||
@commits = compare_result.commits
|
||||
@diffs = compare_result.diffs
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
require 'gitlab/satellite/satellite'
|
||||
|
||||
class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
before_action :module_enabled
|
||||
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
|
||||
]
|
||||
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits]
|
||||
|
@ -135,7 +137,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def merge_check
|
||||
def automerge_check
|
||||
if @merge_request.unchecked?
|
||||
@merge_request.check_if_can_be_merged
|
||||
end
|
||||
|
@ -145,11 +147,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
render partial: "projects/merge_requests/widget/show.html.haml", layout: false
|
||||
end
|
||||
|
||||
def merge
|
||||
def automerge
|
||||
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
|
||||
|
||||
if @merge_request.mergeable?
|
||||
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
|
||||
if @merge_request.automergeable?
|
||||
AutoMergeWorker.perform_async(@merge_request.id, current_user.id, params)
|
||||
@status = true
|
||||
else
|
||||
@status = false
|
||||
|
|
|
@ -61,14 +61,4 @@ module MergeRequestsHelper
|
|||
}
|
||||
)
|
||||
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
|
||||
|
|
|
@ -231,37 +231,20 @@ module ProjectsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def readme_path(project)
|
||||
filename_path(project, :readme)
|
||||
end
|
||||
|
||||
def changelog_path(project)
|
||||
if project && changelog = project.repository.changelog
|
||||
namespace_project_blob_path(
|
||||
project.namespace,
|
||||
project,
|
||||
tree_join(project.default_branch,
|
||||
changelog.name)
|
||||
)
|
||||
end
|
||||
filename_path(project, :changelog)
|
||||
end
|
||||
|
||||
def license_path(project)
|
||||
if project && license = project.repository.license
|
||||
namespace_project_blob_path(
|
||||
project.namespace,
|
||||
project,
|
||||
tree_join(project.default_branch,
|
||||
license.name)
|
||||
)
|
||||
end
|
||||
filename_path(project, :license)
|
||||
end
|
||||
|
||||
def version_path(project)
|
||||
if project && version = project.repository.version
|
||||
namespace_project_blob_path(
|
||||
project.namespace,
|
||||
project,
|
||||
tree_join(project.default_branch,
|
||||
version.name)
|
||||
)
|
||||
end
|
||||
filename_path(project, :version)
|
||||
end
|
||||
|
||||
def hidden_pass_url(original_url)
|
||||
|
@ -331,4 +314,17 @@ module ProjectsHelper
|
|||
count
|
||||
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
|
||||
|
|
|
@ -41,6 +41,8 @@ class MergeRequest < ActiveRecord::Base
|
|||
|
||||
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
|
||||
# It allows us to close or modify broken merge requests
|
||||
attr_accessor :allow_broken
|
||||
|
@ -55,7 +57,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
transition [:reopened, :opened] => :closed
|
||||
end
|
||||
|
||||
event :mark_as_merged do
|
||||
event :merge do
|
||||
transition [:reopened, :opened, :locked] => :merged
|
||||
end
|
||||
|
||||
|
@ -203,10 +205,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def check_if_can_be_merged
|
||||
can_be_merged =
|
||||
project.repository.can_be_merged?(source_sha, target_branch)
|
||||
|
||||
if can_be_merged
|
||||
if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
|
||||
mark_as_mergeable
|
||||
else
|
||||
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
|
||||
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?
|
||||
opened? || reopened?
|
||||
end
|
||||
|
@ -229,11 +240,11 @@ class MergeRequest < ActiveRecord::Base
|
|||
title =~ /\A\[?WIP\]?:? /i
|
||||
end
|
||||
|
||||
def mergeable?
|
||||
def automergeable?
|
||||
open? && !work_in_progress? && can_be_merged?
|
||||
end
|
||||
|
||||
def gitlab_merge_status
|
||||
def automerge_status
|
||||
if work_in_progress?
|
||||
"work_in_progress"
|
||||
else
|
||||
|
@ -260,14 +271,14 @@ class MergeRequest < ActiveRecord::Base
|
|||
#
|
||||
# see "git diff"
|
||||
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
|
||||
|
||||
# Returns the commit as a series of email patches.
|
||||
#
|
||||
# see "git format-patch"
|
||||
def to_patch(current_user)
|
||||
target_project.repository.format_patch(target_branch, source_sha)
|
||||
Gitlab::Satellite::MergeAction.new(current_user, self).format_patch
|
||||
end
|
||||
|
||||
def hook_attrs
|
||||
|
@ -418,30 +429,4 @@ class MergeRequest < ActiveRecord::Base
|
|||
"Open"
|
||||
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
|
||||
|
|
|
@ -16,8 +16,9 @@ require Rails.root.join("app/models/commit")
|
|||
class MergeRequestDiff < ActiveRecord::Base
|
||||
include Sortable
|
||||
|
||||
# Prevent store of diff if commits amount more then 500
|
||||
COMMITS_SAFE_SIZE = 500
|
||||
# Prevent store of diff
|
||||
# if commits amount more then 200
|
||||
COMMITS_SAFE_SIZE = 200
|
||||
|
||||
attr_reader :commits, :diffs
|
||||
|
||||
|
@ -123,12 +124,12 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
if new_diffs.any?
|
||||
if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES
|
||||
self.state = :overflow_diff_files_limit
|
||||
new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES]
|
||||
new_diffs = []
|
||||
end
|
||||
|
||||
if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES
|
||||
self.state = :overflow_diff_lines_limit
|
||||
new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES]
|
||||
new_diffs = []
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -159,21 +160,12 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
private
|
||||
|
||||
def compare_result
|
||||
@compare_result ||=
|
||||
begin
|
||||
# Update ref for merge request
|
||||
merge_request.fetch_ref
|
||||
|
||||
# Get latest sha of branch from source project
|
||||
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
|
||||
@compare_result ||= CompareService.new.execute(
|
||||
merge_request.author,
|
||||
merge_request.source_project,
|
||||
merge_request.source_branch,
|
||||
merge_request.target_project,
|
||||
merge_request.target_branch,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -118,11 +118,12 @@ class Namespace < ActiveRecord::Base
|
|||
gitlab_shell.add_namespace(path_was)
|
||||
|
||||
if gitlab_shell.mv_namespace(path_was, path)
|
||||
# If repositories moved successfully we need to
|
||||
# send update instructions to users.
|
||||
# If repositories moved successfully we need to remove old satellites
|
||||
# and send update instructions to users.
|
||||
# However we cannot allow rollback since we moved namespace dir
|
||||
# So we basically we mute exceptions in next actions
|
||||
begin
|
||||
gitlab_shell.rm_satellites(path_was)
|
||||
send_update_instructions
|
||||
rescue
|
||||
# Returning false does not rollback after_* transaction but gives
|
||||
|
|
|
@ -520,6 +520,14 @@ class Project < ActiveRecord::Base
|
|||
!repository.exists? || repository.empty?
|
||||
end
|
||||
|
||||
def ensure_satellite_exists
|
||||
self.satellite.create unless self.satellite.exists?
|
||||
end
|
||||
|
||||
def satellite
|
||||
@satellite ||= Gitlab::Satellite::Satellite.new(self)
|
||||
end
|
||||
|
||||
def repo
|
||||
repository.raw
|
||||
end
|
||||
|
@ -589,11 +597,14 @@ class Project < ActiveRecord::Base
|
|||
new_path_with_namespace = File.join(namespace_dir, path)
|
||||
|
||||
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
|
||||
# So we basically we mute exceptions in next actions
|
||||
begin
|
||||
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
|
||||
reset_events_cache
|
||||
rescue
|
||||
|
@ -691,6 +702,7 @@ class Project < ActiveRecord::Base
|
|||
def create_repository
|
||||
if forked?
|
||||
if gitlab_shell.fork_repository(forked_from_project.path_with_namespace, self.namespace.path)
|
||||
ensure_satellite_exists
|
||||
true
|
||||
else
|
||||
errors.add(:base, 'Failed to fork repository via gitlab-shell')
|
||||
|
|
|
@ -74,8 +74,6 @@ class GitlabCiService < CiService
|
|||
else
|
||||
:error
|
||||
end
|
||||
rescue Errno::ECONNREFUSED
|
||||
:error
|
||||
end
|
||||
|
||||
def fork_registration(new_project, private_token)
|
||||
|
@ -105,8 +103,6 @@ class GitlabCiService < CiService
|
|||
if response.code == 200 and response["coverage"]
|
||||
response["coverage"]
|
||||
end
|
||||
rescue Errno::ECONNREFUSED
|
||||
nil
|
||||
end
|
||||
|
||||
def build_page(sha, ref)
|
||||
|
|
|
@ -364,83 +364,6 @@ class Repository
|
|||
@root_ref ||= raw_repository.root_ref
|
||||
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)
|
||||
offset = 2
|
||||
args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
|
||||
|
@ -474,11 +397,6 @@ class Repository
|
|||
)
|
||||
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
|
||||
|
||||
def cache
|
||||
|
|
|
@ -31,10 +31,6 @@ class BaseService
|
|||
SystemHooksService.new
|
||||
end
|
||||
|
||||
def repository
|
||||
project.repository
|
||||
end
|
||||
|
||||
# Add an error to the specified model for restricted visibility levels
|
||||
def deny_visibility_level(model, denied_visibility_level = nil)
|
||||
denied_visibility_level ||= model.visibility_level
|
||||
|
|
|
@ -1,28 +1,27 @@
|
|||
require 'securerandom'
|
||||
|
||||
# Compare 2 branches for one repo or between repositories
|
||||
# and return Gitlab::CompareResult object that responds to commits and diffs
|
||||
class CompareService
|
||||
def execute(source_project, source_branch, target_project, target_branch)
|
||||
source_sha = source_project.commit(source_branch).sha
|
||||
|
||||
# If compare with other project we need to fetch ref first
|
||||
unless target_project == source_project
|
||||
random_string = SecureRandom.hex
|
||||
|
||||
target_project.repository.fetch_ref(
|
||||
source_project.repository.path_to_repo,
|
||||
"refs/heads/#{source_branch}",
|
||||
"refs/tmp/#{random_string}/head"
|
||||
def execute(current_user, source_project, source_branch, target_project, target_branch)
|
||||
# Try to compare branches to get commits list and diffs
|
||||
#
|
||||
# Note: Use satellite only when need to compare between two repos
|
||||
# because satellites are slower than operations on bare repo
|
||||
if target_project == source_project
|
||||
Gitlab::CompareResult.new(
|
||||
Gitlab::Git::Compare.new(
|
||||
target_project.repository.raw_repository,
|
||||
target_branch,
|
||||
source_branch,
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
Gitlab::CompareResult.new(
|
||||
Gitlab::Git::Compare.new(
|
||||
target_project.repository.raw_repository,
|
||||
else
|
||||
Gitlab::Satellite::CompareAction.new(
|
||||
current_user,
|
||||
target_project,
|
||||
target_branch,
|
||||
source_sha,
|
||||
)
|
||||
)
|
||||
source_project,
|
||||
source_branch
|
||||
).result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,80 +1,17 @@
|
|||
module Files
|
||||
class BaseService < ::BaseService
|
||||
class ValidationError < StandardError; end
|
||||
attr_reader :ref, :path
|
||||
|
||||
def execute
|
||||
@current_branch = params[:current_branch]
|
||||
@target_branch = params[:target_branch]
|
||||
@commit_message = params[:commit_message]
|
||||
@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)
|
||||
def initialize(project, user, params, ref, path = nil)
|
||||
@project, @current_user, @params = project, user, params.dup
|
||||
@ref = ref
|
||||
@path = path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def after_commit(sha, branch)
|
||||
PostCommitService.new(project, current_user).execute(sha, branch)
|
||||
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
|
||||
def repository
|
||||
project.repository
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,30 +1,52 @@
|
|||
require_relative "base_service"
|
||||
|
||||
module Files
|
||||
class CreateService < Files::BaseService
|
||||
def commit
|
||||
repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
|
||||
end
|
||||
class CreateService < BaseService
|
||||
def execute
|
||||
allowed = Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
|
||||
|
||||
def validate
|
||||
super
|
||||
unless allowed
|
||||
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
|
||||
raise_error(
|
||||
return error(
|
||||
'Your changes could not be committed, because the file name ' +
|
||||
Gitlab::Regex.file_name_regex_message
|
||||
)
|
||||
end
|
||||
|
||||
unless project.empty_repo?
|
||||
blob = repository.blob_at_branch(@current_branch, @file_path)
|
||||
if project.empty_repo?
|
||||
# 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
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
|
|
@ -1,9 +1,36 @@
|
|||
require_relative "base_service"
|
||||
|
||||
module Files
|
||||
class DeleteService < Files::BaseService
|
||||
def commit
|
||||
repository.remove_file(current_user, @file_path, @commit_message, @target_branch)
|
||||
class DeleteService < BaseService
|
||||
def execute
|
||||
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
|
||||
|
|
|
@ -1,9 +1,39 @@
|
|||
require_relative "base_service"
|
||||
|
||||
module Files
|
||||
class UpdateService < Files::BaseService
|
||||
def commit
|
||||
repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
|
||||
class UpdateService < BaseService
|
||||
def execute
|
||||
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
|
||||
|
|
|
@ -10,14 +10,16 @@ class GitPushService
|
|||
#
|
||||
# Next, this method:
|
||||
# 1. Creates the push event
|
||||
# 2. Updates merge requests
|
||||
# 3. Recognizes cross-references from commit messages
|
||||
# 4. Executes the project's web hooks
|
||||
# 5. Executes the project's services
|
||||
# 2. Ensures that the project satellite exists
|
||||
# 3. Updates merge requests
|
||||
# 4. Recognizes cross-references from commit messages
|
||||
# 5. Executes the project's web hooks
|
||||
# 6. Executes the project's services
|
||||
#
|
||||
def execute(project, user, oldrev, newrev, ref)
|
||||
@project, @user = project, user
|
||||
|
||||
project.ensure_satellite_exists
|
||||
project.repository.expire_cache
|
||||
|
||||
if push_remove_branch?(ref, newrev)
|
||||
|
@ -131,8 +133,7 @@ class GitPushService
|
|||
end
|
||||
|
||||
def is_default_branch?(ref)
|
||||
Gitlab::Git.branch_ref?(ref) &&
|
||||
(Gitlab::Git.ref_name(ref) == project.default_branch || project.default_branch.nil?)
|
||||
Gitlab::Git.branch_ref?(ref) && Gitlab::Git.ref_name(ref) == project.default_branch
|
||||
end
|
||||
|
||||
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_branch ||= merge_request.target_project.default_branch
|
||||
|
||||
if merge_request.target_branch.blank? || merge_request.source_branch.blank?
|
||||
message =
|
||||
if params[:source_branch] || params[:target_branch]
|
||||
"You must select source and target branch"
|
||||
end
|
||||
|
||||
return build_failed(merge_request, message)
|
||||
unless merge_request.target_branch && merge_request.source_branch
|
||||
return build_failed(merge_request, nil)
|
||||
end
|
||||
|
||||
compare_result = CompareService.new.execute(
|
||||
current_user,
|
||||
merge_request.source_project,
|
||||
merge_request.source_branch,
|
||||
merge_request.target_project,
|
||||
|
@ -44,6 +40,7 @@ module MergeRequests
|
|||
merge_request.compare_diffs = diffs
|
||||
|
||||
elsif diffs == false
|
||||
# satellite timeout return false
|
||||
merge_request.can_be_created = false
|
||||
merge_request.compare_failed = true
|
||||
end
|
||||
|
@ -62,6 +59,9 @@ module MergeRequests
|
|||
end
|
||||
|
||||
merge_request
|
||||
|
||||
rescue Gitlab::Satellite::BranchesWithoutParent
|
||||
return build_failed(merge_request, "Selected branches have no common commit so they cannot be merged.")
|
||||
end
|
||||
|
||||
def build_failed(merge_request, message)
|
||||
|
|
|
@ -1,57 +1,22 @@
|
|||
module MergeRequests
|
||||
# MergeService class
|
||||
#
|
||||
# Do git merge and in case of success
|
||||
# mark merge request as merged and execute all hooks and notifications
|
||||
# Executed when you do merge via GitLab UI
|
||||
#
|
||||
class MergeService < MergeRequests::BaseService
|
||||
attr_reader :merge_request, :commit_message
|
||||
|
||||
# Mark existing merge request as merged
|
||||
# and execute all hooks and notifications
|
||||
# Called when you do merge via command line and push code
|
||||
# to target branch
|
||||
class MergeService < BaseMergeService
|
||||
def execute(merge_request, commit_message)
|
||||
@commit_message = commit_message
|
||||
@merge_request = merge_request
|
||||
merge_request.merge
|
||||
|
||||
unless @merge_request.mergeable?
|
||||
return error('Merge request is not mergeable')
|
||||
end
|
||||
create_merge_event(merge_request, current_user)
|
||||
create_note(merge_request)
|
||||
notification_service.merge_mr(merge_request, current_user)
|
||||
execute_hooks(merge_request, 'merge')
|
||||
|
||||
merge_request.in_locked_state do
|
||||
if merge_changes
|
||||
after_merge
|
||||
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)
|
||||
true
|
||||
rescue
|
||||
false
|
||||
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|
|
||||
MergeRequests::PostMergeService.
|
||||
MergeRequests::MergeService.
|
||||
new(merge_request.target_project, @current_user).
|
||||
execute(merge_request)
|
||||
execute(merge_request, nil)
|
||||
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
|
||||
|
||||
project.satellite.destroy
|
||||
log_info("Project \"#{project.name}\" was removed")
|
||||
system_hook_service.execute_hooks_for(project, :destroy)
|
||||
true
|
||||
|
|
|
@ -33,6 +33,9 @@ module Projects
|
|||
raise TransferError.new("Project with same path in target namespace already exists")
|
||||
end
|
||||
|
||||
# Remove old satellite
|
||||
project.satellite.destroy
|
||||
|
||||
# Apply new namespace id
|
||||
project.namespace = new_namespace
|
||||
project.save!
|
||||
|
@ -48,6 +51,9 @@ module Projects
|
|||
# Move wiki repo also if present
|
||||
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
|
||||
project.reset_events_cache
|
||||
|
||||
|
|
|
@ -14,12 +14,16 @@
|
|||
:plain
|
||||
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>")
|
||||
- else
|
||||
- elsif @project.persisted?
|
||||
:plain
|
||||
job = $("tr#repo_#{@repo_id}")
|
||||
job.attr("id", "project_#{@project.id}")
|
||||
target_field = job.find(".import-target")
|
||||
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)
|
||||
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
|
||||
= link_to "them to Git,", "https://www.atlassian.com/git/tutorials/migrating-overview"
|
||||
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.
|
||||
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
\/
|
||||
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
|
||||
required: true, class: 'form-control new-file-name'
|
||||
.pull-right
|
||||
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
|
||||
.pull-right
|
||||
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
|
||||
|
||||
.file-content.code
|
||||
%pre.js-edit-mode-pane#editor
|
||||
|
|
|
@ -6,12 +6,11 @@
|
|||
= render 'shared/commit_message_container', params: params,
|
||||
placeholder: 'Add new file'
|
||||
|
||||
- unless @project.empty_repo?
|
||||
.form-group.branch
|
||||
= label_tag 'branch', class: 'control-label' do
|
||||
Branch
|
||||
.col-sm-10
|
||||
= text_field_tag 'new_branch', @ref, class: "form-control"
|
||||
.form-group.branch
|
||||
= label_tag 'branch', class: 'control-label' do
|
||||
Branch
|
||||
.col-sm-10
|
||||
= text_field_tag 'new_branch', @ref, class: "form-control"
|
||||
|
||||
= hidden_field_tag 'content', '', id: 'file-content'
|
||||
= render 'projects/commit_button', ref: @ref,
|
||||
|
|
|
@ -43,6 +43,8 @@
|
|||
cd existing_folder
|
||||
git init
|
||||
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
|
||||
git add .
|
||||
git commit
|
||||
git push -u origin master
|
||||
|
||||
- if can? current_user, :remove_project, @project
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
- if @merge_request.compare_failed
|
||||
.alert.alert-danger
|
||||
%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
|
||||
.light-well
|
||||
.center
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
= icon('history')
|
||||
Commits
|
||||
%span.badge= @commits.size
|
||||
%li.diffs-tab.active
|
||||
%li.diffs-tab
|
||||
= link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
|
||||
= icon('list-alt')
|
||||
Changes
|
||||
|
@ -33,7 +33,7 @@
|
|||
.tab-content
|
||||
#commits.commits.tab-pane
|
||||
= render "projects/commits/commits", project: @project
|
||||
#diffs.diffs.tab-pane.active
|
||||
#diffs.diffs.tab-pane
|
||||
- if @diffs.present?
|
||||
= render "projects/diffs/diffs", diffs: @diffs, project: @project
|
||||
- elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
|
||||
|
|
|
@ -6,25 +6,40 @@
|
|||
= render "projects/merge_requests/show/mr_box"
|
||||
%hr
|
||||
.append-bottom-20
|
||||
- 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)
|
||||
.light
|
||||
%div
|
||||
%span From
|
||||
%span.label-branch #{source_branch_with_namespace(@merge_request)}
|
||||
.slead
|
||||
%span From
|
||||
- if @merge_request.for_fork?
|
||||
%strong.label-branch<
|
||||
- if @merge_request.source_project
|
||||
= link_to @merge_request.source_project_namespace, namespace_project_path(@merge_request.source_project.namespace, @merge_request.source_project)
|
||||
- else
|
||||
\ #{@merge_request.source_project_namespace}
|
||||
\:#{@merge_request.source_branch}
|
||||
%span into
|
||||
%span.label-branch #{@merge_request.target_branch}
|
||||
- if @merge_request.open? && !@merge_request.branch_missing?
|
||||
%div
|
||||
If you want to try or merge this request manually, you can use the
|
||||
= 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_project_namespace}:#{@merge_request.target_branch}
|
||||
- else
|
||||
%strong.label-branch #{@merge_request.source_branch}
|
||||
%span into
|
||||
%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/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?
|
||||
= 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?
|
||||
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
|
||||
- else
|
||||
|
|
|
@ -3,45 +3,42 @@
|
|||
.modal-content
|
||||
.modal-header
|
||||
%a.close{href: "#", "data-dismiss" => "modal"} ×
|
||||
%h3 Check out, review and merge locally
|
||||
%h3 How to merge
|
||||
.modal-body
|
||||
%p
|
||||
%strong Step 1.
|
||||
Fetch and check out the branch for this merge request
|
||||
%pre.dark
|
||||
- if @merge_request.for_fork?
|
||||
- if @merge_request.for_fork?
|
||||
- source_remote = @merge_request.source_project.namespace.nil? ? "source" :@merge_request.source_project.namespace.path
|
||||
- target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path
|
||||
%p
|
||||
%strong Step 1.
|
||||
Fetch the code and create a new branch pointing to it
|
||||
%pre.dark
|
||||
:preserve
|
||||
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
|
||||
- else
|
||||
:preserve
|
||||
git fetch origin
|
||||
git checkout -b #{@merge_request.source_branch} origin/#{@merge_request.source_branch}
|
||||
%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?
|
||||
%p
|
||||
%strong Step 2.
|
||||
Merge the branch and push the changes to GitLab
|
||||
%pre.dark
|
||||
:preserve
|
||||
git checkout #{@merge_request.target_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
|
||||
git checkout #{@merge_request.target_branch}
|
||||
git merge --no-ff #{@merge_request.source_branch}
|
||||
%p
|
||||
%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.
|
||||
git push origin #{@merge_request.target_branch}
|
||||
|
||||
:javascript
|
||||
$(function(){
|
||||
|
|
|
@ -6,7 +6,4 @@
|
|||
- if @merge_request.closed_event
|
||||
by #{link_to_member(@project, @merge_request.closed_event.author, avatar: true)}
|
||||
#{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
|
||||
%p
|
||||
= succeed '.' do
|
||||
The changes were not merged into
|
||||
%span.label-branch= @merge_request.target_branch
|
||||
%p Changes were not merged into target branch
|
||||
|
|
|
@ -1,28 +1,49 @@
|
|||
- if @merge_request.has_ci?
|
||||
.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"}
|
||||
- if status == :success
|
||||
- status = "passed"
|
||||
= icon("check-circle")
|
||||
- else
|
||||
= icon("circle")
|
||||
= icon("clock-o")
|
||||
%span CI build #{status}
|
||||
for #{@merge_request.last_commit_short_sha}.
|
||||
%span.ci-coverage
|
||||
= link_to "View build details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
|
||||
= link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
|
||||
|
||||
.ci_widget
|
||||
= 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"}
|
||||
= icon("times-circle")
|
||||
Could not find CI status for #{@merge_request.last_commit_short_sha}.
|
||||
= icon("times")
|
||||
%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"}
|
||||
= icon("times-circle")
|
||||
Could not connect to the CI server. Please check your settings and try again.
|
||||
= icon("times")
|
||||
%span Cannot connect to the CI server. Please check your settings and try again.
|
||||
|
||||
:coffeescript
|
||||
$ ->
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
= render 'projects/merge_requests/widget/heading'
|
||||
.mr-widget-body
|
||||
%h4
|
||||
= icon("spinner spin")
|
||||
Merge in progress…
|
||||
Merge in progress...
|
||||
%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)}
|
||||
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
|
||||
%div
|
||||
- if !@merge_request.source_branch_exists?
|
||||
= succeed '.' do
|
||||
The changes were merged into
|
||||
%span.label-branch= @merge_request.target_branch
|
||||
The source branch has been removed.
|
||||
- if @source_branch.blank?
|
||||
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
|
||||
%p
|
||||
= succeed '.' do
|
||||
The changes were merged into
|
||||
%span.label-branch= @merge_request.target_branch
|
||||
You can remove the source branch now.
|
||||
%p Changes merged into #{@merge_request.target_branch}. You can remove 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
|
||||
%i.fa.fa-times
|
||||
Remove Source Branch
|
||||
|
||||
.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
|
||||
%p
|
||||
= icon('spinner spin')
|
||||
Removing source branch '#{@merge_request.source_branch}'. Please wait. This page will be automatically reload.
|
||||
%i.fa.fa-spinner.fa-spin
|
||||
|
||||
Removing source branch '#{@merge_request.source_branch}'. Please wait. Page will be automatically reloaded.
|
||||
|
||||
:coffeescript
|
||||
$('.remove_source_branch').on 'click', ->
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
.mr-widget-body
|
||||
- if @project.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?
|
||||
= render 'projects/merge_requests/widget/open/nothing'
|
||||
- elsif @merge_request.branch_missing?
|
||||
|
@ -22,6 +24,6 @@
|
|||
.mr-widget-footer
|
||||
%span
|
||||
%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
|
||||
!= gfm(issues_sentence(@closes_issues))
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
var merge_request_widget;
|
||||
|
||||
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"},
|
||||
url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
|
||||
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
|
||||
.accept-merge-holder.clearfix.js-toggle-container
|
||||
.accept-action
|
||||
|
@ -8,16 +8,22 @@
|
|||
.accept-control.checkbox
|
||||
= label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
|
||||
= check_box_tag :should_remove_source_branch
|
||||
Remove source branch
|
||||
Remove source-branch
|
||||
.accept-control
|
||||
= link_to "#", class: "modify-merge-commit-link js-toggle-button" do
|
||||
= icon('edit')
|
||||
= link_to "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" do
|
||||
%i.fa.fa-edit
|
||||
Modify commit message
|
||||
.js-toggle-content.hide.prepend-top-20
|
||||
= render 'shared/commit_message_container', params: params,
|
||||
text: @merge_request.merge_commit_message,
|
||||
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
|
||||
$('.accept-mr-form').on 'ajax:before', ->
|
||||
btn = $('.accept_merge_request')
|
||||
|
|
|
@ -1,4 +1,2 @@
|
|||
%h4
|
||||
Project is archived
|
||||
%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
|
||||
= icon("spinner spin")
|
||||
Checking ability to merge automatically…
|
||||
%i.fa.fa-spinner.fa-spin
|
||||
Checking automatic merge…
|
||||
|
||||
:coffeescript
|
||||
$ ->
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
%h4
|
||||
= icon("exclamation-triangle")
|
||||
This merge request contains merge conflicts
|
||||
|
||||
%p
|
||||
Please resolve these conflicts or
|
||||
- if @merge_request.can_be_merged_by?(current_user)
|
||||
#{link_to "merge this request manually", "#modal_merge_info", class: "how_to_merge_link vlink", "data-toggle" => "modal"}.
|
||||
- else
|
||||
ask someone with write access to this repository to merge this request manually.
|
||||
- if @merge_request.can_be_merged_by?(current_user)
|
||||
%h4
|
||||
This merge request contains merge conflicts that must be resolved.
|
||||
%p
|
||||
You can merge it manually using the
|
||||
%strong
|
||||
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
|
||||
- else
|
||||
%strong This merge request contains merge conflicts that must be resolved.
|
||||
Only those with write access to this repository can merge merge requests.
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
- unless @merge_request.source_branch_exists?
|
||||
%h4
|
||||
= icon("exclamation-triangle")
|
||||
Source branch
|
||||
%span.label-branch= source_branch_with_namespace(@merge_request)
|
||||
does not exist
|
||||
%p
|
||||
Please restore the source branch or close this merge request and open a new merge request with a different source branch.
|
||||
- else
|
||||
%h4
|
||||
= icon("exclamation-triangle")
|
||||
Target branch
|
||||
%span.label-branch= @merge_request.target_branch
|
||||
does not exist
|
||||
%p
|
||||
Please restore the target branch or use a different target branch.
|
||||
%h4
|
||||
Can't be merged
|
||||
%p
|
||||
This merge request can not be accepted because branch
|
||||
- unless @merge_request.source_branch_exists?
|
||||
%span.label.label-inverse= @merge_request.source_branch
|
||||
does not exist in
|
||||
%span.label.label-info= @merge_request.source_project_path
|
||||
%br
|
||||
%strong Please close this merge request and open a new merge request to change source branches.
|
||||
- else
|
||||
%span.label.label-inverse= @merge_request.target_branch
|
||||
does not exist in
|
||||
%span.label.label-info= @merge_request.target_project_path
|
||||
%br
|
||||
%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
|
||||
Ready to be merged automatically
|
||||
%p
|
||||
Ask someone with write access to this repository to merge this request.
|
||||
%strong This request can be merged automatically.
|
||||
Only those with write access to this repository can merge merge requests.
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
%h4
|
||||
= icon("exclamation-triangle")
|
||||
Nothing to merge from
|
||||
%span.label-branch= source_branch_with_namespace(@merge_request)
|
||||
into
|
||||
%span.label-branch= @merge_request.target_branch
|
||||
%h4 Nothing to merge
|
||||
%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
|
||||
= icon("exclamation-triangle")
|
||||
This merge request failed to be merged automatically
|
||||
|
||||
%p
|
||||
Please reload the page to find out the reason.
|
||||
This merge request cannot be merged. Try to reload the page.
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
%h4
|
||||
This merge request is currently a Work In Progress
|
||||
- if @merge_request.can_be_merged_by?(current_user)
|
||||
%h4
|
||||
This merge request cannot be accepted because it is marked as Work In Progress.
|
||||
|
||||
%p
|
||||
When this merge request is ready, remove the "WIP" prefix from the title to allow it to be merged.
|
||||
%p
|
||||
%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?
|
||||
= 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
|
||||
Bitbucket
|
||||
- else
|
||||
|
|
|
@ -23,18 +23,21 @@
|
|||
= link_to namespace_project_tags_path(@project.namespace, @project) do
|
||||
= pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
|
||||
|
||||
- if !prefer_readme? && @repository.readme
|
||||
%li
|
||||
= link_to 'Readme', readme_path(@project)
|
||||
|
||||
- if @repository.changelog
|
||||
%li
|
||||
= link_to changelog_path(@project) do
|
||||
Changelog
|
||||
= link_to 'Changelog', changelog_path(@project)
|
||||
|
||||
- if @repository.license
|
||||
%li
|
||||
= link_to license_path(@project) do
|
||||
License
|
||||
= link_to 'License', license_path(@project)
|
||||
|
||||
- if @repository.contribution_guide
|
||||
%li
|
||||
= link_to contribution_guide_path(@project) do
|
||||
Contribution guide
|
||||
= link_to 'Contribution guide', contribution_guide_path(@project)
|
||||
|
||||
- if current_user && can_push_branch?(@project, @project.default_branch)
|
||||
- unless @repository.changelog
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
%p.help-block
|
||||
- if issuable.work_in_progress?
|
||||
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
|
||||
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
|
||||
= f.label :description, 'Description', class: 'control-label'
|
||||
.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.save
|
||||
project.satellite.create unless project.satellite.exists?
|
||||
ProjectCacheWorker.perform_async(project.id)
|
||||
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
|
||||
end
|
||||
|
|
|
@ -463,8 +463,8 @@ Gitlab::Application.routes.draw do
|
|||
member do
|
||||
get :diffs
|
||||
get :commits
|
||||
post :merge
|
||||
get :merge_check
|
||||
post :automerge
|
||||
get :automerge_check
|
||||
get :ci_status
|
||||
post :toggle_subscription
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# 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
|
||||
[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.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -129,7 +129,7 @@ Note: `/home/git/` is shorthand for `/home/git`.
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -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 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
|
||||
sudo chmod -R u+rwX tmp/pids/
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
|
@ -93,7 +93,7 @@ To change the Unicorn workers when you have the Omnibus package please see [the
|
|||
|
||||
## 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
|
||||
|
||||
|
|
|
@ -6,14 +6,16 @@ This is the directory structure you will end up with following the instructions
|
|||
| |-- git
|
||||
| |-- .ssh
|
||||
| |-- gitlab
|
||||
| |-- gitlab-satellites
|
||||
| |-- gitlab-shell
|
||||
| |-- repositories
|
||||
|
||||
* `/home/git/.ssh` - contains openssh settings. Specifically the `authorized_keys` file managed by gitlab-shell.
|
||||
* `/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/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).
|
||||
|
|
|
@ -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'
|
||||
```
|
||||
|
||||
#### 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
|
||||
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.534501 #13379] INFO -- : worker=1 spawned pid=13379
|
||||
I, [2015-02-13T07:16:01.534848 #13379] INFO -- : worker=1 ready
|
||||
```
|
||||
```
|
|
@ -105,11 +105,24 @@ Log directory writable? ... yes
|
|||
Tmp directory writable? ... yes
|
||||
Init script exists? ... yes
|
||||
Init script up-to-date? ... yes
|
||||
Projects have satellites? ... yes
|
||||
Redis version >= 2.0.0? ... yes
|
||||
|
||||
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
|
||||
|
||||
In some case it is necessary to rebuild the `authorized_keys` file.
|
||||
|
|
|
@ -13,5 +13,4 @@
|
|||
- [Project users](add-user/add-user.md)
|
||||
- [Protected branches](protected_branches.md)
|
||||
- [Web Editor](web_editor.md)
|
||||
- [Merge Requests](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
|
||||
|
||||
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
|
||||
|
|
|
@ -66,7 +66,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
|
|||
|
||||
def authored_merge_request
|
||||
@authored_merge_request ||= create :merge_request,
|
||||
source_branch: 'markdown',
|
||||
source_branch: 'simple_merge_request',
|
||||
author: current_user,
|
||||
target_project: project,
|
||||
source_project: project
|
||||
|
@ -74,14 +74,14 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
|
|||
|
||||
def other_merge_request
|
||||
@other_merge_request ||= create :merge_request,
|
||||
source_branch: 'fix',
|
||||
source_branch: '2_3_notes_fix',
|
||||
target_project: project,
|
||||
source_project: project
|
||||
end
|
||||
|
||||
def authored_merge_request_from_fork
|
||||
@authored_merge_request_from_fork ||= create :merge_request,
|
||||
source_branch: 'feature_conflict',
|
||||
source_branch: 'basic_page',
|
||||
author: current_user,
|
||||
target_project: public_project,
|
||||
source_project: forked_project
|
||||
|
@ -89,7 +89,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
|
|||
|
||||
def assigned_merge_request_from_fork
|
||||
@assigned_merge_request_from_fork ||= create :merge_request,
|
||||
source_branch: 'markdown',
|
||||
source_branch: 'basic_page_fix',
|
||||
assignee: current_user,
|
||||
target_project: public_project,
|
||||
source_project: forked_project
|
||||
|
|
|
@ -9,6 +9,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
|
|||
@project = Project.find_by(name: "Shop")
|
||||
@project ||= create(:project, name: "Shop")
|
||||
@project.team << [@user, :reporter]
|
||||
@project.ensure_satellite_exists
|
||||
end
|
||||
|
||||
step 'I have a project forked off of "Shop" called "Forked Shop"' do
|
||||
|
|
|
@ -198,10 +198,15 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'merge request "Bug NS-05" is mergeable' do
|
||||
merge_request.project.satellite.create
|
||||
merge_request.mark_as_mergeable
|
||||
end
|
||||
|
||||
step 'I accept this merge request' do
|
||||
Gitlab::Satellite::MergeAction.any_instance.stub(
|
||||
merge!: true,
|
||||
)
|
||||
|
||||
page.within '.mr-state-widget' do
|
||||
click_button "Accept Merge Request"
|
||||
end
|
||||
|
|
|
@ -3,26 +3,6 @@ module API
|
|||
class Files < Grape::API
|
||||
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
|
||||
# Get file from repository
|
||||
# File content is Base64 encoded
|
||||
|
@ -93,11 +73,17 @@ module API
|
|||
|
||||
required_attributes! [:file_path, :branch_name, :content, :commit_message]
|
||||
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
|
||||
status(201)
|
||||
commit_response(attrs)
|
||||
|
||||
{
|
||||
file_path: file_path,
|
||||
branch_name: branch_name
|
||||
}
|
||||
else
|
||||
render_api_error!(result[:message], 400)
|
||||
end
|
||||
|
@ -119,11 +105,17 @@ module API
|
|||
|
||||
required_attributes! [:file_path, :branch_name, :content, :commit_message]
|
||||
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
|
||||
status(200)
|
||||
commit_response(attrs)
|
||||
|
||||
{
|
||||
file_path: file_path,
|
||||
branch_name: branch_name
|
||||
}
|
||||
else
|
||||
http_status = result[:http_status] || 400
|
||||
render_api_error!(result[:message], http_status)
|
||||
|
@ -146,11 +138,17 @@ module API
|
|||
|
||||
required_attributes! [: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
|
||||
status(200)
|
||||
commit_response(attrs)
|
||||
|
||||
{
|
||||
file_path: file_path,
|
||||
branch_name: branch_name
|
||||
}
|
||||
else
|
||||
render_api_error!(result[:message], 400)
|
||||
end
|
||||
|
|
|
@ -198,11 +198,7 @@ module API
|
|||
|
||||
if merge_request.open? && !merge_request.work_in_progress?
|
||||
if merge_request.can_be_merged?
|
||||
commit_message = params[:merge_commit_message] || merge_request.merge_commit_message
|
||||
|
||||
::MergeRequests::MergeService.new(merge_request.target_project, current_user).
|
||||
execute(merge_request, commit_message)
|
||||
|
||||
merge_request.automerge!(current_user, params[:merge_commit_message] || merge_request.merge_commit_message)
|
||||
present merge_request, with: Entities::MergeRequest
|
||||
else
|
||||
render_api_error!('Branch cannot be merged', 405)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'gitlab/git'
|
||||
|
||||
module Gitlab
|
||||
autoload :Satellite, 'gitlab/satellite/satellite'
|
||||
end
|
||||
|
|
|
@ -217,6 +217,20 @@ module Gitlab
|
|||
FileUtils.mv(full_path(old_name), full_path(new_name))
|
||||
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)
|
||||
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
|
||||
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_up_to_date
|
||||
check_projects_have_namespace
|
||||
check_satellites_exist
|
||||
check_redis_version
|
||||
check_ruby_version
|
||||
check_git_version
|
||||
|
@ -237,6 +238,37 @@ namespace :gitlab do
|
|||
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
|
||||
print "Log directory writable? ... "
|
||||
|
||||
|
@ -307,6 +339,7 @@ namespace :gitlab do
|
|||
check_repo_base_is_not_symlink
|
||||
check_repo_base_user_and_group
|
||||
check_repo_base_permissions
|
||||
check_satellites_permissions
|
||||
check_repos_hooks_directory_is_link
|
||||
check_gitlab_shell_self_test
|
||||
|
||||
|
@ -384,6 +417,29 @@ namespace :gitlab do
|
|||
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
|
||||
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
|
||||
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]
|
||||
|
||||
subject.analyze(%Q{
|
||||
Inline code: `@foo`
|
||||
Inline code: `@foo`
|
||||
|
||||
Code block:
|
||||
|
||||
|
@ -33,7 +33,7 @@ describe Gitlab::ReferenceExtractor do
|
|||
@bar
|
||||
```
|
||||
|
||||
Quote:
|
||||
Quote:
|
||||
|
||||
> @offteam
|
||||
})
|
||||
|
@ -49,8 +49,8 @@ describe Gitlab::ReferenceExtractor do
|
|||
end
|
||||
|
||||
it 'accesses valid merge requests' do
|
||||
@m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'markdown')
|
||||
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'feature_conflict')
|
||||
@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: 'bbb')
|
||||
|
||||
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.")
|
||||
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
|
||||
|
||||
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(:set_mentionable_text) { ->(txt){ subject.description = txt } }
|
||||
|
|
|
@ -67,7 +67,7 @@ describe SlackService do
|
|||
opts = {
|
||||
title: 'Awesome merge_request',
|
||||
description: 'please fix',
|
||||
source_branch: 'feature',
|
||||
source_branch: 'stable',
|
||||
target_branch: 'master'
|
||||
}
|
||||
merge_service = MergeRequests::CreateService.new(project,
|
||||
|
|
|
@ -91,6 +91,7 @@ describe Project do
|
|||
describe 'Respond to' do
|
||||
it { is_expected.to respond_to(:url_to_repo) }
|
||||
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(:execute_hooks) }
|
||||
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