repository.rb 18.8 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
  # 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

257 258 259 260 261 262 263 264 265 266 267 268
  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}")
269
    end
D
Dmitriy Zaporozhets 已提交
270 271
  end

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

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

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

290 291
  def rebuild_cache
    cache_keys.each do |key|
292
      cache.expire(key)
293
      send(key)
D
Dmitriy Zaporozhets 已提交
294
    end
295

296
    branches.each do |branch|
J
Jeff Stubler 已提交
297 298
      cache.expire(:"diverging_commit_counts_#{branch.name}")
      diverging_commit_counts(branch)
299
    end
300 301
  end

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

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

310
  def method_missing(m, *args, &block)
311 312 313 314 315 316
    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
317 318
  end

319 320
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
321
  end
D
Dmitriy Zaporozhets 已提交
322 323

  def blob_at(sha, path)
324 325 326
    unless Gitlab::Git.blank_ref?(sha)
      Gitlab::Git::Blob.find(self, sha, path)
    end
D
Dmitriy Zaporozhets 已提交
327
  end
328

329 330 331 332
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

333
  def readme
334
    cache.fetch(:readme) { tree(:head).readme }
335
  end
336

337
  def version
338
    cache.fetch(:version) do
339 340 341 342 343 344
      tree(:head).blobs.find do |file|
        file.name.downcase == 'version'
      end
    end
  end

345
  def contribution_guide
346 347 348 349 350 351
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
352 353 354 355

  def changelog
    cache.fetch(:changelog) do
      tree(:head).blobs.find do |file|
356
        file.name =~ /\A(changelog|history)/i
357 358
      end
    end
359 360
  end

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

367 368 369 370 371 372 373 374 375 376 377 378 379
      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 已提交
380

381
      license
382
    end
383 384
  end

385
  def head_commit
386 387 388 389 390
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
391 392 393 394
  end

  def tree(sha = :head, path = nil)
    if sha == :head
395 396 397 398 399
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
400 401 402 403
    end

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

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

D
Dmitriy Zaporozhets 已提交
408 409 410 411 412
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
413
  end
D
Dmitriy Zaporozhets 已提交
414 415 416 417 418 419 420 421

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

      if submodule
        submodule['url']
      end
    end
  end
430 431

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

437 438 439 440 441 442 443 444 445 446 447
  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

448
  # Remove archives older than 2 hours
449 450 451 452 453 454 455 456 457 458 459 460 461 462
  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
463 464

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

D
Dmitriy Zaporozhets 已提交
467
    commits.group_by(&:author_email).map do |email, commits|
468 469
      contributor = Gitlab::Contributor.new
      contributor.email = email
470

D
Dmitriy Zaporozhets 已提交
471
      commits.each do |commit|
472
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
473
          contributor.name = commit.author_name
474 475
        end

476
        contributor.commits += 1
477 478
      end

479 480
      contributor
    end
481
  end
D
Dmitriy Zaporozhets 已提交
482 483

  def blob_for_diff(commit, diff)
484
    blob_at(commit.id, diff.file_path)
D
Dmitriy Zaporozhets 已提交
485 486 487 488 489 490 491
  end

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

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

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

514 515
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
516
  end
517

518 519 520 521 522 523 524 525 526
  def branches
    @branches ||= raw_repository.branches
  end

  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
527
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
528 529
  end

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

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

557 558
      options[:file] = {
        content: content,
S
Stan Hu 已提交
559 560
        path: path,
        update: update
561
      }
562

563 564
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
565 566
  end

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

578 579 580
      options[:file] = {
        path: path
      }
581

582 583
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
584 585
  end

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

605
  def merge(user, source_sha, target_branch, options = {})
606 607 608 609 610 611 612 613 614
    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?

615 616 617 618 619 620
    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
      )
621

622
      Rugged::Commit.create(rugged, actual_options)
623
    end
624 625
  end

626
  def revert(user, commit, base_branch, target_branch = nil)
627
    source_sha    = find_branch(base_branch).target
628
    target_branch ||= base_branch
629
    args          = [commit.id, source_sha]
630
    args          << { mainline: 1 } if commit.merge_commit?
631

632 633
    revert_index = rugged.revert_commit(*args)
    return false if revert_index.conflicts?
634 635

    tree_id = revert_index.write_tree(rugged)
636
    return false unless diff_exists?(source_sha, tree_id)
637 638 639

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

650 651
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
652 653
  end

F
Florent (HP) 已提交
654 655 656 657 658
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
659
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
660 661 662 663 664
    else
      nil
    end
  end

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

673 674 675 676 677
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end


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

D
Dmitriy Zaporozhets 已提交
684
  def parse_search_result(result)
685 686 687 688
    ref = nil
    filename = nil
    startline = 0

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

697
    data = ""
698

699 700 701
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
702 703 704 705 706 707 708 709 710

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

711
  def fetch_ref(source_path, source_ref, target_ref)
712
    args = %W(#{Gitlab.config.git.bin_path} fetch -f #{source_path} #{source_ref}:#{target_ref})
713 714 715
    Gitlab::Popen.popen(args, path_to_repo)
  end

716
  def with_tmp_ref(oldrev = nil)
717 718 719
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

720
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
721 722 723 724
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
725 726 727 728 729 730
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
731 732
    update_autocrlf_option

733 734
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
735
    target_branch = find_branch(branch)
736
    was_empty = empty?
737

738 739
    if !was_empty && target_branch
      oldrev = target_branch.target
740 741
    end

742 743 744 745 746 747 748
    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
749

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

      newrev
768 769 770
    end
  end

771 772 773 774 775
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

776 777
  private

778 779 780
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
781
end