repository.rb 19.7 KB
Newer Older
1 2
require 'securerandom'

3
class Repository
4 5
  class CommitError < StandardError; end

6 7
  include Gitlab::ShellAdapter

8
  attr_accessor :path_with_namespace, :project
9

J
Jacob Vosmaer 已提交
10 11 12 13 14 15 16 17
  def self.clean_old_archives
    repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path

    return unless File.directory?(repository_downloads_path)

    Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
  end

18
  def initialize(path_with_namespace, project)
19
    @path_with_namespace = path_with_namespace
20
    @project = project
21
  end
22

23 24
  def raw_repository
    return nil unless path_with_namespace
25

26
    @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
27 28
  end

29 30 31 32
  def update_autocrlf_option
    raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
  end

33
  # Return absolute path to repository
34
  def path_to_repo
35 36 37
    @path_to_repo ||= File.expand_path(
      File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git")
    )
38 39
  end

40
  def exists?
41 42
    return false unless raw_repository

43 44 45 46
    raw_repository.rugged
    true
  rescue Gitlab::Git::Repository::NoRepository
    false
47 48 49
  end

  def empty?
50 51 52
    return @empty unless @empty.nil?

    @empty = cache.fetch(:empty?) { raw_repository.empty? }
53 54
  end

55 56 57 58 59 60 61 62 63 64
  #
  # Git repository can contains some hidden refs like:
  #   /refs/notes/*
  #   /refs/git-as-svn/*
  #   /refs/pulls/*
  # This refs by default not visible in project page and not cloned to client side.
  #
  # This method return true if repository contains some content visible in project page.
  #
  def has_visible_content?
65 66 67 68 69
    return @has_visible_content unless @has_visible_content.nil?

    @has_visible_content = cache.fetch(:has_visible_content?) do
      raw_repository.branch_count > 0
    end
70 71
  end

72
  def commit(id = 'HEAD')
73
    return nil unless exists?
74
    commit = Gitlab::Git::Commit.find(raw_repository, id)
75
    commit = Commit.new(commit, @project) if commit
76
    commit
77
  rescue Rugged::OdbError
78
    nil
79 80
  end

D
Dmitriy Zaporozhets 已提交
81
  def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
82
    options = {
83 84 85 86 87
      repo: raw_repository,
      ref: ref,
      path: path,
      limit: limit,
      offset: offset,
88 89
      # --follow doesn't play well with --skip. See:
      # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
90 91
      follow: false,
      skip_merges: skip_merges
92 93 94
    }

    commits = Gitlab::Git::Commit.where(options)
95
    commits = Commit.decorate(commits, @project) if commits.present?
96 97 98
    commits
  end

99 100
  def commits_between(from, to)
    commits = Gitlab::Git::Commit.between(raw_repository, from, to)
101
    commits = Commit.decorate(commits, @project) if commits.present?
102 103 104
    commits
  end

105 106 107
  def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
    ref ||= root_ref

108
    # Limited to 1000 commits for now, could be parameterized?
