repository.rb 18.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
  end
243

244 245 246 247 248 249 250 251 252
  # Expires _all_ caches, including those that would normally only be expired
  # under specific conditions.
  def expire_all_caches!
    expire_cache
    expire_root_ref_cache
    expire_emptiness_caches
    expire_has_visible_content_cache
  end

253 254 255 256 257 258 259 260 261 262 263 264
  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}")
265
    end
D
Dmitriy Zaporozhets 已提交
266 267
  end

268 269 270 271 272
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

273 274 275 276 277 278 279 280
  # 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

281 282 283 284 285
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

286 287
  def rebuild_cache
    cache_keys.each do |key|
288
      cache.expire(key)
289
      send(key)
D
Dmitriy Zaporozhets 已提交
290
    end
291

292
    branches.each do |branch|
J
Jeff Stubler 已提交
293 294
      cache.expire(:"diverging_commit_counts_#{branch.name}")
      diverging_commit_counts(branch)
295
    end
296 297
  end

298 299 300 301
  def lookup_cache
    @lookup_cache ||= {}
  end

302 303 304 305
  def expire_branch_names
    cache.expire(:branch_names)
  end

306
  def method_missing(m, *args, &block)
307 308 309 310 311 312
    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
313 314
  end

315 316
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
317
  end
D
Dmitriy Zaporozhets 已提交
318 319

  def blob_at(sha, path)
320 321 322
    unless Gitlab::Git.blank_ref?(sha)
      Gitlab::Git::Blob.find(self, sha, path)
    end
D
Dmitriy Zaporozhets 已提交
323
  end
324

325 326 327 328
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

329
  def readme
330
    cache.fetch(:readme) { tree(:head).readme }
331
  end
332

333
  def version
334
    cache.fetch(:version) do
335 336 337 338 339 340
      tree(:head).blobs.find do |file|
        file.name.downcase == 'version'
      end
    end
  end

341
  def contribution_guide
342 343 344 345 346 347
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
348 349 350 351

  def changelog
    cache.fetch(:changelog) do
      tree(:head).blobs.find do |file|
352
        file.name =~ /\A(changelog|history)/i
353 354
      end
    end
355 356
  end

357 358
  def license
    cache.fetch(:license) do
Z
Zeger-Jan van de Weg 已提交
359
      licenses =  tree(:head).blobs.find_all do |file|
360
                    file.name =~ /\A(copying|license|licence)/i
Z
Zeger-Jan van de Weg 已提交
361 362
                  end

363 364 365 366 367 368 369 370 371 372 373 374 375
      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 已提交
376

377
      license
378
    end
379 380
  end

381
  def head_commit
382 383 384 385 386
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
387 388 389 390
  end

  def tree(sha = :head, path = nil)
    if sha == :head
391 392 393 394 395
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
396 397 398 399
    end

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

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

D
Dmitriy Zaporozhets 已提交
404 405 406 407 408
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
409
  end
D
Dmitriy Zaporozhets 已提交
410 411 412 413 414 415 416 417

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
418
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
419 420 421 422 423 424 425
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
426 427

  def last_commit_for_path(sha, path)
428
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
429 430
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
431
  end
432

433 434 435 436 437 438 439 440 441 442 443
  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

444
  # Remove archives older than 2 hours
445 446 447 448 449 450 451 452 453 454 455 456 457 458
  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
459 460

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

D
Dmitriy Zaporozhets 已提交
463
    commits.group_by(&:author_email).map do |email, commits|
464 465
      contributor = Gitlab::Contributor.new
      contributor.email = email
466

D
Dmitriy Zaporozhets 已提交
467
      commits.each do |commit|
468
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
469
          contributor.name = commit.author_name
470 471
        end

472
        contributor.commits += 1
473 474
      end

475 476
      contributor
    end
477
  end
D
Dmitriy Zaporozhets 已提交
478 479

  def blob_for_diff(commit, diff)
480
    blob_at(commit.id, diff.file_path)
