repository.rb 22.3 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

J
Jacob Vosmaer 已提交
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
    return @has_visible_content unless @has_visible_content.nil?

    @has_visible_content = cache.fetch(:has_visible_content?) do
75
      branch_count > 0
76
    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

D
Dmitriy Zaporozhets 已提交
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)
Y
Yorick Peterse 已提交
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) { branches.map(&:name) }
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

Y
Yorick Peterse 已提交
193
  def branch_count
194
    @branch_count ||= cache.fetch(:branch_count) { branches.size }
Y
Yorick Peterse 已提交
195 196 197 198 199 200
  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
J
Jeff Stubler 已提交
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

D
Douwe Maan 已提交
235 236 237 238 239 240 241
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
242
    @local_branches = nil
D
Douwe Maan 已提交
243 244
  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 257
    expire_branch_count_cache
    expire_tag_count_cache
258
  end
259

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

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

280 281 282 283 284 285 286 287
  # 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

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

Y
Yorick Peterse 已提交
293 294 295 296 297 298 299 300 301 302
  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

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

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

311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
  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

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
336 337
    expire_root_ref_cache
    expire_emptiness_caches
338 339
  end

340 341
  # Runs code just before a repository is deleted.
  def before_delete
342 343
    expire_exists_cache

344 345 346 347
    expire_cache if exists?

    expire_root_ref_cache
    expire_emptiness_caches
348
    expire_exists_cache
349 350 351 352 353 354 355 356 357
  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

Y
Yorick Peterse 已提交
358 359
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
360
    expire_cache
361
    expire_tags_cache
Y
Yorick Peterse 已提交
362 363 364 365 366 367 368
    expire_tag_count_cache
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
369 370
  end

371 372 373 374 375
  def before_import
    expire_emptiness_caches
    expire_exists_cache
  end

376 377 378
  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
379
    expire_exists_cache
380 381 382
  end

  # Runs code after a new commit has been pushed.
383 384
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
385 386 387 388
  end

  # Runs code after a new branch has been created.
  def after_create_branch
389
    expire_branches_cache
390
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
391
    expire_branch_count_cache
392 393
  end

394 395 396 397 398
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
  end

399 400 401
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
402
    expire_branch_count_cache
403
    expire_branches_cache
404 405
  end

406
  def method_missing(m, *args, &block)
407 408 409 410 411 412
    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
413 414
  end

415 416
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
417
  end
D
Dmitriy Zaporozhets 已提交
418 419

  def blob_at(sha, path)
420 421 422
    unless Gitlab::Git.blank_ref?(sha)
      Gitlab::Git::Blob.find(self, sha, path)
    end
D
Dmitriy Zaporozhets 已提交
423
  end
424

425 426 427 428
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

429
  def readme
430
    cache.fetch(:readme) { tree(:head).readme }
431
  end
432

433
  def version
434
    cache.fetch(:version) do
435 436 437 438 439 440
      tree(:head).blobs.find do |file|
        file.name.downcase == 'version'
      end
    end
  end

441
  def contribution_guide
442 443 444 445 446 447
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
448 449 450 451

  def changelog
    cache.fetch(:changelog) do
      tree(:head).blobs.find do |file|
452
        file.name =~ /\A(changelog|history)/i
453 454
      end
    end
455 456
  end

457 458
  def license
    cache.fetch(:license) do
Z
Zeger-Jan van de Weg 已提交
459
      licenses =  tree(:head).blobs.find_all do |file|
460
                    file.name =~ /\A(copying|license|licence)/i
Z
Zeger-Jan van de Weg 已提交
461 462
                  end

463 464 465 466 467 468 469 470 471 472 473 474 475
      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 已提交
476

477
      license
478
    end
479 480
  end

481
  def gitlab_ci_yml
482
    return nil if !exists? || empty?
483 484 485 486

    @gitlab_ci_yml ||= tree(:head).blobs.find do |file|
      file.name == '.gitlab-ci.yml'
    end
487 488 489 490
  rescue Rugged::ReferenceError
    # For unknow reason spinach scenario "Scenario: I change project path"
    # lead to "Reference 'HEAD' not found" exception from Repository#empty?
    nil
491 492
  end

493
  def head_commit
494 495 496 497 498
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
499 500 501 502
  end

  def tree(sha = :head, path = nil)
    if sha == :head
503 504 505 506 507
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
508 509 510 511
    end

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

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

D
Dmitriy Zaporozhets 已提交
516 517 518 519 520
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
521
  end
D
Dmitriy Zaporozhets 已提交
522 523 524 525 526 527 528 529

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
530
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
531 532 533 534 535 536 537
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
538 539

  def last_commit_for_path(sha, path)
540
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
541 542
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
543
  end
544

545 546 547 548 549 550 551 552 553 554 555
  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

556
  # Remove archives older than 2 hours
557 558 559 560 561 562 563 564 565 566 567 568 569 570
  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
571 572

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

D
Dmitriy Zaporozhets 已提交
575
    commits.group_by(&:author_email).map do |email, commits|
576 577
      contributor = Gitlab::Contributor.new
      contributor.email = email
578

