Commit c0bf026e authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

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