D
Dmitriy Zaporozhets 已提交
481 482 483 484 485 486 487
  end

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

489 490
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
491 492 493 494 495 496 497 498 499 500 501 502 503 504
    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 已提交
505

506 507 508
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
509

510 511
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
512
  end
513

514 515 516 517 518 519 520 521 522
  def branches
    @branches ||= raw_repository.branches
  end

  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
523
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
524 525
  end

S
Stan Hu 已提交
526
  def commit_dir(user, path, message, branch)
527
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
528 529 530 531 532 533 534 535 536 537 538 539 540
      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
541

S
Stan Hu 已提交
542 543 544
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
545 546 547 548 549 550 551
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
      }
552

553 554
      options[:file] = {
        content: content,
S
Stan Hu 已提交
555 556
        path: path,
        update: update
557
      }
558

559 560
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
561 562
  end

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

574 575 576
      options[:file] = {
        path: path
      }
577

578 579
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
580 581
  end

S
Stan Hu 已提交
582
  def user_to_committer(user)
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
    {
      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

601
  def merge(user, source_sha, target_branch, options = {})
602 603 604 605 606 607 608 609 610
    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?

611 612 613 614 615 616
    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
      )
617

618
      Rugged::Commit.create(rugged, actual_options)
619
    end
620 621
  end

622
  def revert(user, commit, base_branch, target_branch = nil)
623
    source_sha    = find_branch(base_branch).target
624
    target_branch ||= base_branch
625
    args          = [commit.id, source_sha]
626
    args          << { mainline: 1 } if commit.merge_commit?
627

628 629
    revert_index = rugged.revert_commit(*args)
    return false if revert_index.conflicts?
630 631

    tree_id = revert_index.write_tree(rugged)
632
    return false unless diff_exists?(source_sha, tree_id)
633 634 635

    commit_with_hooks(user, target_branch) do |ref|
      committer = user_to_committer(user)
636
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
637
        message: commit.revert_message,
638 639
        author: committer,
        committer: committer,
640
        tree: tree_id,
641
        parents: [rugged.lookup(source_sha)],
R
Rubén Dávila 已提交
642
        update_ref: ref)
643
    end
644 645
  end

646 647
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
648 649
  end

F
Florent (HP) 已提交
650 651 652 653 654
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
655
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
656 657 658 659 660
    else
      nil
    end
  end

S
Stan Hu 已提交
661
  def merge_base(first_commit_id, second_commit_id)
662 663
    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 已提交
664
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
665 666
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
667 668
  end

669 670 671 672 673
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end


674 675
  def search_files(query, ref)
    offset = 2
676
    args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
677 678 679
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

D
Dmitriy Zaporozhets 已提交
680
  def parse_search_result(result)
681 682 683 684
    ref = nil
    filename = nil
    startline = 0

685
    result.each_line.each_with_index do |line, index|
686 687 688 689 690 691 692
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
        break
      end
    end

693
    data = ""
694

695 696 697
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
698 699 700 701 702 703 704 705 706

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

707
  def fetch_ref(source_path, source_ref, target_ref)
708
    args = %W(#{Gitlab.config.git.bin_path} fetch -f #{source_path} #{source_ref}:#{target_ref})
709 710 711
    Gitlab::Popen.popen(args, path_to_repo)
  end

712
  def with_tmp_ref(oldrev = nil)
713 714 715
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

716
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
717 718 719 720
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
721 722 723 724 725 726
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
727 728
    update_autocrlf_option

729 730
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
731
    target_branch = find_branch(branch)
732
    was_empty = empty?
733

734 735
    if !was_empty && target_branch
      oldrev = target_branch.target
736 737
    end

738 739 740 741 742 743 744
    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
745

746
      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
747
        if was_empty || !target_branch
748 749
          # Create branch
          rugged.references.create(ref, newrev)
750
        else
751 752 753 754 755 756 757 758 759
          # 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
760 761
        end
      end
762 763

      newrev
764 765 766
    end
  end

767 768 769 770 771
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

772 773
  private

774 775 776
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
777
end