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

3
class Repository
4 5
  class CommitError < StandardError; end

6 7 8 9
  # Files to use as a project avatar in case no avatar was uploaded via the web
  # UI.
  AVATAR_FILES = %w{logo.png logo.jpg logo.gif}

10 11
  include Gitlab::ShellAdapter

12
  attr_accessor :path_with_namespace, :project
13

14 15 16 17 18 19 20 21
  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

22
  def initialize(path_with_namespace, project)
23
    @path_with_namespace = path_with_namespace
24
    @project = project
25
  end
26

27 28
  def raw_repository
    return nil unless path_with_namespace
29

30
    @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
31 32
  end

33 34 35 36
  def update_autocrlf_option
    raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
  end

37
  # Return absolute path to repository
38
  def path_to_repo
39 40 41
    @path_to_repo ||= File.expand_path(
      File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git")
    )
42 43
  end

44
  def exists?
45
    return @exists unless @exists.nil?
46

47 48 49 50 51 52 53
    @exists = cache.fetch(:exists?) do
      begin
        raw_repository && raw_repository.rugged ? true : false
      rescue Gitlab::Git::Repository::NoRepository
        false
      end
    end
54 55 56
  end

  def empty?
57 58 59
    return @empty unless @empty.nil?

    @empty = cache.fetch(:empty?) { raw_repository.empty? }
60 61
  end

62 63 64 65 66 67 68 69 70 71
  #
  # 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?
72 73 74 75 76
    return @has_visible_content unless @has_visible_content.nil?

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

79
  def commit(id = 'HEAD')
80
    return nil unless exists?
81
    commit = Gitlab::Git::Commit.find(raw_repository, id)
82
    commit = Commit.new(commit, @project) if commit
83
    commit
84
  rescue Rugged::OdbError
85
    nil
86 87
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
88
  def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
89
    options = {
90 91 92 93 94
      repo: raw_repository,
      ref: ref,
      path: path,
      limit: limit,
      offset: offset,
95 96
      # --follow doesn't play well with --skip. See:
      # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
97 98
      follow: false,
      skip_merges: skip_merges
99 100 101
    }

    commits = Gitlab::Git::Commit.where(options)
102
    commits = Commit.decorate(commits, @project) if commits.present?
103 104 105
    commits
  end

106 107
  def commits_between(from, to)
    commits = Gitlab::Git::Commit.between(raw_repository, from, to)
108
    commits = Commit.decorate(commits, @project) if commits.present?
109 110 111
    commits
  end

112 113 114
  def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
    ref ||= root_ref

115
    # Limited to 1000 commits for now, could be parameterized?
116 117
    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?
118

119 120
    git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
    commits = git_log_results.map { |c| commit(c) }
121
    commits
122 123
  end

124
  def find_branch(name)
125
    raw_repository.branches.find { |branch| branch.name == name }
126 127 128
  end

  def find_tag(name)
129
    raw_repository.tags.find { |tag| tag.name == name }
130 131
  end

132 133 134 135 136 137 138 139 140 141
  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
142

143
    after_create_branch
144
    find_branch(branch_name)
145 146
  end

147
  def add_tag(tag_name, ref, message = nil)
148
    before_push_tag
149

150
    gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
151 152
  end

153
  def rm_branch(user, branch_name)
154
    before_remove_branch
155

156 157 158 159 160 161 162 163
    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
164

165
    after_remove_branch
166
    true
167 168
  end

169
  def rm_tag(tag_name)
170
    before_remove_tag
171

172 173 174
    gitlab_shell.rm_tag(path_with_namespace, tag_name)
  end

175
  def branch_names
176
    cache.fetch(:branch_names) { raw_repository.branch_names }
177 178 179
  end

  def tag_names
180
    cache.fetch(:tag_names) { raw_repository.tag_names }
181 182
  end

183
  def commit_count
184
    cache.fetch(:commit_count) do
185
      begin
186
        raw_repository.commit_count(self.root_ref)
187 188 189
      rescue
        0
      end
190
    end
191 192
  end

193 194 195 196 197 198 199 200
  def branch_count
    @branch_count ||= cache.fetch(:branch_count) { raw_repository.branch_count }
  end

  def tag_count
    @tag_count ||= cache.fetch(:tag_count) { raw_repository.rugged.tags.count }
  end

201 202 203
  # Return repo size in megabytes
  # Cached in redis
  def size
204
    cache.fetch(:size) { raw_repository.size }
205
  end
206

207
  def diverging_commit_counts(branch)
208
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
Jeff Stubler's avatar
Jeff Stubler committed
209
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
210 211
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
212 213 214 215 216
      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)
217

218 219 220
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
221

222
  def cache_keys
223
    %i(size branch_names tag_names commit_count
224 225
       readme version contribution_guide changelog license)
  end
226

227 228 229 230 231 232 233 234
  def build_cache
    cache_keys.each do |key|
      unless cache.exist?(key)
        send(key)
      end
    end
  end

235 236 237 238 239 240 241 242 243 244
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

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