D
Dmitriy Zaporozhets 已提交
579
      commits.each do |commit|
580
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
581
          contributor.name = commit.author_name
582 583
        end

584
        contributor.commits += 1
585 586
      end

587 588
      contributor
    end
589
  end
D
Dmitriy Zaporozhets 已提交
590 591

  def blob_for_diff(commit, diff)
592
    blob_at(commit.id, diff.file_path)
D
Dmitriy Zaporozhets 已提交
593 594 595 596 597 598 599
  end

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

601 602
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
603 604 605 606 607 608 609 610 611 612 613 614 615 616
    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 已提交
617

618 619 620
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
621

622 623
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
624
  end
625

626 627 628 629
  def local_branches
    @local_branches ||= rugged.branches.each(:local).map do |branch|
      Gitlab::Git::Branch.new(branch.name, branch.target)
    end
630 631
  end

632 633
  alias_method :branches, :local_branches

634 635 636 637 638
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
639
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
640 641
  end

S
Stan Hu 已提交
642
  def commit_dir(user, path, message, branch)
643
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
644 645 646 647 648 649 650 651 652 653 654 655 656
      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
657

S
Stan Hu 已提交
658 659 660
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
661 662 663 664 665 666 667
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
      }
668

669 670
      options[:file] = {
        content: content,
S
Stan Hu 已提交
671 672
        path: path,
        update: update
673
      }
674

675 676
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
677 678
  end

679
  def remove_file(user, path, message, branch)
680
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
681
      committer = user_to_committer(user)
682 683 684 685 686 687 688
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref
      }
689

690 691 692
      options[:file] = {
        path: path
      }
693

694 695
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
696 697
  end

S
Stan Hu 已提交
698
  def user_to_committer(user)
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
    {
      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

717
  def merge(user, source_sha, target_branch, options = {})
718 719 720 721 722 723 724 725 726
    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?

727 728 729 730 731 732
    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
      )
733

734
      Rugged::Commit.create(rugged, actual_options)
735
    end
736 737
  end

738 739
  def revert(user, commit, base_branch, revert_tree_id = nil)
    source_sha = find_branch(base_branch).target
740
    revert_tree_id ||= check_revert_content(commit, base_branch)
741

742
    return false unless revert_tree_id
743

744
    commit_with_hooks(user, base_branch) do |ref|
745
      committer = user_to_committer(user)
746
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
747
        message: commit.revert_message,
748 749
        author: committer,
        committer: committer,
750
        tree: revert_tree_id,
751
        parents: [rugged.lookup(source_sha)],
R
Rubén Dávila 已提交
752
        update_ref: ref)
753
    end
754 755
  end

756 757 758 759 760 761 762 763 764 765 766 767 768 769
  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

770 771
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
772 773
  end

F
Florent (HP) 已提交
774 775 776 777 778
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
779
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
780 781 782 783 784
    else
      nil
    end
  end

S
Stan Hu 已提交
785
  def merge_base(first_commit_id, second_commit_id)
786 787
    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 已提交
788
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
789 790
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
791 792
  end

793 794 795 796 797
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end


798 799
  def search_files(query, ref)
    offset = 2
800
    args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{Regexp.escape(query)} #{ref || root_ref})
801 802 803
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

D
Dmitriy Zaporozhets 已提交
804
  def parse_search_result(result)
805 806
    ref = nil
    filename = nil
807
    basename = nil
808 809
    startline = 0

810
    result.each_line.each_with_index do |line, index|
811 812 813
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
814 815
        extname = File.extname(filename)
        basename = filename.sub(/#{extname}$/, '')
816 817 818 819
        break
      end
    end

820
    data = ""
821

822 823 824
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
825 826 827

    OpenStruct.new(
      filename: filename,
828
      basename: basename,
829 830 831 832 833 834
      ref: ref,
      startline: startline,
      data: data
    )
  end

835
  def fetch_ref(source_path, source_ref, target_ref)
836
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
837 838 839
    Gitlab::Popen.popen(args, path_to_repo)
  end

840
  def with_tmp_ref(oldrev = nil)
841 842 843
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

844
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
845 846 847 848
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
849 850 851 852 853 854
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
855 856
    update_autocrlf_option

857 858
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
859
    target_branch = find_branch(branch)
860
    was_empty = empty?
861

862 863
    if !was_empty && target_branch
      oldrev = target_branch.target
864 865
    end

866 867 868 869 870 871 872
    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
873

874
      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
875
        if was_empty || !target_branch
876 877
          # Create branch
          rugged.references.create(ref, newrev)
878
        else
879 880 881 882 883 884 885 886 887
          # 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
888 889
        end
      end
890 891

      newrev
892 893 894
    end
  end

895 896 897 898 899
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

900
  def main_language
J
Jeroen Bobbeldijk 已提交
901
    return if empty? || rugged.head_unborn?
902 903

    Linguist::Repository.new(rugged, rugged.head.target_id).language
904 905
  end

906
  def avatar
907 908
    return nil unless exists?

909 910 911 912 913 914 915
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
        blob_at_branch('master', file)
      end
    end
  end

916 917
  private

918 919 920
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
921
end