109 110
    args = %W(#{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} --max-count #{limit} --grep=#{query})
    args = args.concat(%W(-- #{path})) if path.present?
111

112 113
    git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
    commits = git_log_results.map { |c| commit(c) }
114
    commits
115 116
  end

117
  def find_branch(name)
118
    raw_repository.branches.find { |branch| branch.name == name }
119 120 121
  end

  def find_tag(name)
122
    raw_repository.tags.find { |tag| tag.name == name }
123 124
  end

125 126 127 128 129 130 131 132 133 134
  def add_branch(user, branch_name, target)
    oldrev = Gitlab::Git::BLANK_SHA
    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
    target = commit(target).try(:id)

    return false unless target

    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
      rugged.branches.create(branch_name, target)
    end
135

136 137
    expire_branches_cache
    find_branch(branch_name)
138 139
  end

140
  def add_tag(tag_name, ref, message = nil)
D
Douwe Maan 已提交
141
    expire_tags_cache
142

143
    gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
144 145
  end

146
  def rm_branch(user, branch_name)
D
Douwe Maan 已提交
147
    expire_branches_cache
148

149 150 151 152 153 154 155 156
    branch = find_branch(branch_name)
    oldrev = branch.try(:target)
    newrev = Gitlab::Git::BLANK_SHA
    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name

    GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
      rugged.branches.delete(branch_name)
    end
157

158 159
    expire_branches_cache
    true
160 161
  end

162
  def rm_tag(tag_name)
D
Douwe Maan 已提交
163
    expire_tags_cache
164

165 166 167
    gitlab_shell.rm_tag(path_with_namespace, tag_name)
  end

168
  def branch_names
169
    cache.fetch(:branch_names) { raw_repository.branch_names }
170 171 172
  end

  def tag_names
173
    cache.fetch(:tag_names) { raw_repository.tag_names }
174 175
  end

176
  def commit_count
177
    cache.fetch(:commit_count) do
178
      begin
179
        raw_repository.commit_count(self.root_ref)
180 181 182
      rescue
        0
      end
183
    end
184 185
  end

186 187 188
  # Return repo size in megabytes
  # Cached in redis
  def size
189
    cache.fetch(:size) { raw_repository.size }
190
  end
191

192
  def diverging_commit_counts(branch)
193
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
J
Jeff Stubler 已提交
194
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
195 196
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
197 198 199 200 201
      number_commits_behind = raw_repository.
        count_commits_between(branch.target, root_ref_hash)

      number_commits_ahead = raw_repository.
        count_commits_between(root_ref_hash, branch.target)
202

203 204 205
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
206

207
  def cache_keys
208
    %i(size branch_names tag_names commit_count
209 210
       readme version contribution_guide changelog license)
  end
211

212 213 214 215 216 217
  def build_cache
    cache_keys.each do |key|
      unless cache.exist?(key)
        send(key)
      end
    end
218

219
    branches.each do |branch|
J
Jeff Stubler 已提交
220
      unless cache.exist?(:"diverging_commit_counts_#{branch.name}")
221 222 223
        send(:diverging_commit_counts, branch)
      end
    end
224 225
  end

D
Douwe Maan 已提交
226 227 228 229 230 231 232 233 234 235
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
    @branches = nil
  end

236
  def expire_cache(branch_name = nil)
237
    cache_keys.each do |key|
238 239
      cache.expire(key)
    end
240

241
    expire_branch_cache(branch_name)
242 243 244 245

    # This ensures this particular cache is flushed after the first commit to a
    # new repository.
    expire_emptiness_caches if empty?
246
  end
247

248 249 250 251 252 253 254 255 256 257 258 259
  def expire_branch_cache(branch_name = nil)
    # When we push to the root branch we have to flush the cache for all other
    # branches as their statistics are based on the commits relative to the
    # root branch.
    if !branch_name || branch_name == root_ref
      branches.each do |branch|
        cache.expire(:"diverging_commit_counts_#{branch.name}")
      end
    # In case a commit is pushed to a non-root branch we only have to flush the
    # cache for said branch.
    else
      cache.expire(:"diverging_commit_counts_#{branch_name}")
260
    end
D
Dmitriy Zaporozhets 已提交
261 262
  end

263 264 265 266 267
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

268 269 270 271 272 273 274 275
  # Expires the cache(s) used to determine if a repository is empty or not.
  def expire_emptiness_caches
    cache.expire(:empty?)
    @empty = nil

    expire_has_visible_content_cache
  end

276 277 278 279 280
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

281 282
  def rebuild_cache
    cache_keys.each do |key|
283
      cache.expire(key)
284
      send(key)
D
Dmitriy Zaporozhets 已提交
285
    end
286

287
    branches.each do |branch|
J
Jeff Stubler 已提交
288 289
      cache.expire(:"diverging_commit_counts_#{branch.name}")
      diverging_commit_counts(branch)
290
    end
291 292
  end

293 294 295 296
  def lookup_cache
    @lookup_cache ||= {}
  end

297 298 299 300
  def expire_branch_names
    cache.expire(:branch_names)
  end

301 302
  # Runs code just before a repository is deleted.
  def before_delete
303 304 305 306
    expire_cache if exists?

    expire_root_ref_cache
    expire_emptiness_caches
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
  end

  # Runs code just before the HEAD of a repository is changed.
  def before_change_head
    # Cached divergent commit counts are based on repository head
    expire_branch_cache
    expire_root_ref_cache
  end

  # Runs code before creating a new tag.
  def before_create_tag
    expire_cache
  end

  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
  end

  # Runs code after a new commit has been pushed.
  def after_push_commit(branch_name)
    expire_cache(branch_name)
  end

  # Runs code after a new branch has been created.
  def after_create_branch
    expire_has_visible_content_cache
  end

  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
  end

341
  def method_missing(m, *args, &block)
342 343 344 345 346 347
    if m == :lookup && !block_given?
      lookup_cache[m] ||= {}
      lookup_cache[m][args.join(":")] ||= raw_repository.send(m, *args, &block)
    else
      raw_repository.send(m, *args, &block)
    end
348 349
  end

350 351
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
352
  end
D
Dmitriy Zaporozhets 已提交
353 354

  def blob_at(sha, path)
355 356 357
    unless Gitlab::Git.blank_ref?(sha)
      Gitlab::Git::Blob.find(self, sha, path)
    end
D
Dmitriy Zaporozhets 已提交
358
  end
359

360 361 362 363
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

364
  def readme
365
    cache.fetch(:readme) { tree(:head).readme }
366
  end
367

368
  def version
369
    cache.fetch(:version) do
370 371 372 373 374 375
      tree(:head).blobs.find do |file|
        file.name.downcase == 'version'
      end
    end
  end

376
  def contribution_guide
377 378 379 380 381 382
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
383 384 385 386

  def changelog
    cache.fetch(:changelog) do
      tree(:head).blobs.find do |file|
387
        file.name =~ /\A(changelog|history)/i
388 389
      end
    end
390 391
  end

392 393
  def license
    cache.fetch(:license) do
Z
Zeger-Jan van de Weg 已提交
394
      licenses =  tree(:head).blobs.find_all do |file|
395
                    file.name =~ /\A(copying|license|licence)/i
Z
Zeger-Jan van de Weg 已提交
396 397
                  end

398 399 400 401 402 403 404 405 406 407 408 409 410
      preferences = [
        /\Alicen[sc]e\z/i,        # LICENSE, LICENCE
        /\Alicen[sc]e\./i,        # LICENSE.md, LICENSE.txt
        /\Acopying\z/i,           # COPYING
        /\Acopying\.(?!lesser)/i, # COPYING.txt
        /Acopying.lesser/i        # COPYING.LESSER
      ]

      license = nil
      preferences.each do |r|
        license = licenses.find { |l| l.name =~ r }
        break if license
      end
Z
Zeger-Jan van de Weg 已提交
411

412
      license
413
    end
414 415
  end

416
  def head_commit
417 418 419 420 421
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
422 423 424 425
  end

  def tree(sha = :head, path = nil)
    if sha == :head
426 427 428 429 430
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
431 432 433 434
    end

    Tree.new(self, sha, path)
  end
D
Dmitriy Zaporozhets 已提交
435 436

  def blob_at_branch(branch_name, path)
D
Dmitriy Zaporozhets 已提交
437
    last_commit = commit(branch_name)
D
Dmitriy Zaporozhets 已提交
438

D
Dmitriy Zaporozhets 已提交
439 440 441 442 443
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
444
  end
D
Dmitriy Zaporozhets 已提交
445 446 447 448 449 450 451 452

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
453
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
454 455 456 457 458 459 460
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
461 462

  def last_commit_for_path(sha, path)
463
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
464 465
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
466
  end
467

468 469 470 471 472 473 474 475 476 477 478
  def next_patch_branch
    patch_branch_ids = self.branch_names.map do |n|
      result = n.match(/\Apatch-([0-9]+)\z/)
      result[1].to_i if result
    end.compact

    highest_patch_branch_id = patch_branch_ids.max || 0

    "patch-#{highest_patch_branch_id + 1}"
  end

479
  # Remove archives older than 2 hours
480 481 482 483 484 485 486 487 488 489 490 491 492 493
  def branches_sorted_by(value)
    case value
    when 'recently_updated'
      branches.sort do |a, b|
        commit(b.target).committed_date <=> commit(a.target).committed_date
      end
    when 'last_updated'
      branches.sort do |a, b|
        commit(a.target).committed_date <=> commit(b.target).committed_date
      end
    else
      branches
    end
  end
494 495

  def contributors
D
Dmitriy Zaporozhets 已提交
496
    commits = self.commits(nil, nil, 2000, 0, true)
497

D
Dmitriy Zaporozhets 已提交
498
    commits.group_by(&:author_email).map do |email, commits|
499 500
      contributor = Gitlab::Contributor.new
      contributor.email = email
501

D
Dmitriy Zaporozhets 已提交
502
      commits.each do |commit|
503
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
504
          contributor.name = commit.author_name
505 506
        end

507
        contributor.commits += 1
508 509
      end

510 511
      contributor
    end
512
  end
D
Dmitriy Zaporozhets 已提交
513 514

  def blob_for_diff(commit, diff)
515
    blob_at(commit.id, diff.file_path)
D
Dmitriy Zaporozhets 已提交
516 517 518 519 520 521 522
  end

  def prev_blob_for_diff(commit, diff)
    if commit.parent_id
      blob_at(commit.parent_id, diff.old_path)
    end
  end
523

524 525
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
526 527 528 529 530 531 532 533 534 535 536 537 538 539
    names = Gitlab::Popen.popen(args, path_to_repo).first

    if names.respond_to?(:split)
      names = names.split("\n").map(&:strip)

      names.each do |name|
        name.slice! '* '
      end

      names
    else
      []
    end
  end
H
Hannes Rosenögger 已提交
540

541 542 543
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
544

545 546
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
547
  end
548

549 550 551 552 553 554 555 556 557
  def branches
    @branches ||= raw_repository.branches
  end

  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
558
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
559 560
  end

S
Stan Hu 已提交
561
  def commit_dir(user, path, message, branch)
562
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
563 564 565 566 567 568 569 570 571 572 573 574 575
      committer = user_to_committer(user)
      options = {}
      options[:committer] = committer
      options[:author] = committer

      options[:commit] = {
        message: message,
        branch: ref,
      }

      raw_repository.mkdir(path, options)
    end
  end
576

S
Stan Hu 已提交
577 578 579
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
580 581 582 583 584 585 586
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
      }
587

588 589
      options[:file] = {
        content: content,
S
Stan Hu 已提交
590 591
        path: path,
        update: update
592
      }
593

594 595
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
596 597
  end

598
  def remove_file(user, path, message, branch)
599
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
600
      committer = user_to_committer(user)
601 602 603 604 605 606 607
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref
      }
608

609 610 611
      options[:file] = {
        path: path
      }
612

613 614
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
615 616
  end

S
Stan Hu 已提交
617
  def user_to_committer(user)
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
    {
      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

636
  def merge(user, source_sha, target_branch, options = {})
637 638 639 640 641 642 643 644 645
    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?

646 647 648 649 650 651
    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
      )
652

653
      Rugged::Commit.create(rugged, actual_options)
654
    end
655 656
  end

657 658 659
  def revert(user, commit, base_branch, revert_tree_id = nil)
    source_sha = find_branch(base_branch).target
    revert_tree_id = check_revert_content(commit, base_branch) unless revert_tree_id
660

661
    return false unless revert_tree_id
662

663
    commit_with_hooks(user, base_branch) do |ref|
664
      committer = user_to_committer(user)
665
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
666
        message: commit.revert_message,
667 668
        author: committer,
        committer: committer,
669
        tree: revert_tree_id,
670
        parents: [rugged.lookup(source_sha)],
R
Rubén Dávila 已提交
671
        update_ref: ref)