245
  def expire_cache(branch_name = nil, revision = nil)
246
    cache_keys.each do |key|
247 248
      cache.expire(key)
    end
249

250
    expire_branch_cache(branch_name)
251
    expire_avatar_cache(branch_name, revision)
252 253 254 255

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

258 259 260 261 262 263 264 265 266 267 268 269
  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}")
270
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
271 272
  end

273 274 275 276 277
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

278 279 280 281 282 283 284 285
  # 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

286 287 288 289 290
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

291 292 293 294 295 296 297 298 299 300
  def expire_branch_count_cache
    cache.expire(:branch_count)
    @branch_count = nil
  end

  def expire_tag_count_cache
    cache.expire(:tag_count)
    @tag_count = nil
  end

301 302 303 304
  def lookup_cache
    @lookup_cache ||= {}
  end

305 306 307 308
  def expire_branch_names
    cache.expire(:branch_names)
  end

309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
  def expire_avatar_cache(branch_name = nil, revision = nil)
    # Avatars are pulled from the default branch, thus if somebody pushes to a
    # different branch there's no need to expire anything.
    return if branch_name && branch_name != root_ref

    # We don't want to flush the cache if the commit didn't actually make any
    # changes to any of the possible avatar files.
    if revision && commit = self.commit(revision)
      return unless commit.diffs.
        any? { |diff| AVATAR_FILES.include?(diff.new_path) }
    end

    cache.expire(:avatar)

    @avatar = nil
  end

326 327 328 329 330 331 332 333 334 335
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
  end

336 337
  # Runs code just before a repository is deleted.
  def before_delete
338 339 340 341
    expire_cache if exists?

    expire_root_ref_cache
    expire_emptiness_caches
342
    expire_exists_cache
343 344 345 346 347 348 349 350 351
  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

352 353
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
354
    expire_cache
355
    expire_tags_cache
356 357 358 359 360 361 362
    expire_tag_count_cache
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
363 364 365 366 367
  end

  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
368
    expire_exists_cache
369 370 371
  end

  # Runs code after a new commit has been pushed.
372 373
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
374 375 376 377
  end

  # Runs code after a new branch has been created.
  def after_create_branch
378
    expire_branches_cache
379
    expire_has_visible_content_cache
380
    expire_branch_count_cache
381 382
  end

383 384 385 386 387
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
  end

388 389 390
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
391
    expire_branch_count_cache
392
    expire_branches_cache
393 394
  end

395
  def method_missing(m, *args, &block)
396 397 398 399 400 401
    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
402 403
  end

404 405
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
406
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
407 408

  def blob_at(sha, path)
409 410 411
    unless Gitlab::Git.blank_ref?(sha)
      Gitlab::Git::Blob.find(self, sha, path)
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
412
  end
413

414 415 416 417
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

418
  def readme
419
    cache.fetch(:readme) { tree(:head).readme }
420
  end
421

422
  def version
423
    cache.fetch(:version) do
424 425 426 427 428 429
      tree(:head).blobs.find do |file|
        file.name.downcase == 'version'
      end
    end
  end

430
  def contribution_guide
431 432 433 434 435 436
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
437 438 439 440

  def changelog
    cache.fetch(:changelog) do
      tree(:head).blobs.find do |file|
441
        file.name =~ /\A(changelog|history)/i
442 443
      end
    end
444 445
  end

446 447
  def license
    cache.fetch(:license) do
448
      licenses =  tree(:head).blobs.find_all do |file|
449
                    file.name =~ /\A(copying|license|licence)/i
450 451
                  end

452 453 454 455 456 457 458 459 460 461 462 463 464
      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
465

466
      license
467
    end
468 469
  end

470
  def head_commit
471 472 473 474 475
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
476 477 478 479
  end

  def tree(sha = :head, path = nil)
    if sha == :head
480 481 482 483 484
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
485 486 487 488
    end

    Tree.new(self, sha, path)
  end
489 490

  def blob_at_branch(branch_name, path)
491
    last_commit = commit(branch_name)
492

493 494 495 496 497
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
498
  end
499 500 501 502 503 504 505 506

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
507
    if submodules(ref).any?
508 509 510 511 512 513 514
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
515 516

  def last_commit_for_path(sha, path)
517
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
518 519
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
520
  end
521

522 523 524 525 526 527 528 529 530 531 532
  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

533
  # Remove archives older than 2 hours
534 535 536 537 538 539 540 541 542 543 544 545 546 547
  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
548 549

  def contributors
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
550
    commits = self.commits(nil, nil, 2000, 0, true)
551

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
552
    commits.group_by(&:author_email).map do |email, commits|
553 554
      contributor = Gitlab::Contributor.new
      contributor.email = email
555

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
556
      commits.each do |commit|
557
        if contributor.name.blank?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
558
          contributor.name = commit.author_name
559 560
        end

561
        contributor.commits += 1
562 563
      end

564 565
      contributor
    end
566
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
567 568

  def blob_for_diff(commit, diff)
