gitlab-org--gitlab-foss/app/services/git_operation_service.rb
2017-01-05 01:10:35 +08:00

172 lines
5 KiB
Ruby

class GitOperationService
attr_reader :user, :repository
def initialize(new_user, new_repository)
@user = new_user
@repository = new_repository
end
def add_branch(branch_name, newrev)
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
oldrev = Gitlab::Git::BLANK_SHA
update_ref_in_hooks(ref, newrev, oldrev)
end
def rm_branch(branch)
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name
oldrev = branch.dereferenced_target.id
newrev = Gitlab::Git::BLANK_SHA
update_ref_in_hooks(ref, newrev, oldrev)
end
def add_tag(tag_name, newrev, options = {})
ref = Gitlab::Git::TAG_REF_PREFIX + tag_name
oldrev = Gitlab::Git::BLANK_SHA
with_hooks(ref, newrev, oldrev) do |service|
# We want to pass the OID of the tag object to the hooks. For an
# annotated tag we don't know that OID until after the tag object
# (raw_tag) is created in the repository. That is why we have to
# update the value after creating the tag object. Only the
# "post-receive" hook will receive the correct value in this case.
raw_tag = repository.rugged.tags.create(tag_name, newrev, options)
service.newrev = raw_tag.target_id
end
end
# Whenever `source_branch_name` is passed, if `branch_name` doesn't exist,
# it would be created from `source_branch_name`.
# If `source_project` is passed, and the branch doesn't exist,
# it would try to find the source from it instead of current repository.
def with_branch(
branch_name,
source_branch_name: nil,
source_project: repository.project)
check_with_branch_arguments!(
branch_name, source_branch_name, source_project)
source_commit = source_project.repository.find_branch(
source_branch_name || branch_name).try(:dereferenced_target)
update_branch_with_hooks(branch_name) do
if repository.project == source_project
yield(source_commit)
else
repository.with_tmp_ref(
source_project.repository, source_branch_name) do
yield(source_commit)
end
end
end
end
private
def update_branch_with_hooks(branch_name)
update_autocrlf_option
was_empty = repository.empty?
# Make commit
newrev = yield
unless newrev
raise Repository::CommitError.new('Failed to create commit')
end
branch = repository.find_branch(branch_name)
oldrev = if branch
# This could verify we're not losing commits
repository.rugged.merge_base(newrev, branch.target)
else
Gitlab::Git::BLANK_SHA
end
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
update_ref_in_hooks(ref, newrev, oldrev)
# If repo was empty expire cache
repository.after_create if was_empty
repository.after_create_branch if
was_empty || Gitlab::Git.blank_ref?(oldrev)
newrev
end
def update_ref_in_hooks(ref, newrev, oldrev)
with_hooks(ref, newrev, oldrev) do
update_ref(ref, newrev, oldrev)
end
end
def with_hooks(ref, newrev, oldrev)
result = nil
GitHooksService.new.execute(
user,
repository.path_to_repo,
oldrev,
newrev,
ref) do |service|
result = yield(service) if block_given?
end
result
end
def update_ref(ref, newrev, oldrev)
# We use 'git update-ref' because libgit2/rugged currently does not
# offer 'compare and swap' ref updates. Without compare-and-swap we can
# (and have!) accidentally reset the ref to an earlier state, clobbering
# commits. See also https://github.com/libgit2/libgit2/issues/1534.
command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]
_, status = Gitlab::Popen.popen(
command,
repository.path_to_repo) do |stdin|
stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00")
end
unless status.zero?
raise Repository::CommitError.new(
"Could not update branch #{Gitlab::Git.branch_name(ref)}." \
" Please refresh and try again.")
end
end
def update_autocrlf_option
if repository.raw_repository.autocrlf != :input
repository.raw_repository.autocrlf = :input
end
end
def check_with_branch_arguments!(
branch_name, source_branch_name, source_project)
return if repository.branch_exists?(branch_name)
if repository.project != source_project
unless source_branch_name
raise ArgumentError,
'Should also pass :source_branch_name if' +
' :source_project is different from current project'
end
unless source_project.repository.branch_exists?(source_branch_name)
raise ArgumentError,
"Cannot find branch #{branch_name} nor" \
" #{source_branch_name} from" \
" #{source_project.path_with_namespace}"
end
elsif source_branch_name
unless repository.branch_exists?(source_branch_name)
raise ArgumentError,
"Cannot find branch #{branch_name} nor" \
" #{source_branch_name} from" \
" #{repository.project.path_with_namespace}"
end
end
end
end