672
    end
673 674
  end

675 676 677 678 679 680 681 682 683 684 685 686 687 688
  def check_revert_content(commit, base_branch)
    source_sha = find_branch(base_branch).target
    args       = [commit.id, source_sha]
    args       << { mainline: 1 } if commit.merge_commit?

    revert_index = rugged.revert_commit(*args)
    return false if revert_index.conflicts?

    tree_id = revert_index.write_tree(rugged)
    return false unless diff_exists?(source_sha, tree_id)

    tree_id
  end

689 690
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
691 692
  end

F
Florent (HP) 已提交
693 694 695 696 697
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
698
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
699 700 701 702 703
    else
      nil
    end
  end

S
Stan Hu 已提交
704
  def merge_base(first_commit_id, second_commit_id)
705 706
    first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
    second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
S
Stan Hu 已提交
707
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
708 709
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
710 711
  end

712 713 714 715 716
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end


717 718
  def search_files(query, ref)
    offset = 2
719
    args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
720 721 722
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

D
Dmitriy Zaporozhets 已提交
723
  def parse_search_result(result)
724 725 726 727
    ref = nil
    filename = nil
    startline = 0

728
    result.each_line.each_with_index do |line, index|
729 730 731 732 733 734 735
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
        break
      end
    end

736
    data = ""