569
    blob_at(commit.id, diff.file_path)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
570 571 572 573 574 575 576
  end

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

578 579
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
580 581 582 583 584 585 586 587 588 589 590 591 592 593
    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
594

595 596 597
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
598

599 600
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
601
  end
602

603 604 605 606 607 608 609 610 611
  def branches
    @branches ||= raw_repository.branches
  end

  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
612
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
613 614
  end

Stan Hu's avatar
Stan Hu committed
615
  def commit_dir(user, path, message, branch)
616
    commit_with_hooks(user, branch) do |ref|
Stan Hu's avatar
Stan Hu committed
617 618 619 620 621 622 623 624 625 626 627 628 629
      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
630

Stan Hu's avatar
Stan Hu committed
631 632 633
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
634 635 636 637 638 639 640
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
      }
641

642 643
      options[:file] = {
        content: content,
Stan Hu's avatar
Stan Hu committed
644 645
        path: path,
        update: update
646
      }
647

648 649
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
650 651
  end

652
  def remove_file(user, path, message, branch)
653
    commit_with_hooks(user, branch) do |ref|
Stan Hu's avatar
Stan Hu committed
654
      committer = user_to_committer(user)
655 656 657 658 659 660 661
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref
      }
662

663 664 665
      options[:file] = {
        path: path
      }
666

667 668
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
669 670
  end

Stan Hu's avatar
Stan Hu committed
671
  def user_to_committer(user)
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
    {
      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

690
  def merge(user, source_sha, target_branch, options = {})
691 692 693 694 695 696 697 698 699
    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?

700 701 702 703 704 705
    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
      )
706

707
      Rugged::Commit.create(rugged, actual_options)
708
    end
709 710
  end

711 712
  def revert(user, commit, base_branch, revert_tree_id = nil)
    source_sha = find_branch(base_branch).target
713
    revert_tree_id ||= check_revert_content(commit, base_branch)
714

715
    return false unless revert_tree_id
716

717
    commit_with_hooks(user, base_branch) do |ref|
718
      committer = user_to_committer(user)
719
      source_sha = Rugged::Commit.create(rugged,
720
        message: commit.revert_message,
721 722
        author: committer,
        committer: committer,
723
        tree: revert_tree_id,
724
        parents: [rugged.lookup(source_sha)],
Rubén Dávila's avatar
Rubén Dávila committed
725
        update_ref: ref)
726
    end
727 728
  end

729 730 731 732 733 734 735 736 737 738 739 740 741 742
  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

743 744
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
745 746
  end

747 748 749 750 751
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
752
      is_ancestor?(branch_commit.id, root_ref_commit.id)
753 754 755 756 757
    else
      nil
    end
  end

758
  def merge_base(first_commit_id, second_commit_id)
759 760
    first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
    second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
761
    rugged.merge_base(first_commit_id, second_commit_id)
Douwe Maan's avatar
Douwe Maan committed
762 763
  rescue Rugged::ReferenceError
    nil
764 765
  end

766 767 768 769 770
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end


771 772
  def search_files(query, ref)
    offset = 2
773
    args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
774 775 776
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

777
  def parse_search_result(result)
778 779
    ref = nil
    filename = nil
780
    basename = nil
781 782
    startline = 0

783
    result.each_line.each_with_index do |line, index|
784 785 786
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
787 788
        extname = File.extname(filename)
        basename = filename.sub(/#{extname}$/, '')
789 790 791 792
        break
      end
    end

793
    data = ""
794

795 796 797
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
798 799 800

    OpenStruct.new(
      filename: filename,
801
      basename: basename,
802 803 804 805 806 807
      ref: ref,
      startline: startline,
      data: data
    )
  end

808
  def fetch_ref(source_path, source_ref, target_ref)
809
    args = %W(#{Gitlab.config.git.bin_path} fetch -f #{source_path} #{source_ref}:#{target_ref})
810 811 812
    Gitlab::Popen.popen(args, path_to_repo)
  end

813
  def with_tmp_ref(oldrev = nil)
814 815 816
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

817
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
818 819 820 821
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
822 823 824 825 826 827
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
828 829
    update_autocrlf_option

830 831
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
832
    target_branch = find_branch(branch)
833
    was_empty = empty?
834

835 836
    if !was_empty && target_branch
      oldrev = target_branch.target
837 838
    end

839 840 841 842 843 844 845
    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
846

847
      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
848
        if was_empty || !target_branch
849 850
          # Create branch
          rugged.references.create(ref, newrev)
851
        else
852 853 854 855 856 857 858 859 860
          # 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
861 862
        end
      end
863 864

      newrev
865 866 867
    end
  end

868 869 870 871 872
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

873 874 875 876 877 878
  def main_language
    unless empty?
      Linguist::Repository.new(rugged, rugged.head.target_id).language
    end
  end

879 880 881 882 883 884 885 886
  def avatar
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
        blob_at_branch('master', file)
      end
    end
  end

887 888
  private

889 890 891
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
892
end