Commit 3d51a6d4 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into reply-by-email

parents 8ec5fb13 8819007c
Please view this file on the master branch, on stable branches it's out of date.
v 8.0.0 (unreleased)
- Remove satellites
- Better performance for web editor (switched from satellites to rugged)
- Faster merge
- Ability to fetch merge requests from refs/merge-requests/:id
- Allow displaying of archived projects in the admin interface (Artem Sidorenko)
- Allow configuration of import sources for new projects (Artem Sidorenko)
v 7.14.0 (unreleased)
- Update default robots.txt rules to disallow crawling of irrelevant pages (Ben Bodenmiller)
- Fix redirection after sign in when using auto_sign_in_with_provider
......
7.14.0.pre
8.0.0.pre
......@@ -19,7 +19,7 @@ class @MergeRequestWidget
when 'merged'
location.reload()
else
setTimeout(merge_request_widget.mergeInProgress, 3000)
setTimeout(merge_request_widget.mergeInProgress, 2000)
dataType: 'json'
getMergeStatus: ->
......
......@@ -373,3 +373,23 @@ table {
border-color: #EEE !important;
}
}
.center-top-menu {
border-bottom: 1px solid #EEE;
list-style: none;
text-align: center;
padding-bottom: 15px;
margin-bottom: 15px;
li {
display: inline-block;
a {
padding: 10px;
}
&.active a {
color: #666;
}
}
}
......@@ -6,3 +6,11 @@
font-size: 30px;
}
}
.explore-trending-block {
.lead {
line-height: 32px;
font-size: 18px;
margin-top: 10px;
}
}
......@@ -29,6 +29,15 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
end
import_sources = params[:application_setting][:import_sources]
if import_sources.nil?
params[:application_setting][:import_sources] = []
else
import_sources.map! do |source|
source.to_str
end
end
params.require(:application_setting).permit(
:default_projects_limit,
:default_branch_protection,
......@@ -47,6 +56,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:version_check_enabled,
:user_oauth_applications,
restricted_visibility_levels: [],
import_sources: []
)
end
end
......@@ -9,6 +9,7 @@ class Admin::ProjectsController < Admin::ApplicationController
@projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present?
@projects = @projects.non_archived unless params[:with_archived].present?
@projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(PER_PAGE)
......
......@@ -20,7 +20,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :abilities, :can?, :current_application_settings
helper_method :github_import_enabled?, :gitlab_import_enabled?, :bitbucket_import_enabled?
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :git_import_enabled?
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
......@@ -298,15 +298,43 @@ class ApplicationController < ActionController::Base
@issuable_finder.execute
end
def import_sources_enabled?
!current_application_settings.import_sources.empty?
end
def github_import_enabled?
current_application_settings.import_sources.include?('github')
end
def github_import_configured?
Gitlab::OAuth::Provider.enabled?(:github)
end
def gitlab_import_enabled?
request.host != 'gitlab.com' && current_application_settings.import_sources.include?('gitlab')
end
def gitlab_import_configured?
Gitlab::OAuth::Provider.enabled?(:gitlab)
end
def bitbucket_import_enabled?
current_application_settings.import_sources.include?('bitbucket')
end
def bitbucket_import_configured?
Gitlab::OAuth::Provider.enabled?(:bitbucket) && Gitlab::BitbucketImport.public_key.present?
end
def gitorious_import_enabled?
current_application_settings.import_sources.include?('gitorious')
end
def google_code_import_enabled?
current_application_settings.import_sources.include?('google_code')
end
def git_import_enabled?
current_application_settings.import_sources.include?('git')
end
end
......@@ -7,6 +7,7 @@ class Explore::ProjectsController < Explore::ApplicationController
@tags = @projects.tags_on(:tags)
@projects = @projects.tagged_with(params[:tag]) if params[:tag].present?
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.non_archived
@projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE)
......@@ -14,6 +15,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def trending
@trending_projects = TrendingProjectsFinder.new.execute(current_user)
@trending_projects = @trending_projects.non_archived
@trending_projects = @trending_projects.page(params[:page]).per(PER_PAGE)
end
......
......@@ -10,7 +10,8 @@ class HelpController < ApplicationController
respond_to do |format|
format.any(:markdown, :md, :html) do
path = Rails.root.join('doc', @category, "#{@file}.md")
# Note: We are purposefully NOT using `Rails.root.join`
path = File.join(Rails.root, 'doc', @category, "#{@file}.md")
if File.exist?(path)
@markdown = File.read(path)
......@@ -24,7 +25,8 @@ class HelpController < ApplicationController
# Allow access to images in the doc folder
format.any(:png, :gif, :jpeg) do
path = Rails.root.join('doc', @category, "#{@file}.#{params[:format]}")
# Note: We are purposefully NOT using `Rails.root.join`
path = File.join(Rails.root, 'doc', @category, "#{@file}.#{params[:format]}")
if File.exist?(path)
send_file(path, disposition: 'inline')
......
class Import::GitoriousController < Import::BaseController
before_action :verify_gitorious_import_enabled
def new
redirect_to client.authorize_url(callback_import_gitorious_url)
......@@ -40,4 +41,8 @@ class Import::GitoriousController < Import::BaseController
@client ||= Gitlab::GitoriousImport::Client.new(session[:gitorious_repos])
end
def verify_gitorious_import_enabled
not_found! unless gitorious_import_enabled?
end
end
class Import::GoogleCodeController < Import::BaseController
before_action :verify_google_code_import_enabled
before_action :user_map, only: [:new_user_map, :create_user_map]
def new
......@@ -104,6 +105,10 @@ class Import::GoogleCodeController < Import::BaseController
@client ||= Gitlab::GoogleCodeImport::Client.new(session[:google_code_dump])
end
def verify_google_code_import_enabled
not_found! unless google_code_import_enabled?
end
def user_map
@user_map ||= begin
user_map = client.user_map
......
......@@ -13,27 +13,20 @@ class Projects::BlobController < Projects::ApplicationController
before_action :commit, except: [:new, :create]
before_action :blob, except: [:new, :create]
before_action :from_merge_request, only: [:edit, :update]
before_action :after_edit_path, 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]
def new
commit unless @repository.empty?
end
def create
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
result = Files::CreateService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
ref = sanitized_new_branch_name.presence || @ref
redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(ref, file_path))
redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path))
else
flash[:alert] = result[:message]
render :new
......@@ -48,22 +41,10 @@ class Projects::BlobController < Projects::ApplicationController
end
def update
result = Files::UpdateService.
new(
@project,
current_user,
params.merge(new_branch: sanitized_new_branch_name),
@ref,
@path
).execute
result = Files::UpdateService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
if from_merge_request
from_merge_request.reload_code
end
redirect_to after_edit_path
else
flash[:alert] = result[:message]
......@@ -80,12 +61,11 @@ class Projects::BlobController < Projects::ApplicationController
end
def destroy
result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute
result = Files::DeleteService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
redirect_to namespace_project_tree_path(@project.namespace, @project,
@ref)
redirect_to namespace_project_tree_path(@project.namespace, @project, @target_branch)
else
flash[:alert] = result[:message]
render :show
......@@ -135,7 +115,6 @@ class Projects::BlobController < Projects::ApplicationController
@id = params[:id]
@ref, @path = extract_ref(@id)
rescue InvalidPathError
not_found!
end
......@@ -145,8 +124,8 @@ class Projects::BlobController < Projects::ApplicationController
if from_merge_request
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
elsif sanitized_new_branch_name.present?
namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path))
elsif @target_branch.present?
namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
else
namespace_project_blob_path(@project.namespace, @project, @id)
end
......@@ -160,4 +139,25 @@ class Projects::BlobController < Projects::ApplicationController
def sanitized_new_branch_name
@new_branch ||= sanitize(strip_tags(params[:new_branch]))
end
def editor_variables
@current_branch = @ref
@target_branch = (sanitized_new_branch_name || @ref)
@file_path =
if action_name.to_s == 'create'
File.join(@path, File.basename(params[:file_name]))
else
@path
end
@commit_params = {
file_path: @file_path,
current_branch: @current_branch,
target_branch: @target_branch,
commit_message: params[:commit_message],
file_content: params[:content],
file_content_encoding: params[:encoding]
}
end
end
......@@ -13,13 +13,8 @@ class Projects::CompareController < Projects::ApplicationController
base_ref = Addressable::URI.unescape(params[:from])
@ref = head_ref = Addressable::URI.unescape(params[:to])
compare_result = CompareService.new.execute(
current_user,
@project,
head_ref,
@project,
base_ref
)
compare_result = CompareService.new.
execute(@project, head_ref, @project, base_ref)
@commits = compare_result.commits
@diffs = compare_result.diffs
......
require 'gitlab/satellite/satellite'
class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled
before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :automerge, :automerge_check,
:edit, :update, :show, :diffs, :commits, :merge, :merge_check,
:ci_status, :toggle_subscription
]
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits]
......@@ -137,7 +135,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
def automerge_check
def merge_check
if @merge_request.unchecked?
@merge_request.check_if_can_be_merged
end
......@@ -147,11 +145,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render partial: "projects/merge_requests/widget/show.html.haml", layout: false
end
def automerge
def merge
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
if @merge_request.automergeable?
AutoMergeWorker.perform_async(@merge_request.id, current_user.id, params)
if @merge_request.mergeable?
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = true
else
@status = false
......
......@@ -39,4 +39,21 @@ module ApplicationSettingsHelper
end
end
end
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def import_sources_checkboxes(help_block_id)
Gitlab::ImportSources.options.map do |name, source|
checked = current_application_settings.import_sources.include?(source)
css_class = 'btn'
css_class += ' active' if checked
checkbox_name = 'application_setting[import_sources][]'
label_tag(checkbox_name, class: css_class) do
check_box_tag(checkbox_name, source, checked,
autocomplete: 'off',
'aria-describedby' => help_block_id) + name
end
end
end
end
......@@ -10,7 +10,7 @@ module ExploreHelper
options = exist_opts.merge(options)
path = request.path
path = explore_projects_path
path << "?#{options.to_param}"
path
end
......
......@@ -67,6 +67,14 @@ module TabHelper
path.any? do |single_path|
current_path?(single_path)
end
elsif page = options.delete(:page)
unless page.respond_to?(:each)
page = [page]
end
page.any? do |single_page|
current_page?(single_page)
end
else
c = options.delete(:controller)
a = options.delete(:action)
......
......@@ -22,10 +22,12 @@
# user_oauth_applications :boolean default(TRUE)
# after_sign_out_path :string(255)
# session_expire_delay :integer default(10080), not null
# import_sources :text
#
class ApplicationSetting < ActiveRecord::Base
serialize :restricted_visibility_levels
serialize :import_sources
serialize :restricted_signup_domains, Array
attr_accessor :restricted_signup_domains_raw
......@@ -52,6 +54,16 @@ class ApplicationSetting < ActiveRecord::Base
end
end
validates_each :import_sources do |record, attr, value|
unless value.nil?
value.each do |source|
unless Gitlab::ImportSources.options.has_value?(source)
record.errors.add(attr, "'#{source}' is not a import source")
end
end
end
end
def self.current
ApplicationSetting.last
end
......@@ -70,7 +82,8 @@ class ApplicationSetting < ActiveRecord::Base
session_expire_delay: Settings.gitlab['session_expire_delay'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
restricted_signup_domains: Settings.gitlab['restricted_signup_domains']
restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
import_sources: ['github','bitbucket','gitlab','gitorious','google_code','git']
)
end
......
......@@ -41,8 +41,6 @@ class MergeRequest < ActiveRecord::Base
delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil
attr_accessor :should_remove_source_branch
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
attr_accessor :allow_broken
......@@ -57,7 +55,7 @@ class MergeRequest < ActiveRecord::Base
transition [:reopened, :opened] => :closed
end
event :merge do
event :mark_as_merged do
transition [:reopened, :opened, :locked] => :merged
end
......@@ -205,7 +203,10 @@ class MergeRequest < ActiveRecord::Base
end
def check_if_can_be_merged
if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
can_be_merged =
project.repository.can_be_merged?(source_sha, target_branch)
if can_be_merged
mark_as_mergeable
else
mark_as_unmergeable
......@@ -220,18 +221,6 @@ class MergeRequest < ActiveRecord::Base
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end
def automerge!(current_user, commit_message = nil)
return unless automergeable?
MergeRequests::AutoMergeService.
new(target_project, current_user).
execute(self, commit_message)
end
def remove_source_branch?
self.should_remove_source_branch && !self.source_project.root_ref?(self.source_branch) && !self.for_fork?
end
def open?
opened? || reopened?
end
......@@ -240,11 +229,11 @@ class MergeRequest < ActiveRecord::Base
title =~ /\A\[?WIP\]?:? /i
end
def automergeable?
def mergeable?
open? && !work_in_progress? && can_be_merged?
end
def automerge_status
def gitlab_merge_status
if work_in_progress?
"work_in_progress"
else
......@@ -271,14 +260,14 @@ class MergeRequest < ActiveRecord::Base
#
# see "git diff"
def to_diff(current_user)
Gitlab::Satellite::MergeAction.new(current_user, self).diff_in_satellite
target_project.repository.diff_text(target_branch, source_sha)
end
# Returns the commit as a series of email patches.
#
# see "git format-patch"
def to_patch(current_user)
Gitlab::Satellite::MergeAction.new(current_user, self).format_patch
target_project.repository.format_patch(target_branch, source_sha)
end
def hook_attrs
......@@ -429,4 +418,30 @@ class MergeRequest < ActiveRecord::Base
"Open"
end
end
def target_sha
@target_sha ||= target_project.
repository.commit(target_branch).sha
end
def source_sha
commits.first.sha
end
def fetch_ref
target_project.repository.fetch_ref(
source_project.repository.path_to_repo,
"refs/heads/#{source_branch}",
"refs/merge-requests/#{iid}/head"
)
end
def in_locked_state
begin
lock_mr
yield
ensure
unlock_mr if locked?
end
end
end
......@@ -16,9 +16,8 @@ require Rails.root.join("app/models/commit")
class MergeRequestDiff < ActiveRecord::Base
include Sortable
# Prevent store of diff
# if commits amount more then 200
COMMITS_SAFE_SIZE = 200
# Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 500
attr_reader :commits, :diffs
......@@ -124,12 +123,12 @@ class MergeRequestDiff < ActiveRecord::Base
if new_diffs.any?
if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES
self.state = :overflow_diff_files_limit
new_diffs = []
new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES]
end
if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES
self.state = :overflow_diff_lines_limit
new_diffs = []
new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES]
end
end
......@@ -160,12 +159,21 @@ class MergeRequestDiff < ActiveRecord::Base
private
def compare_result
@compare_result ||= CompareService.new.execute(
merge_request.author,
merge_request.source_project,
merge_request.source_branch,
merge_request.target_project,
merge_request.target_branch,
)
@compare_result ||=
begin
# Update ref for merge request
merge_request.fetch_ref
# Get latest sha of branch from source project
source_sha = merge_request.source_project.commit(source_branch).sha
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
merge_request.target_project.repository.raw_repository,
merge_request.target_branch,
source_sha,
)
)
end
end
end
......@@ -118,12 +118,11 @@ class Namespace < ActiveRecord::Base
gitlab_shell.add_namespace(path_was)
if gitlab_shell.mv_namespace(path_was, path)
# If repositories moved successfully we need to remove old satellites
# and send update instructions to users.
# If repositories moved successfully we need to
# send update instructions to users.
# However we cannot allow rollback since we moved namespace dir
# So we basically we mute exceptions in next actions
begin
gitlab_shell.rm_satellites(path_was)
send_update_instructions
rescue
# Returning false does not rollback after_* transaction but gives
......
......@@ -215,7 +215,7 @@ class Project < ActiveRecord::Base
end
def search(query)
joins(:namespace).where('projects.archived = ?', false).
joins(:namespace).
where('LOWER(projects.name) LIKE :query OR
LOWER(projects.path) LIKE :query OR
LOWER(namespaces.name) LIKE :query OR
......@@ -520,14 +520,6 @@ class Project < ActiveRecord::Base
!repository.exists? || repository.empty?
end
def ensure_satellite_exists
self.satellite.create unless self.satellite.exists?
end
def satellite
@satellite ||= Gitlab::Satellite::Satellite.new(self)
end
def repo
repository.raw
end
......@@ -597,14 +589,11 @@ class Project < ActiveRecord::Base
new_path_with_namespace = File.join(namespace_dir, path)
if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
# If repository moved successfully we need to remove old satellite
# and send update instructions to users.
# If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository
# So we basically we mute exceptions in next actions
begin
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
gitlab_shell.rm_satellites(old_path_with_namespace)
ensure_satellite_exists
send_move_instructions
reset_events_cache
rescue
......@@ -702,7 +691,6 @@ class Project < ActiveRecord::Base
def create_repository
if forked?
if gitlab_shell.fork_repository(forked_from_project.path_with_namespace, self.namespace.path)
ensure_satellite_exists
true
else
errors.add(:base, 'Failed to fork repository via gitlab-shell')
......
......@@ -74,6 +74,8 @@ class GitlabCiService < CiService
else
:error
end
rescue Errno::ECONNREFUSED
:error
end
def fork_registration(new_project, private_token)
......@@ -103,6 +105,8 @@ class GitlabCiService < CiService
if response.code == 200 and response["coverage"]
response["coverage"]
end
rescue Errno::ECONNREFUSED
nil
end
def build_page(sha, ref)
......
require 'securerandom'
class Repository
class PreReceiveError < StandardError; end
class CommitError < StandardError; end
include Gitlab::ShellAdapter
attr_accessor :raw_repository, :path_with_namespace, :project
......@@ -368,6 +373,89 @@ class Repository
@root_ref ||= raw_repository.root_ref
end
def commit_file(user, path, content, message, branch)
commit_with_hooks(user, branch) do |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
end
def remove_file(user, path, message, branch)
commit_with_hooks(user, branch) do |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
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(user, 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?
commit_with_hooks(user, target_branch) do |ref|
actual_options = options.merge(
parents: [our_commit, their_commit],
tree: merge_index.write_tree(rugged),
update_ref: ref
)
Rugged::Commit.create(rugged, actual_options)
end
end
def merged_to_root_ref?(branch_name)
branch_commit = commit(branch_name)
root_ref_commit = commit(root_ref)
......@@ -412,6 +500,64 @@ class Repository
)
end
def fetch_ref(source_path, source_ref, target_ref)
args = %W(git fetch #{source_path} #{source_ref}:#{target_ref})
Gitlab::Popen.popen(args, path_to_repo)
end
def commit_with_hooks(current_user, branch)
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
gl_id = Gitlab::ShellEnv.gl_id(current_user)
was_empty = empty?
# Create temporary ref
random_string = SecureRandom.hex
tmp_ref = "refs/tmp/#{random_string}/head"
unless was_empty
oldrev = find_branch(branch).target
rugged.references.create(tmp_ref, oldrev)
end
# Make commit in tmp ref
newrev = yield(tmp_ref)
unless newrev
raise CommitError.new('Failed to create commit')
end
# Run GitLab pre-receive hook
pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo)
status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref)
if status
if was_empty
# Create branch
rugged.references.create(ref, newrev)
else
# Update head
current_head = find_branch(branch).target
# Make sure target branch was not changed during pre-receive hook
if current_head == oldrev
rugged.references.update(ref, newrev)
else
raise CommitError.new('Commit was rejected because branch received new push')
end
end
# Run GitLab post receive hook
post_receive_hook = Gitlab::Git::Hook.new('post-receive', path_to_repo)
status = post_receive_hook.trigger(gl_id, oldrev, newrev, ref)
else
# Remove tmp ref and return error to user
rugged.references.delete(tmp_ref)
raise PreReceiveError.new('Commit was rejected by pre-receive hook')
end
end
private
def cache
......
......@@ -31,6 +31,10 @@ class BaseService
SystemHooksService.new
end
def repository
project.repository
end
# Add an error to the specified model for restricted visibility levels
def deny_visibility_level(model, denied_visibility_level = nil)
denied_visibility_level ||= model.visibility_level
......
require 'securerandom'
# Compare 2 branches for one repo or between repositories
# and return Gitlab::CompareResult object that responds to commits and diffs
class CompareService
def execute(current_user, source_project, source_branch, target_project, target_branch)
# Try to compare branches to get commits list and diffs
#
# Note: Use satellite only when need to compare between two repos
# because satellites are slower than operations on bare repo
if target_project == source_project
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
target_project.repository.raw_repository,
target_branch,
source_branch,
)
def execute(source_project, source_branch, target_project, target_branch)
source_sha = source_project.commit(source_branch).sha
# If compare with other project we need to fetch ref first
unless target_project == source_project
random_string = SecureRandom.hex
target_project.repository.fetch_ref(
source_project.repository.path_to_repo,
"refs/heads/#{source_branch}",
"refs/tmp/#{random_string}/head"
)
else
Gitlab::Satellite::CompareAction.new(
current_user,
target_project,
target_branch,
source_project,
source_branch
).result
end
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
target_project.repository.raw_repository,
target_branch,
source_sha,
)
)
end
end
module Files
class BaseService < ::BaseService
attr_reader :ref, :path
class ValidationError < StandardError; end
def initialize(project, user, params, ref, path = nil)
@project, @current_user, @params = project, user, params.dup
@ref = ref
@path = path
def execute
@current_branch = params[:current_branch]
@target_branch = params[:target_branch]
@commit_message = params[:commit_message]
@file_path = params[:file_path]
@file_content = if params[:file_content_encoding] == 'base64'
Base64.decode64(params[:file_content])
else
params[:file_content]
end
# Validate parameters
validate
# Create new branch if it different from current_branch
if @target_branch != @current_branch
create_target_branch
end
if sha = commit
success
else
error("Something went wrong. Your changes were not committed")
end
rescue Repository::CommitError, Repository::PreReceiveError, ValidationError => ex
error(ex.message)
end
private
def repository
project.repository
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
require_relative "base_service"
module Files
class CreateService < BaseService
def execute
allowed = Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
class CreateService < Files::BaseService
def commit
repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
end
unless allowed
return error("You are not allowed to create file in this branch")
end
def validate
super
file_name = File.basename(path)
file_path = path
file_name = File.basename(@file_path)
unless file_name =~ Gitlab::Regex.file_name_regex
return error(
raise_error(
'Your changes could not be committed, because the file name ' +
Gitlab::Regex.file_name_regex_message
)
end
if project.empty_repo?
# everything is ok because repo does not have a commits yet
else
unless repository.branch_names.include?(ref)
return error("You can only create files if you are on top of a branch")
end
blob = repository.blob_at_branch(ref, file_path)
unless project.empty_repo?
blob = repository.blob_at_branch(@current_branch, @file_path)
if blob
return error("Your changes could not be committed, because file with such name exists")
raise_error("Your changes could not be committed, because file with such name exists")
end
end
new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path)
created_successfully = new_file_action.commit!(
params[:content],
params[:commit_message],
params[:encoding],
params[:new_branch]
)
if created_successfully
success
else
error("Your changes could not be committed, because the file has been changed")
end
end
end
end
require_relative "base_service"
module Files
class DeleteService < BaseService
def execute
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
unless allowed
return error("You are not allowed to push into this branch")
end
unless repository.branch_names.include?(ref)
return error("You can only create files if you are on top of a branch")
end
blob = repository.blob_at_branch(ref, path)
unless blob
return error("You can only edit text files")
end
delete_file_action = Gitlab::Satellite::DeleteFileAction.new(current_user, project, ref, path)
deleted_successfully = delete_file_action.commit!(
nil,
params[:commit_message]
)
if deleted_successfully
success
else
error("Your changes could not be committed, because the file has been changed")
end
class DeleteService < Files::BaseService
def commit
repository.remove_file(current_user, @file_path, @commit_message, @target_branch)
end
end
end
require_relative "base_service"
module Files
class UpdateService < BaseService
def execute
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
unless allowed
return error("You are not allowed to push into this branch")
end
unless repository.branch_names.include?(ref)
return error("You can only create files if you are on top of a branch")
end
blob = repository.blob_at_branch(ref, path)
unless blob
return error("You can only edit text files")
end
edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path)
edit_file_action.commit!(
params[:content],
params[:commit_message],
params[:encoding],
params[:new_branch]
)
success
rescue Gitlab::Satellite::CheckoutFailed => ex
error("Your changes could not be committed because ref '#{ref}' could not be checked out", 400)
rescue Gitlab::Satellite::CommitFailed => ex
error("Your changes could not be committed. Maybe there was nothing to commit?", 409)
rescue Gitlab::Satellite::PushFailed => ex
error("Your changes could not be committed. Maybe the file was changed by another process?", 409)
class UpdateService < Files::BaseService
def commit
repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
end
end
end
......@@ -10,16 +10,14 @@ class GitPushService
#
# Next, this method:
# 1. Creates the push event
# 2. Ensures that the project satellite exists
# 3. Updates merge requests
# 4. Recognizes cross-references from commit messages
# 5. Executes the project's web hooks
# 6. Executes the project's services
# 2. Updates merge requests
# 3. Recognizes cross-references from commit messages
# 4. Executes the project's web hooks
# 5. Executes the project's services
#
def execute(project, user, oldrev, newrev, ref)
@project, @user = project, user
project.ensure_satellite_exists
project.repository.expire_cache
if push_remove_branch?(ref, newrev)
......@@ -133,7 +131,8 @@ class GitPushService
end
def is_default_branch?(ref)
Gitlab::Git.branch_ref?(ref) && Gitlab::Git.ref_name(ref) == project.default_branch
Gitlab::Git.branch_ref?(ref) &&
(Gitlab::Git.ref_name(ref) == project.default_branch || project.default_branch.nil?)
end
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,12 +12,16 @@ module MergeRequests
merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch
unless merge_request.target_branch && merge_request.source_branch
return build_failed(merge_request, nil)
if merge_request.target_branch.blank? || merge_request.source_branch.blank?
message =
if params[:source_branch] || params[:target_branch]
"You must select source and target branch"
end
return build_failed(merge_request, message)
end
compare_result = CompareService.new.execute(
current_user,
merge_request.source_project,
merge_request.source_branch,
merge_request.target_project,
......@@ -40,7 +44,6 @@ module MergeRequests
merge_request.compare_diffs = diffs
elsif diffs == false
# satellite timeout return false
merge_request.can_be_created = false
merge_request.compare_failed = true
end
......@@ -59,9 +62,6 @@ module MergeRequests
end
merge_request
rescue Gitlab::Satellite::BranchesWithoutParent
return build_failed(merge_request, "Selected branches have no common commit so they cannot be merged.")
end
def build_failed(merge_request, message)
......
module MergeRequests
# MergeService class
#
# Mark existing merge request as merged
# and execute all hooks and notifications
# Called when you do merge via command line and push code
# to target branch
class MergeService < BaseMergeService
# Do git merge and in case of success
# mark merge request as merged and execute all hooks and notifications
# Executed when you do merge via GitLab UI
#
class MergeService < MergeRequests::BaseService
attr_reader :merge_request, :commit_message
def execute(merge_request, commit_message)
merge_request.merge
@commit_message = commit_message
@merge_request = merge_request
unless @merge_request.mergeable?
return error('Merge request is not mergeable')
end
merge_request.in_locked_state do
if commit
after_merge
success
else
error('Can not merge changes')
end
end
end
create_merge_event(merge_request, current_user)
create_note(merge_request)
notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request, 'merge')
private
def commit
committer = repository.user_to_comitter(current_user)
options = {
message: commit_message,
author: committer,
committer: committer
}
repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options)
end
true
rescue
false
def after_merge
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
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
merge_requests.uniq.select(&:source_project).each do |merge_request|
MergeRequests::MergeService.
MergeRequests::PostMergeService.
new(merge_request.target_project, @current_user).
execute(merge_request, nil)
execute(merge_request)
end
end
......
......@@ -27,7 +27,6 @@ module Projects
end
end
project.satellite.destroy
log_info("Project \"#{project.name}\" was removed")
system_hook_service.execute_hooks_for(project, :destroy)
true
......
......@@ -33,9 +33,6 @@ module Projects
raise TransferError.new("Project with same path in target namespace already exists")
end
# Remove old satellite
project.satellite.destroy
# Apply new namespace id
project.namespace = new_namespace
project.save!
......@@ -51,9 +48,6 @@ module Projects
# Move wiki repo also if present
gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki")
# Create a new satellite (reload project from DB)
Project.find(project.id).ensure_satellite_exists
# clear project cached events
project.reset_events_cache
......
......@@ -27,6 +27,20 @@
- restricted_level_checkboxes('restricted-visibility-help').each do |level|
= level
%span.help-block#restricted-visibility-help Selected levels cannot be used by non-admin users for projects or snippets
.form-group
= f.label :import_sources, class: 'control-label col-sm-2'
.col-sm-10
- data_attrs = { toggle: 'buttons' }
.btn-group{ data: data_attrs }
- import_sources_checkboxes('import-sources-help').each do |source|
= source
%span.help-block#import-sources-help
Enabled sources for code import during project creation. OmniAuth must be configured for GitHub
= link_to "(?)", help_page_path("integration", "github")
, Bitbucket
= link_to "(?)", help_page_path("integration", "bitbucket")
and GitLab.com
= link_to "(?)", help_page_path("integration", "gitlab")
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
......
......@@ -23,6 +23,10 @@
= label_tag :abandoned do
= check_box_tag :abandoned, 1, params[:abandoned]
%span No activity over 6 month
.checkbox
= label_tag :with_archived do
= check_box_tag :with_archived, 1, params[:with_archived]
%span Show archived projects
%fieldset
%strong Visibility level:
......@@ -73,6 +77,8 @@
= visibility_level_icon(project.visibility_level)
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
.pull-right
- if project.archived
%span.label.label-warning archived
%span.label.label-gray
= repository_size(project)
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
......
%ul.center-top-menu
= nav_link(page: [dashboard_groups_path]) do
= link_to dashboard_groups_path, title: 'Your groups', data: {placement: 'right'} do
Your Groups
= nav_link(page: [explore_groups_path]) do
= link_to explore_groups_path, title: 'Explore groups', data: {toggle: 'tooltip', placement: 'bottom'} do
Explore Groups
%ul.center-top-menu
= nav_link(path: ['dashboard#show', 'root#show']) do
= link_to dashboard_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
Your Projects
= nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
Starred Projects
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
= link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do
Explore Projects
- page_title "Groups"
%h3.page-title
Group Membership
= render 'dashboard/groups_head'
.slead
Group members have access to all group projects.
- if current_user.can_create_group?
%span.pull-right.hidden-xs
= link_to new_group_path, class: "btn btn-new" do
= link_to new_group_path, class: "btn btn-new btn-sm" do
%i.fa.fa-plus
New Group
%p.light
Group members have access to all group projects.
%hr
.panel.panel-default
.panel-heading
%strong Groups
......
- page_title "Starred Projects"
= render 'dashboard/projects_head'
- if @projects.any?
= render 'shared/show_aside'
......
......@@ -2,6 +2,8 @@
- if current_user
= auto_discovery_link_tag(:atom, dashboard_url(format: :atom, private_token: current_user.private_token), title: "All activity")
= render 'dashboard/projects_head'
- if @projects.any?
= render 'shared/show_aside'
......
- page_title "Groups"
.clearfix
- if current_user
= render 'dashboard/groups_head'
.clearfix.append-bottom-10
.pull-left
= form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f|
= hidden_field_tag :sort, @sort
......@@ -28,15 +30,12 @@
= link_to explore_groups_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
%hr
%ul.bordered-list
- @groups.each do |group|
%li
.clearfix
%h4
= link_to group_path(id: group.path) do
%i.fa.fa-users
= group.name
.clearfix
%p
......
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort:
- if @sort.present?
= sort_options_hash[@sort]
- elsif current_page?(trending_explore_projects_path) || current_page?(explore_root_path)
Trending projects
- elsif current_page?(starred_explore_projects_path)
Most stars
- else
= sort_title_recently_created
%b.caret
%ul.dropdown-menu
%li
= link_to trending_explore_projects_path do
Trending projects
= link_to starred_explore_projects_path do
Most stars
= link_to explore_projects_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created
= link_to explore_projects_filter_path(sort: sort_value_oldest_created) do
= sort_title_oldest_created
= link_to explore_projects_filter_path(sort: sort_value_recently_updated) do
= sort_title_recently_updated
= link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
......@@ -46,22 +46,4 @@
= link_to explore_projects_filter_path(tag: tag.name) do
%i.fa.fa-tag
= tag.name
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort:
- if @sort.present?
= sort_options_hash[@sort]
- else
= sort_title_recently_created
%b.caret
%ul.dropdown-menu
%li
= link_to explore_projects_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created
= link_to explore_projects_filter_path(sort: sort_value_oldest_created) do
= sort_title_oldest_created
= link_to explore_projects_filter_path(sort: sort_value_recently_updated) do
= sort_title_recently_updated
= link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
= render 'explore/projects/dropdown'
- page_title "Projects"
- if current_user
= render 'dashboard/projects_head'
.clearfix
= render 'filter'
%hr
%br
.public-projects
%ul.bordered-list.top-list
= render @projects
......
- page_title "Starred Projects"
- if current_user
= render 'dashboard/projects_head'
.explore-trending-block
%p.lead
.lead
%i.fa.fa-star
See most starred projects
%hr
.pull-right
= render 'explore/projects/dropdown'
.public-projects
%ul.bordered-list
= render @starred_projects
......
- page_title "Trending Projects"
- if current_user
= render 'dashboard/projects_head'
.explore-title
%h3
Explore GitLab
......@@ -6,10 +8,11 @@
Discover projects and groups. Share your projects with others
%hr
.explore-trending-block
%p.lead
.lead
%i.fa.fa-comments-o
See most discussed projects for last month
%hr
.pull-right
= render 'explore/projects/dropdown'
.public-projects
%ul.bordered-list
= render @trending_projects
......@@ -12,11 +12,14 @@
- @projects.each do |project|
%li
.list-item-name
= visibility_level_icon(project.visibility_level)
%span{ class: visibility_level_color(project.visibility_level) }
= visibility_level_icon(project.visibility_level)
%strong= link_to project.name_with_namespace, project
.pull-right
- if project.archived
%span.label.label-warning archived
%span.label.label-gray
= repository_size(project)
.pull-right
= link_to 'Members', namespace_project_project_members_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove"
......
- page_title "Explore"
- header_title "Explore GitLab", explore_root_path
- sidebar "explore"
- if current_user
- header_title "Dashboard", root_path
- else
- header_title "Explore GitLab", explore_root_path
- sidebar "dashboard"
= render template: "layouts/application"
......@@ -17,13 +17,13 @@
%li.visible-sm.visible-xs
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('search')
%li.hidden-xs
-#%li.hidden-xs
= link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('question-circle fw')
%li
-#%li
= link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('globe fw')
%li
-#%li
= link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('clipboard fw')
- if current_user.is_admin?
......@@ -34,7 +34,7 @@
%li.hidden-xs
= link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('plus fw')
%li
-#%li
= link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('cog fw')
%li
......
%ul.nav.nav-sidebar
= nav_link(path: ['dashboard#show', 'root#show'], html_options: {class: 'home'}) do
= link_to dashboard_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to (current_user ? root_path : explore_root_path), title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
= icon('dashboard fw')
%span
Your Projects
= nav_link(path: 'projects#starred') do
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
= icon('star fw')
%span
Starred Projects
Projects
= nav_link(controller: :groups) do
= link_to dashboard_groups_path, title: 'Groups', data: {placement: 'right'} do
= link_to (current_user ? dashboard_groups_path : explore_groups_path), title: 'Groups', data: {placement: 'right'} do
= icon('group fw')
%span
Groups
= nav_link(controller: :milestones) do
= link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do
= icon('clock-o fw')
%span
Milestones
= nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
= icon('exclamation-circle fw')
%span
Issues
%span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do
= icon('tasks fw')
- if current_user
= nav_link(controller: :milestones) do
= link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do
= icon('clock-o fw')
%span
Milestones
= nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
= icon('exclamation-circle fw')
%span
Issues
%span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do
= icon('tasks fw')
%span
Merge Requests
%span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :snippets) do
= link_to (current_user ? user_snippets_path(current_user) : snippets_path), title: 'Your snippets', data: {placement: 'right'} do
= icon('dashboard fw')
%span
Merge Requests
%span.count= current_user.assigned_merge_requests.opened.count
Snippets
- if current_user
= nav_link(controller: :profile) do
= link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('user fw')
%span
Profile
= nav_link(controller: :help) do
= link_to help_path, title: 'Help', data: {placement: 'right'} do
= icon('question-circle fw')
......
%ul.nav.nav-sidebar
= nav_link(path: 'projects#trending') do
= link_to explore_root_path, title: 'Trending Projects', data: {placement: 'right'} do
= icon('comments fw')
%span Trending Projects
= nav_link(path: 'projects#starred') do
= link_to starred_explore_projects_path, title: 'Most-starred Projects', data: {placement: 'right'} do
= icon('star fw')
%span Most-starred Projects
= nav_link(path: 'projects#index') do
= link_to explore_projects_path, title: 'All Projects', data: {placement: 'right'} do
= icon('bookmark fw')
%span All Projects
= nav_link(controller: :groups) do
= link_to explore_groups_path, title: 'All Groups', data: {placement: 'right'} do
= icon('group fw')
%span All Groups
%ul.nav.nav-sidebar
- if current_user
= nav_link(path: user_snippets_path(current_user), html_options: {class: 'home'}) do
= link_to user_snippets_path(current_user), title: 'Your snippets', data: {placement: 'right'} do
= icon('dashboard fw')
%span
Your Snippets
= nav_link(path: snippets_path) do
= link_to snippets_path, title: 'Discover snippets', data: {placement: 'right'} do
= icon('globe fw')
%span
Discover Snippets
- page_title 'Snippets'
- header_title 'Snippets', snippets_path
- sidebar "snippets"
- if current_user
- header_title "Dashboard", root_path
- else
- header_title 'Snippets', snippets_path
- sidebar "dashboard"
= render template: "layouts/application"
%p.slead
Should you ever lose your phone, each of these recovery codes can be used one
time each to regain access to your account. Please save them in a safe place.
time each to regain access to your account. Please save them in a safe place, or you
%b will
lose access to your account.
.codes.well
%ul
......
......@@ -5,9 +5,9 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3 Import projects from Bitbucket
.modal-body
To enable importing projects from Bitbucket,
To enable importing projects from Bitbucket,
- if current_user.admin?
you need to
as administrator you need to configure
- else
your GitLab administrator needs to
== #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/bitbucket.md'}.
ask your GitLab administrator to configure
== #{link_to 'OAuth integration', help_page_path("integration", "bitbucket")}.
......@@ -5,9 +5,9 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3 Import projects from GitHub
.modal-body
To enable importing projects from GitHub,
To enable importing projects from GitHub,
- if current_user.admin?
you need to
as administrator you need to configure
- else
your GitLab administrator needs to
== #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md'}.
\ No newline at end of file
ask your Gitlab administrator to configure
== #{link_to 'OAuth integration', help_page_path("integration", "github")}.
......@@ -5,9 +5,9 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3 Import projects from GitLab.com
.modal-body
To enable importing projects from GitLab.com,
To enable importing projects from GitLab.com,
- if current_user.admin?
you need to
as administrator you need to configure
- else
your GitLab administrator needs to
== #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/gitlab.md'}.
\ No newline at end of file
ask your GitLab administrator to configure
== #{link_to 'OAuth integration', help_page_path("integration", "gitlab")}.
......@@ -12,8 +12,8 @@
\/
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
required: true, class: 'form-control new-file-name'
.pull-right
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
.pull-right
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
.file-content.code
%pre.js-edit-mode-pane#editor
......
......@@ -6,11 +6,12 @@
= render 'shared/commit_message_container', params: params,
placeholder: 'Add new file'
.form-group.branch
= label_tag 'branch', class: 'control-label' do
Branch
.col-sm-10
= text_field_tag 'new_branch', @ref, class: "form-control"
- unless @project.empty_repo?
.form-group.branch
= label_tag 'branch', class: 'control-label' do
Branch
.col-sm-10
= text_field_tag 'new_branch', @ref, class: "form-control"
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
......
......@@ -35,7 +35,7 @@
- if @merge_request.compare_failed
.alert.alert-danger
%h4 Compare failed
%p We can't compare selected branches. It may be because of huge diff or satellite timeout. Please try again or select different branches.
%p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches.
- else
.light-well
.center
......
......@@ -24,7 +24,7 @@
= icon('history')
Commits
%span.badge= @commits.size
%li.diffs-tab
%li.diffs-tab.active
= link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
= icon('list-alt')
Changes
......@@ -33,7 +33,7 @@
.tab-content
#commits.commits.tab-pane
= render "projects/commits/commits", project: @project
#diffs.diffs.tab-pane
#diffs.diffs.tab-pane.active
- if @diffs.present?
= render "projects/diffs/diffs", diffs: @diffs, project: @project
- elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
......
= render "projects/commits/commits", project: @merge_request.source_project
= render "projects/commits/commits", project: @merge_request.project
- if @merge_request_diff.collected?
= render "projects/diffs/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project
= render "projects/diffs/diffs", diffs: @merge_request.diffs, project: @merge_request.project
- elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
- else
......
......@@ -3,8 +3,6 @@
.mr-widget-body
- if @project.archived?
= render 'projects/merge_requests/widget/open/archived'
- elsif !@project.satellite.exists?
= render 'projects/merge_requests/widget/open/no_satellite'
- elsif @merge_request.commits.blank?
= render 'projects/merge_requests/widget/open/nothing'
- elsif @merge_request.branch_missing?
......
......@@ -11,10 +11,10 @@
var merge_request_widget;
merge_request_widget = new MergeRequestWidget({
url_to_automerge_check: "#{automerge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
url_to_automerge_check: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
check_enable: #{@merge_request.unchecked? ? "true" : "false"},
url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
ci_enable: #{@project.ci_service ? "true" : "false"},
current_status: "#{@merge_request.automerge_status}",
current_status: "#{@merge_request.gitlab_merge_status}",
});
= form_for [:automerge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f|
= form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f|
= hidden_field_tag :authenticity_token, form_authenticity_token
.accept-merge-holder.clearfix.js-toggle-container
.accept-action
......
%p
%span
%strong This repository does not have a satellite. Please ask an administrator to fix this issue!
......@@ -22,70 +22,75 @@
.col-sm-10
= f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2}
%hr
- if import_sources_enabled?
%hr
.project-import.js-toggle-container
.form-group
%label.control-label Import project from
.col-sm-10
- if github_import_enabled?
= link_to status_import_github_path, class: 'btn' do
%i.fa.fa-github
GitHub
- else
= link_to '#', class: 'how_to_import_link light btn' do
%i.fa.fa-github
GitHub
= render 'github_import_modal'
- if bitbucket_import_enabled?
= link_to status_import_bitbucket_path, class: 'btn', "data-no-turbolink" => "true" do
%i.fa.fa-bitbucket
Bitbucket
- else
= link_to '#', class: 'how_to_import_link light btn' do
%i.fa.fa-bitbucket
Bitbucket
= render 'bitbucket_import_modal'
- unless request.host == 'gitlab.com'
- if gitlab_import_enabled?
= link_to status_import_gitlab_path, class: 'btn' do
%i.fa.fa-heart
GitLab.com
- else
= link_to '#', class: 'how_to_import_link light btn' do
%i.fa.fa-heart
GitLab.com
= render 'gitlab_import_modal'
= link_to new_import_gitorious_path, class: 'btn' do
%i.icon-gitorious.icon-gitorious-small
Gitorious.org
= link_to new_import_google_code_path, class: 'btn' do
%i.fa.fa-google
Google Code
= link_to "#", class: 'btn js-toggle-button' do
%i.fa.fa-git
%span Any repo by URL
.js-toggle-content.hide
.form-group.import-url-data
= f.label :import_url, class: 'control-label' do
%span Git repository URL
.project-import.js-toggle-container
.form-group
%label.control-label Import project from
.col-sm-10
= f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
.well.prepend-top-20
%ul
%li
The repository must be accessible over HTTP(S). If it is not publicly accessible, you can add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.
%li
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
%li
To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
- if github_import_enabled?
- if github_import_configured?
= link_to status_import_github_path, class: 'btn import_github' do
%i.fa.fa-github
GitHub
- else
= link_to '#', class: 'how_to_import_link light btn import_github' do
%i.fa.fa-github
GitHub
= render 'github_import_modal'
- if bitbucket_import_enabled?
- if bitbucket_import_configured?
= link_to status_import_bitbucket_path, class: 'btn import_bitbucket', "data-no-turbolink" => "true" do
%i.fa.fa-bitbucket
Bitbucket
- else
= link_to status_import_bitbucket_path, class: 'how_to_import_link light btn import_bitbucket', "data-no-turbolink" => "true" do
%i.fa.fa-bitbucket
Bitbucket
= render 'bitbucket_import_modal'
- if gitlab_import_enabled?
- if gitlab_import_configured?
= link_to status_import_gitlab_path, class: 'btn import_gitlab' do
%i.fa.fa-heart
GitLab.com
- else
= link_to status_import_gitlab_path, class: 'how_to_import_link light btn import_gitlab' do
%i.fa.fa-heart
GitLab.com
= render 'gitlab_import_modal'
- if gitorious_import_enabled?
= link_to new_import_gitorious_path, class: 'btn import_gitorious' do
%i.icon-gitorious.icon-gitorious-small
Gitorious.org
- if google_code_import_enabled?
= link_to new_import_google_code_path, class: 'btn import_google_code' do
%i.fa.fa-google
Google Code
- if git_import_enabled?
= link_to "#", class: 'btn js-toggle-button import_git' do
%i.fa.fa-git
%span Any repo by URL
.js-toggle-content.hide
.form-group.import-url-data
= f.label :import_url, class: 'control-label' do
%span Git repository URL
.col-sm-10
= f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
.well.prepend-top-20
%ul
%li
The repository must be accessible over HTTP(S). If it is not publicly accessible, you can add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.
%li
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
%li
To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
%hr.prepend-botton-10
......
%ul.center-top-menu
= nav_link(page: user_snippets_path(current_user), html_options: {class: 'home'}) do
= link_to user_snippets_path(current_user), title: 'Your snippets', data: {placement: 'right'} do
Your Snippets
= nav_link(page: snippets_path) do
= link_to snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
Explore Snippets
- page_title "Your Snippets"
%h3.page-title
Your Snippets
.pull-right
= link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
Add new snippet
= render 'head'
%p.light
.slead
Share code pastes with others out of git repository
.pull-right
= link_to new_snippet_path, class: "btn btn-new btn-sm", title: "New Snippet" do
Add new snippet
%ul.nav.nav-tabs
= nav_tab :scope, nil do
= link_to user_snippets_path(@user) do
......
- page_title "Public Snippets"
%h3.page-title
Public snippets
- if current_user
= render 'head'
.pull-right
- if current_user
= link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
Add new snippet
= link_to user_snippets_path(current_user), class: "btn btn-grouped" do
Your snippets
%p.light
.slead
Public snippets created by you and other users are listed here
%hr
= render 'snippets'
- page_title @snippet.title, "Snippets"
%h3.page-title
%h4.page-title
= @snippet.title
- if @snippet.private?
......@@ -8,17 +8,14 @@
private
.pull-right
= link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
= link_to new_snippet_path, class: "btn btn-new btn-sm", title: "New Snippet" do
Add new snippet
%hr
.append-bottom-20
.append-bottom-10.prepend-top-10
.pull-right
= "##{@snippet.id}"
%span.light
by
created by
= link_to user_snippets_path(@snippet.author) do
= image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16", alt: ''
= @snippet.author_name
.back-link
......@@ -27,7 +24,7 @@
&larr; your snippets
- else
= link_to snippets_path do
&larr; discover snippets
&larr; explore snippets
.file-holder
.file-title
......
- page_title "Snippets", @user.name
%h3.page-title
= image_tag avatar_icon(@user.email), class: "avatar s24"
= @user.name
%span
\/
Snippets
- if current_user
= link_to new_snippet_path, class: "btn btn-sm add_new pull-right", title: "New Snippet" do
Add new snippet
%hr
%ol.breadcrumb
%li
= link_to snippets_path do
Snippets
%li
= @user.name
.pull-right.hidden-xs
= link_to user_path(@user) do
#{@user.name} profile page
= render 'snippets'
class AutoMergeWorker
class MergeWorker
include Sidekiq::Worker
sidekiq_options queue: :default
......@@ -7,7 +7,13 @@ class AutoMergeWorker
params = params.with_indifferent_access
current_user = User.find(current_user_id)
merge_request = MergeRequest.find(merge_request_id)
merge_request.should_remove_source_branch = params[:should_remove_source_branch]
merge_request.automerge!(current_user, params[:commit_message])
result = MergeRequests::MergeService.new(merge_request.target_project, current_user).
execute(merge_request, params[:commit_message])
if result[:status] == :success && params[:should_remove_source_branch].present?
DeleteBranchService.new(merge_request.source_project, current_user).
execute(merge_request.source_branch)
end
end
end
......@@ -27,7 +27,6 @@ class RepositoryImportWorker
project.import_finish
project.save
project.satellite.create unless project.satellite.exists?
ProjectCacheWorker.perform_async(project.id)
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
end
......
......@@ -148,6 +148,7 @@ Settings.gitlab.default_projects_features['snippets'] = false if Settings.
Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitlab['repository_downloads_path'] || 'tmp/repositories', Rails.root)
Settings.gitlab['restricted_signup_domains'] ||= []
Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','git']
#
# Reply by email
......
......@@ -463,8 +463,8 @@ Gitlab::Application.routes.draw do
member do
get :diffs
get :commits
post :automerge
get :automerge_check
post :merge
get :merge_check
get :ci_status
post :toggle_subscription
end
......
require 'yaml'
class AddSettingsImportSources < ActiveRecord::Migration
def change
unless column_exists?(:application_settings, :import_sources)
add_column :application_settings, :import_sources, :text
import_sources = YAML::dump(Settings.gitlab['import_sources'])
execute("update application_settings set import_sources = '#{import_sources}'")
end
end
end
......@@ -44,6 +44,7 @@ ActiveRecord::Schema.define(version: 20150818213832) do
t.boolean "user_oauth_applications", default: true
t.string "after_sign_out_path"
t.integer "session_expire_delay", default: 10080, null: false
t.text "import_sources"
end
create_table "audit_events", force: true do |t|
......
......@@ -56,9 +56,9 @@ To serve repositories over SSH there's an add-on application called gitlab-shell
A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs.
The GitLab web app uses MySQL or PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository. `/home/git/gitlab-satellites` keeps checked out repositories when performing actions such as a merge request, editing files in the web interface, etc.
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 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.
When serving repositories over HTTP/HTTPS GitLab utilizes the GitLab API to resolve authorization and access as well as serving git objects.
The add-on component gitlab-shell serves repositories over SSH. It manages the SSH keys within `/home/git/.ssh/authorized_keys` which should not be manually edited. gitlab-shell accesses the bare repositories directly to serve git objects and communicates with redis to submit jobs to Sidekiq for GitLab to process. gitlab-shell queries the GitLab API to determine authorization and access.
......@@ -129,7 +129,7 @@ Note: `/home/git/` is shorthand for `/home/git`.
gitlabhq (includes Unicorn and Sidekiq logs)
- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `githost.log`, `satellites.log`, and `unicorn.stderr.log` normally.
- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `githost.log` and `unicorn.stderr.log` normally.
gitlab-shell
......
......@@ -216,10 +216,6 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
sudo chmod -R u+rwX,go-w log/
sudo chmod -R u+rwX tmp/
# Create directory for satellites
sudo -u git -H mkdir /home/git/gitlab-satellites
sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites
# Make sure GitLab can write to the tmp/pids/ and tmp/sockets/ directories
sudo chmod -R u+rwX tmp/pids/
sudo chmod -R u+rwX tmp/sockets/
......
......@@ -40,7 +40,7 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
### Storage
The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least 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.
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.
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.
......@@ -113,4 +113,4 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
### Common UI problems with IE
If you experience UI issues with Internet Explorer, please make sure that you have the `Compatibility View` mode disabled.
If you experience UI issues with Internet Explorer, please make sure that you have the `Compatibility View` mode disabled.
\ No newline at end of file
......@@ -6,16 +6,14 @@ This is the directory structure you will end up with following the instructions
| |-- git
| |-- .ssh
| |-- gitlab
| |-- gitlab-satellites
| |-- gitlab-shell
| |-- repositories
* `/home/git/.ssh` - contains openssh settings. Specifically the `authorized_keys` file managed by gitlab-shell.
* `/home/git/gitlab` - GitLab core software.
* `/home/git/gitlab-satellites` - checked out repositories for merge requests and file editing from web UI. This can be treated as a temporary files directory.
* `/home/git/gitlab-shell` - Core add-on component of GitLab. Maintains SSH cloning and other functionality.
* `/home/git/repositories` - bare repositories for all projects organized by namespace. This is where the git repositories which are pushed/pulled are maintained for all projects. **This area is critical data for projects. [Keep a backup](../raketasks/backup_restore.md)**
*Note: the default locations for gitlab-satellites and repositories can be configured in `config/gitlab.yml` of GitLab and `config.yml` of gitlab-shell.*
*Note: the default locations for repositories can be configured in `config/gitlab.yml` of GitLab and `config.yml` of gitlab-shell.*
To see a more in-depth overview see the [GitLab architecture doc](../development/architecture.md).
......@@ -51,16 +51,6 @@ December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/
error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git'
```
#### satellites.log
This file lives in `/var/log/gitlab/gitlab-rails/satellites.log` for omnibus package or in `/home/git/gitlab/log/satellites.log` for installations from the source.
In some cases GitLab should perform write actions to git repository, for example when it is needed to merge the merge request or edit a file with online editor. If something went wrong you can look into this file to find out what exactly happened.
```
October 07, 2014 11:36: Failed to create satellite for Chesley Weimann III / project1817
October 07, 2014 11:36: PID: 1872: git clone /Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/repositories/conrad6841/gitlabhq.git /Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/conrad6841/gitlabhq
October 07, 2014 11:36: PID: 1872: -> fatal: repository '/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/repositories/conrad6841/gitlabhq.git' does not exist
```
#### sidekiq.log
This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for omnibus package or in `/home/git/gitlab/log/sidekiq.log` for installations from the source.
......@@ -99,4 +89,4 @@ W, [2015-02-13T07:16:01.313000 #9094] WARN -- : Unicorn::WorkerKiller send SIGQ
I, [2015-02-13T07:16:01.530733 #9047] INFO -- : reaped #<Process::Status: pid 9094 exit 0> worker=1
I, [2015-02-13T07:16:01.534501 #13379] INFO -- : worker=1 spawned pid=13379
I, [2015-02-13T07:16:01.534848 #13379] INFO -- : worker=1 ready
```
\ No newline at end of file
```
......@@ -8,6 +8,10 @@ your phone.
By enabling 2FA, the only way someone other than you can log into your account
is to know your username and password *and* have access to your phone.
#### Note
When you enable 2FA, don't forget to back up your recovery codes. For your safety, if you
lose your codes for GitLab.com, we can't disable or recover them.
## Enabling 2FA
**In GitLab:**
......
......@@ -105,24 +105,11 @@ Log directory writable? ... yes
Tmp directory writable? ... yes
Init script exists? ... yes
Init script up-to-date? ... yes
Projects have satellites? ... yes
Redis version >= 2.0.0? ... yes
Checking GitLab ... Finished
```
## (Re-)Create satellite repositories
This will create satellite repositories for all your projects.
If necessary, remove the `repo_satellites` directory and rerun the commands below.
```
sudo -u git -H mkdir -p /home/git/gitlab-satellites
sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production
sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites
```
## Rebuild authorized_keys file
In some case it is necessary to rebuild the `authorized_keys` file.
......
......@@ -156,6 +156,7 @@ Tweet about the RC release:
1. Also check the CI changelog
1. Add a proposed tweet text to the blog post WIP MR description.
1. Create a WIP MR for the blog post
1. Make sure merge request title starts with `WIP` so it can not be accidently merged until ready.
1. Ask Dmitriy (or a team member with OS X) to add screenshots to the WIP MR.
1. Decide with core team who will be the MVP user.
1. Create WIP MR for adding MVP to MVP page on website
......
......@@ -13,4 +13,5 @@
- [Project users](add-user/add-user.md)
- [Protected branches](protected_branches.md)
- [Web Editor](web_editor.md)
- [Merge Requests](merge_requests.md)
- ["Work In Progress" Merge Requests](wip_merge_requests.md)
# 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
```
......@@ -4,9 +4,18 @@ Feature: Admin Projects
Given I sign in as an admin
And there are projects in system
Scenario: Projects list
Scenario: I should see non-archived projects in the list
Given archived project "Archive"
When I visit admin projects page
Then I should see all non-archived projects
And I should not see project "Archive"
Scenario: I should see all projects in the list
Given archived project "Archive"
When I visit admin projects page
And I check "Show archived projects"
Then I should see all projects
And I should see "archived" label
Scenario: Projects show
When I visit admin projects page
......
......@@ -4,10 +4,27 @@ Background:
Given I sign in as a user
And I own project "Shop"
And I visit dashboard page
And I click "New project" link
@javascript
Scenario: I should see New projects page
Given I click "New project" link
Then I see "New project" page
Then I see all possible import optios
@javascript
Scenario: I should see instructions on how to import from Git URL
Given I see "New project" page
When I click on "Any repo by URL"
Then I see instructions on how to import from Git URL
@javascript
Scenario: I should see instructions on how to import from GitHub
Given I see "New project" page
When I click on "Import project from GitHub"
Then I see instructions on how to import from GitHub
@javascript
Scenario: I should see Google Code import page
Given I see "New project" page
When I click on "Google Code"
Then I redirected to Google Code import page
......@@ -6,10 +6,12 @@ Feature: Explore Projects
And private project "Enterprise"
Scenario: I visit public area
Given archived project "Archive"
When I visit the public projects area
Then I should see project "Community"
And I should not see project "Internal"
And I should not see project "Enterprise"
And I should not see project "Archive"
Scenario: I visit public project page
When I visit project "Community" page
......@@ -37,11 +39,13 @@ Feature: Explore Projects
And I should see empty public project details with ssh clone info
Scenario: I visit public area as user
Given I sign in as a user
Given archived project "Archive"
And I sign in as a user
When I visit the public projects area
Then I should see project "Community"
And I should see project "Internal"
And I should not see project "Enterprise"
And I should not see project "Archive"
Scenario: I visit internal project page as user
Given I sign in as a user
......@@ -102,15 +106,20 @@ Feature: Explore Projects
Then I should see list of merge requests for "Internal" project
Scenario: Trending page
Given I sign in as a user
Given archived project "Archive"
And project "Archive" has comments
And I sign in as a user
And project "Community" has comments
When I visit the explore trending projects
Then I should see project "Community"
And I should not see project "Internal"
And I should not see project "Enterprise"
And I should not see project "Archive"
Scenario: Most starred page
Given I sign in as a user
Given archived project "Archive"
And I sign in as a user
When I visit the explore starred projects
Then I should see project "Community"
And I should see project "Internal"
And I should see project "Archive"
......@@ -152,3 +152,10 @@ Feature: Groups
And I click on one group milestone
Then I should see group milestone with descriptions and expiry date
And I should see group milestone with all issues and MRs assigned to that milestone
# Group projects in settings
Scenario: I should see all projects in the project list in settings
Given Group "Owned" has archived project
When I visit group "Owned" projects page
Then I should see group "Owned" projects list
And I should see "archived" label
......@@ -2,6 +2,13 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedAdmin
include SharedProject
step 'I should see all non-archived projects' do
Project.non_archived.each do |p|
expect(page).to have_content p.name_with_namespace
end
end
step 'I should see all projects' do
Project.all.each do |p|
......@@ -9,6 +16,15 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps
end
end
step 'I check "Show archived projects"' do
page.check 'Show archived projects'
click_button "Search"
end
step 'I should see "archived" label' do
expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived')
end
step 'I click on first project' do
click_link Project.first.name_with_namespace
end
......
......@@ -16,6 +16,6 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
end
step 'Header "Rebuild project satellites" should have correct ids and links' do
header_should_have_correct_id_and_link(2, '(Re-)Create satellite repositories', 're-create-satellite-repositories', '.documentation')
header_should_have_correct_id_and_link(2, 'Check GitLab configuration', 'check-gitlab-configuration', '.documentation')
end
end
......@@ -66,7 +66,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
def authored_merge_request
@authored_merge_request ||= create :merge_request,
source_branch: 'simple_merge_request',
source_branch: 'markdown',
author: current_user,
target_project: project,
source_project: project
......@@ -74,14 +74,14 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
def other_merge_request
@other_merge_request ||= create :merge_request,
source_branch: '2_3_notes_fix',
source_branch: 'fix',
target_project: project,
source_project: project
end
def authored_merge_request_from_fork
@authored_merge_request_from_fork ||= create :merge_request,
source_branch: 'basic_page',
source_branch: 'feature_conflict',
author: current_user,
target_project: public_project,
source_project: forked_project
......@@ -89,7 +89,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
def assigned_merge_request_from_fork
@assigned_merge_request_from_fork ||= create :merge_request,
source_branch: 'basic_page_fix',
source_branch: 'markdown',
assignee: current_user,
target_project: public_project,
source_project: forked_project
......
......@@ -13,8 +13,17 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
expect(page).to have_content('Project path')
end
step 'I see all possible import optios' do
expect(page).to have_link('GitHub')
expect(page).to have_link('Bitbucket')
expect(page).to have_link('GitLab.com')
expect(page).to have_link('Gitorious.org')
expect(page).to have_link('Google Code')
expect(page).to have_link('Any repo by URL')
end
step 'I click on "Import project from GitHub"' do
first('.how_to_import_link').click
first('.import_github').click
end
step 'I see instructions on how to import from GitHub' do
......@@ -26,4 +35,24 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
expect(element).not_to be_visible unless element == github_modal
end
end
step 'I click on "Any repo by URL"' do
first('.import_git').click
end
step 'I see instructions on how to import from Git URL' do
git_import_instructions = first('.js-toggle-content')
expect(git_import_instructions).to be_visible
expect(git_import_instructions).to have_content "Git repository URL"
expect(git_import_instructions).to have_content "The repository must be accessible over HTTP(S). If it is not publicly accessible, you can add authentication information to the URL:"
end
step 'I click on "Google Code"' do
first('.import_google_code').click
end
step 'I redirected to Google Code import page' do
expect(current_path).to eq new_import_google_code_path
end
end
......@@ -226,6 +226,15 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
expect(page).to have_link(@mr3.title, href: namespace_project_merge_request_path(@project3.namespace, @project3, @mr3))
end
step 'Group "Owned" has archived project' do
group = Group.find_by(name: 'Owned')
create(:project, namespace: group, archived: true, path: "archived-project")
end
step 'I should see "archived" label' do
expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived')
end
protected
def assigned_to_me(key)
......
......@@ -9,7 +9,6 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
@project = Project.find_by(name: "Shop")
@project ||= create(:project, name: "Shop")
@project.team << [@user, :reporter]
@project.ensure_satellite_exists
end
step 'I have a project forked off of "Shop" called "Forked Shop"' do
......
......@@ -198,15 +198,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'merge request "Bug NS-05" is mergeable' do
merge_request.project.satellite.create
merge_request.mark_as_mergeable
end
step 'I accept this merge request' do
Gitlab::Satellite::MergeAction.any_instance.stub(
merge!: true,
)
page.within '.mr-state-widget' do
click_button "Accept Merge Request"
end
......
......@@ -26,7 +26,7 @@ module SharedActiveTab
end
step 'the active main tab should be Home' do
ensure_active_main_tab('Your Projects')
ensure_active_main_tab('Projects')
end
step 'the active main tab should be Projects' do
......
......@@ -39,6 +39,10 @@ module SharedPaths
visit edit_group_path(Group.find_by(name: "Owned"))
end
step 'I visit group "Owned" projects page' do
visit projects_group_path(Group.find_by(name: "Owned"))
end
step 'I visit group "Guest" page' do
visit group_path(Group.find_by(name: "Guest"))
end
......
......@@ -92,6 +92,29 @@ module SharedProject
@project ||= Project.first
end
# ----------------------------------------
# Visibility of archived project
# ----------------------------------------
step 'archived project "Archive"' do
create :project, :public, archived: true, name: 'Archive'
end
step 'I should not see project "Archive"' do
project = Project.find_by(name: "Archive")
expect(page).not_to have_content project.name_with_namespace
end
step 'I should see project "Archive"' do
project = Project.find_by(name: "Archive")
expect(page).to have_content project.name_with_namespace
end
step 'project "Archive" has comments' do
project = Project.find_by(name: "Archive")
2.times { create(:note_on_issue, project: project) }
end
# ----------------------------------------
# Visibility level
# ----------------------------------------
......
......@@ -28,5 +28,9 @@ Spinach.hooks.before_run do
RSpec::Mocks.setup
TestEnv.init(mailer: false)
# skip pre-receive hook check so we can use
# web editor and merge
TestEnv.disable_pre_receive
include FactoryGirl::Syntax::Methods
end
......@@ -221,6 +221,7 @@ module API
expose(:line) { |note| note.diff_new_line }
expose(:line_type) { |note| note.diff_line_type }
expose :author, using: Entities::UserBasic
expose :created_at
end
class Event < Grape::Entity
......
......@@ -3,6 +3,26 @@ module API
class Files < Grape::API
before { authenticate! }
helpers do
def commit_params(attrs)
{
file_path: attrs[:file_path],
current_branch: attrs[:branch_name],
target_branch: attrs[:branch_name],
commit_message: attrs[:commit_message],
file_content: attrs[:content],
file_content_encoding: attrs[:encoding]
}
end
def commit_response(attrs)
{
file_path: attrs[:file_path],
branch_name: attrs[:branch_name],
}
end
end
resource :projects do
# Get file from repository
# File content is Base64 encoded
......@@ -73,17 +93,11 @@ module API
required_attributes! [:file_path, :branch_name, :content, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
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
result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success
status(201)
{
file_path: file_path,
branch_name: branch_name
}
commit_response(attrs)
else
render_api_error!(result[:message], 400)
end
......@@ -105,17 +119,11 @@ module API
required_attributes! [:file_path, :branch_name, :content, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
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
result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success
status(200)
{
file_path: file_path,
branch_name: branch_name
}
commit_response(attrs)
else
http_status = result[:http_status] || 400
render_api_error!(result[:message], http_status)
......@@ -138,17 +146,11 @@ module API
required_attributes! [:file_path, :branch_name, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :commit_message]
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
result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success
status(200)
{
file_path: file_path,
branch_name: branch_name
}
commit_response(attrs)
else
render_api_error!(result[:message], 400)
end
......
......@@ -198,7 +198,11 @@ module API
if merge_request.open? && !merge_request.work_in_progress?
if merge_request.can_be_merged?
merge_request.automerge!(current_user, params[:merge_commit_message] || merge_request.merge_commit_message)
commit_message = params[:merge_commit_message] || merge_request.merge_commit_message
::MergeRequests::MergeService.new(merge_request.target_project, current_user).
execute(merge_request, commit_message)
present merge_request, with: Entities::MergeRequest
else
render_api_error!('Branch cannot be merged', 405)
......
require 'gitlab/git'
module Gitlab
autoload :Satellite, 'gitlab/satellite/satellite'
end
......@@ -217,20 +217,6 @@ module Gitlab
FileUtils.mv(full_path(old_name), full_path(new_name))
end
# Remove GitLab Satellites for provided path (namespace or repo dir)
#
# Ex.
# rm_satellites("gitlab")
#
# rm_satellites("gitlab/gitlab-ci.git")
#
def rm_satellites(path)
raise ArgumentError.new("Path can't be blank") if path.blank?
satellites_path = File.join(Gitlab.config.satellites.path, path)
FileUtils.rm_r(satellites_path, force: true)
end
def url_to_repo(path)
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
end
......
......@@ -22,7 +22,8 @@ module Gitlab
sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
session_expire_delay: Settings.gitlab['session_expire_delay']
session_expire_delay: Settings.gitlab['session_expire_delay'],
import_sources: Settings.gitlab['import_sources']
)
end
end
......
module Gitlab
module Git
class Hook
attr_reader :name, :repo_path, :path
def initialize(name, repo_path)
@name = name
@repo_path = repo_path
@path = File.join(repo_path.strip, 'hooks', name)
end
def exists?
File.exist?(path)
end
def trigger(gl_id, oldrev, newrev, ref)
return true unless exists?
changes = [oldrev, newrev, ref].join(" ")
# function will return true if succesful
exit_status = false
vars = {
'GL_ID' => gl_id,
'PWD' => repo_path
}
options = {
chdir: repo_path
}
Open3.popen2(vars, path, options) do |stdin, _, 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
stdin.close
unless wait_thr.value == 0
exit_status = false
end
end
exit_status
end
end
end
end
# Gitlab::ImportSources module
#
# Define import sources that can be used
# during the creation of new project
#
module Gitlab
module ImportSources
extend CurrentSettings
class << self
def values
options.values
end
def options
{
'GitHub' => 'github',
'Bitbucket' => 'bitbucket',
'GitLab.com' => 'gitlab',
'Gitorious.org' => 'gitorious',
'Google Code' => 'google_code',
'Any repo by URL' => 'git',
}
end
end
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}")
merge_request.source_project.repository.expire_branch_names
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,7 +25,6 @@ namespace :gitlab do
check_init_script_exists
check_init_script_up_to_date
check_projects_have_namespace
check_satellites_exist
check_redis_version
check_ruby_version
check_git_version
......@@ -238,37 +237,6 @@ namespace :gitlab do
end
end
def check_satellites_exist
print "Projects have satellites? ... "
unless Project.count > 0
puts "can't check, you have no projects".magenta
return
end
puts ""
Project.find_each(batch_size: 100) do |project|
print sanitized_message(project)
if project.satellite.exists?
puts "yes".green
elsif project.empty_repo?
puts "can't create, repository is empty".magenta
else
puts "no".red
try_fixing_it(
sudo_gitlab("bundle exec rake gitlab:satellites:create RAILS_ENV=production"),
"If necessary, remove the tmp/repo_satellites directory ...",
"... and rerun the above command"
)
for_more_information(
"doc/raketasks/maintenance.md "
)
fix_and_rerun
end
end
end
def check_log_writable
print "Log directory writable? ... "
......@@ -339,7 +307,6 @@ namespace :gitlab do
check_repo_base_is_not_symlink
check_repo_base_user_and_group
check_repo_base_permissions
check_satellites_permissions
check_repos_hooks_directory_is_link
check_gitlab_shell_self_test
......@@ -417,29 +384,6 @@ namespace :gitlab do
end
end
def check_satellites_permissions
print "Satellites access is drwxr-x---? ... "
satellites_path = Gitlab.config.satellites.path
unless File.exists?(satellites_path)
puts "can't check because of previous errors".magenta
return
end
if File.stat(satellites_path).mode.to_s(8).ends_with?("0750")
puts "yes".green
else
puts "no".red
try_fixing_it(
"sudo chmod u+rwx,g=rx,o-rwx #{satellites_path}",
)
for_more_information(
see_installation_guide_section "GitLab"
)
fix_and_rerun
end
end
def check_repo_base_user_and_group
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group
......
namespace :gitlab do
namespace :satellites do
desc "GitLab | Create satellite repos"
task create: :environment do
create_satellites
end
end
def create_satellites
warn_user_is_not_gitlab
print "Creating satellites for ..."
unless Project.count > 0
puts "skipping, because you have no projects".magenta
return
end
puts ""
Project.find_each(batch_size: 100) do |project|
print "#{project.name_with_namespace.yellow} ... "
unless project.repo_exists?
puts "skipping, because the repo is empty".magenta
next
end
if project.satellite.exists?
puts "exists already".green
else
print "\n... "
if project.satellite.create
puts "created".green
else
puts "error".red
end
end
end
end
end
......@@ -25,7 +25,7 @@ describe Gitlab::ReferenceExtractor do
project.team << [@u_bar, :guest]
subject.analyze(%Q{
Inline code: `@foo`
Inline code: `@foo`
Code block:
......@@ -33,7 +33,7 @@ describe Gitlab::ReferenceExtractor do
@bar
```
Quote:
Quote:
> @offteam
})
......@@ -49,8 +49,8 @@ describe Gitlab::ReferenceExtractor do
end
it 'accesses valid merge requests' do
@m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'aaa')
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb')
@m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'markdown')
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'feature_conflict')
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.")
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
describe '#merge!' do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, source_branch: "markdown", should_remove_source_branch: true) }
let(:merge_action) { Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request) }
it 'clears cache of source repo after removing source branch' do
project.repository.expire_branch_names
expect(project.repository.branch_names).to include('markdown')
merge_action.merge!
expect(project.repository.branch_names).not_to include('markdown')
end
end
end
......@@ -165,7 +165,7 @@ describe MergeRequest do
end
it_behaves_like 'an editable mentionable' do
subject { create(:merge_request, source_project: project) }
subject { create(:merge_request) }
let(:backref_text) { "merge request #{subject.to_reference}" }
let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
......
......@@ -67,7 +67,7 @@ describe SlackService do
opts = {
title: 'Awesome merge_request',
description: 'please fix',
source_branch: 'stable',
source_branch: 'feature',
target_branch: 'master'
}
merge_service = MergeRequests::CreateService.new(project,
......
......@@ -91,7 +91,6 @@ describe Project do
describe 'Respond to' do
it { is_expected.to respond_to(:url_to_repo) }
it { is_expected.to respond_to(:repo_exists?) }
it { is_expected.to respond_to(:satellite) }
it { is_expected.to respond_to(:update_merge_requests) }
it { is_expected.to respond_to(:execute_hooks) }
it { is_expected.to respond_to(:name_with_namespace) }
......
......@@ -40,6 +40,20 @@ describe Repository do
it { is_expected.to be_truthy }
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
context 'non merged branch' do
subject { repository.merged_to_root_ref?('fix') }
......
......@@ -49,8 +49,6 @@ describe API::API, api: true do
end
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
expect(response.status).to eq(201)
expect(json_response['file_path']).to eq('newfile.rb')
......@@ -61,8 +59,9 @@ describe API::API, api: true do
expect(response.status).to eq(400)
end
it "should return a 400 if satellite fails to create file" do
expect_any_instance_of(Gitlab::Satellite::NewFileAction).to receive(:commit!).and_return(false)
it "should return a 400 if editor fails to create file" do
allow_any_instance_of(Repository).to receive(:commit_file).
and_return(false)
post api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(400)
......@@ -80,8 +79,6 @@ describe API::API, api: true do
end
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
expect(response.status).to eq(200)
expect(json_response['file_path']).to eq(file_path)
......@@ -91,32 +88,6 @@ describe API::API, api: true do
put api("/projects/#{project.id}/repository/files", user)
expect(response.status).to eq(400)
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
describe "DELETE /projects/:id/repository/files" do
......@@ -129,7 +100,6 @@ describe API::API, api: true do
end
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
expect(response.status).to eq(200)
expect(json_response['file_path']).to eq(file_path)
......@@ -140,8 +110,8 @@ describe API::API, api: true do
expect(response.status).to eq(400)
end
it "should return a 400 if satellite fails to create file" do
expect_any_instance_of(Gitlab::Satellite::DeleteFileAction).to receive(:commit!).and_return(false)
it "should return a 400 if fails to create file" do
allow_any_instance_of(Repository).to receive(:remove_file).and_return(false)
delete api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(400)
......
......@@ -148,7 +148,7 @@ describe API::API, api: true do
it "should return merge_request" do
post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request',
source_branch: 'stable',
source_branch: 'feature_conflict',
target_branch: 'master',
author: user,
labels: 'label, label2'
......@@ -171,20 +171,20 @@ describe API::API, api: true do
it "should return 400 when target_branch is missing" do
post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", source_branch: "stable", author: user
title: "Test merge_request", source_branch: "markdown", author: user
expect(response.status).to eq(400)
end
it "should return 400 when title is missing" do
post api("/projects/#{project.id}/merge_requests", user),
target_branch: 'master', source_branch: 'stable'
target_branch: 'master', source_branch: 'markdown'
expect(response.status).to eq(400)
end
it 'should return 400 on invalid label names' do
post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request',
source_branch: 'stable',
source_branch: 'markdown',
target_branch: 'master',
author: user,
labels: 'label, ?'
......@@ -198,7 +198,7 @@ describe API::API, api: true do
before do
post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request',
source_branch: 'stable',
source_branch: 'feature_conflict',
target_branch: 'master',
author: user
@mr = MergeRequest.all.last
......@@ -208,7 +208,7 @@ describe API::API, api: true do
expect do
post api("/projects/#{project.id}/merge_requests", user),
title: 'New test merge_request',
source_branch: 'stable',
source_branch: 'feature_conflict',
target_branch: 'master',
author: user
end.to change { MergeRequest.count }.by(0)
......@@ -228,7 +228,8 @@ describe API::API, api: true do
it "should return merge_request" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
expect(response.status).to eq(201)
expect(json_response['title']).to eq('Test merge_request')
expect(json_response['description']).to eq('Test description for Test merge_request')
......@@ -258,7 +259,7 @@ describe API::API, api: true do
it "should return 400 when title is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: project.id
target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id
expect(response.status).to eq(400)
end
......@@ -267,7 +268,7 @@ describe API::API, api: true do
post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request',
target_branch: 'master',
source_branch: 'stable',
source_branch: 'markdown',
author: user,
target_project_id: fork_project.id
expect(response.status).to eq(422)
......@@ -277,7 +278,7 @@ describe API::API, api: true do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request',
target_branch: 'master',
source_branch: 'stable',
source_branch: 'markdown',
author: user2,
target_project_id: unrelated_project.id
expect(response.status).to eq(422)
......@@ -286,7 +287,7 @@ describe API::API, api: true 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),
title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: fork_project.id
title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id
expect(response.status).to eq(201)
end
end
......@@ -302,9 +303,6 @@ describe API::API, api: true do
describe "PUT /projects/:id/merge_request/:merge_request_id/merge" 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)
expect(response.status).to eq(200)
......
......@@ -210,8 +210,8 @@ end
# 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
# automerge_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/automerge(.:format) projects/merge_requests#automerge
# automerge_check_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/automerge_check(.:format) projects/merge_requests#automerge_check
# merge_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/merge(.:format) projects/merge_requests#merge
# merge_check_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/merge_check(.:format) projects/merge_requests#merge_check
# 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
# 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
expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
it 'to #automerge' do
expect(post('/gitlab/gitlabhq/merge_requests/1/automerge')).to route_to(
'projects/merge_requests#automerge',
it 'to #merge' do
expect(post('/gitlab/gitlabhq/merge_requests/1/merge')).to route_to(
'projects/merge_requests#merge',
namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1'
)
end
it 'to #automerge_check' do
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')
it 'to #merge_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')
end
it 'to #branch_from' do
......
......@@ -10,7 +10,7 @@ describe MergeRequests::CreateService do
{
title: 'Awesome merge_request',
description: 'please fix',
source_branch: 'stable',
source_branch: 'feature',
target_branch: 'master'
}
end
......
......@@ -24,11 +24,6 @@ describe MergeRequests::MergeService do
it { expect(merge_request).to be_valid }
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
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
......
......@@ -9,7 +9,7 @@ def common_mentionable_setup
let(:author) { subject.author }
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(:ext_proj) { create(:project, :public) }
......
......@@ -57,6 +57,10 @@ module TestEnv
and_call_original
end
def disable_pre_receive
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true)
end
# Clean /tmp/tests
#
# Keeps gitlab-shell and gitlab-test
......
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