737

738 739 740
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
741 742 743 744 745 746 747 748 749

    OpenStruct.new(
      filename: filename,
      ref: ref,
      startline: startline,
      data: data
    )
  end

750
  def fetch_ref(source_path, source_ref, target_ref)
751
    args = %W(#{Gitlab.config.git.bin_path} fetch -f #{source_path} #{source_ref}:#{target_ref})
752 753 754
    Gitlab::Popen.popen(args, path_to_repo)
  end

755
  def with_tmp_ref(oldrev = nil)
756 757 758
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

759
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
760 761 762 763
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
764 765 766 767 768 769
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
770 771
    update_autocrlf_option

772 773
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
774
    target_branch = find_branch(branch)
775
    was_empty = empty?
776

777 778
    if !was_empty && target_branch
      oldrev = target_branch.target
779 780
    end

781 782 783 784 785 786 787
    with_tmp_ref(oldrev) do |tmp_ref|
      # Make commit in tmp ref
      newrev = yield(tmp_ref)

      unless newrev
        raise CommitError.new('Failed to create commit')
      end
788

789
      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
790
        if was_empty || !target_branch
791 792
          # Create branch
          rugged.references.create(ref, newrev)
793
        else
794 795 796 797 798 799 800 801 802
          # 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
803 804
        end
      end
805 806

      newrev
807 808 809
    end
  end

810 811 812 813 814
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

815 816
  private

817 818 819
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
820
end