189 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			189 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Ruby
		
	
	
	
| module Gitlab
 | |
|   module Git
 | |
|     class OperationService
 | |
|       include Gitlab::Git::Popen
 | |
| 
 | |
|       BranchUpdate = Struct.new(:newrev, :repo_created, :branch_created) do
 | |
|         alias_method :repo_created?, :repo_created
 | |
|         alias_method :branch_created?, :branch_created
 | |
| 
 | |
|         def self.from_gitaly(branch_update)
 | |
|           new(
 | |
|             branch_update.commit_id,
 | |
|             branch_update.repo_created,
 | |
|             branch_update.branch_created
 | |
|           )
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       attr_reader :user, :repository
 | |
| 
 | |
|       def initialize(user, new_repository)
 | |
|         if user
 | |
|           user = Gitlab::Git::User.from_gitlab(user) unless user.respond_to?(:gl_id)
 | |
|           @user = user
 | |
|         end
 | |
| 
 | |
|         # Refactoring aid
 | |
|         Gitlab::Git.check_namespace!(new_repository)
 | |
| 
 | |
|         @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.target
 | |
|         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
 | |
| 
 | |
|       def rm_tag(tag)
 | |
|         ref = Gitlab::Git::TAG_REF_PREFIX + tag.name
 | |
|         oldrev = tag.target
 | |
|         newrev = Gitlab::Git::BLANK_SHA
 | |
| 
 | |
|         update_ref_in_hooks(ref, newrev, oldrev) do
 | |
|           repository.rugged.tags.delete(tag_name)
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       # Whenever `start_branch_name` is passed, if `branch_name` doesn't exist,
 | |
|       # it would be created from `start_branch_name`.
 | |
|       # If `start_repository` is passed, and the branch doesn't exist,
 | |
|       # it would try to find the commits from it instead of current repository.
 | |
|       def with_branch(
 | |
|         branch_name,
 | |
|         start_branch_name: nil,
 | |
|         start_repository: repository,
 | |
|         &block)
 | |
| 
 | |
|         Gitlab::Git.check_namespace!(start_repository)
 | |
|         start_repository = RemoteRepository.new(start_repository) unless start_repository.is_a?(RemoteRepository)
 | |
| 
 | |
|         start_branch_name = nil if start_repository.empty?
 | |
| 
 | |
|         if start_branch_name && !start_repository.branch_exists?(start_branch_name)
 | |
|           raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.relative_path}"
 | |
|         end
 | |
| 
 | |
|         update_branch_with_hooks(branch_name) do
 | |
|           repository.with_repo_branch_commit(
 | |
|             start_repository,
 | |
|             start_branch_name || branch_name,
 | |
|             &block)
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def update_branch(branch_name, newrev, oldrev)
 | |
|         ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
 | |
|         update_ref_in_hooks(ref, newrev, oldrev)
 | |
|       end
 | |
| 
 | |
|       private
 | |
| 
 | |
|       # Returns [newrev, should_run_after_create, should_run_after_create_branch]
 | |
|       def update_branch_with_hooks(branch_name)
 | |
|         update_autocrlf_option
 | |
| 
 | |
|         was_empty = repository.empty?
 | |
| 
 | |
|         # Make commit
 | |
|         newrev = yield
 | |
| 
 | |
|         unless newrev
 | |
|           raise Gitlab::Git::CommitError.new('Failed to create commit')
 | |
|         end
 | |
| 
 | |
|         branch = repository.find_branch(branch_name)
 | |
|         oldrev = find_oldrev_from_branch(newrev, branch)
 | |
| 
 | |
|         ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
 | |
|         update_ref_in_hooks(ref, newrev, oldrev)
 | |
| 
 | |
|         BranchUpdate.new(newrev, was_empty, was_empty || Gitlab::Git.blank_ref?(oldrev))
 | |
|       end
 | |
| 
 | |
|       def find_oldrev_from_branch(newrev, branch)
 | |
|         return Gitlab::Git::BLANK_SHA unless branch
 | |
| 
 | |
|         oldrev = branch.target
 | |
| 
 | |
|         if oldrev == repository.merge_base(newrev, branch.target)
 | |
|           oldrev
 | |
|         else
 | |
|           raise Gitlab::Git::CommitError.new('Branch diverged')
 | |
|         end
 | |
|       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)
 | |
|         Gitlab::Git::HooksService.new.execute(
 | |
|           user,
 | |
|           repository,
 | |
|           oldrev,
 | |
|           newrev,
 | |
|           ref) do |service|
 | |
| 
 | |
|           yield(service)
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       # Gitaly note: JV: wait with migrating #update_ref until we know how to migrate its call sites.
 | |
|       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]
 | |
| 
 | |
|         output, status = popen(
 | |
|           command,
 | |
|           repository.path) do |stdin|
 | |
|           stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00")
 | |
|         end
 | |
| 
 | |
|         unless status.zero?
 | |
|           Gitlab::GitLogger.error("'git update-ref' in #{repository.path}: #{output}")
 | |
|           raise Gitlab::Git::CommitError.new(
 | |
|             "Could not update branch #{Gitlab::Git.branch_name(ref)}." \
 | |
|             " Please refresh and try again.")
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def update_autocrlf_option
 | |
|         if repository.autocrlf != :input
 | |
|           repository.autocrlf = :input
 | |
|         end
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 |