repository.rb 24.2 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
  def self.clean_old_archives
15 16
    Gitlab::Metrics.measure(:clean_old_archives) do
      repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
J
Jacob Vosmaer 已提交
17

18
      return unless File.directory?(repository_downloads_path)
J
Jacob Vosmaer 已提交
19

20 21
      Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
    end
J
Jacob Vosmaer 已提交
22 23
  end

24
  def initialize(path_with_namespace, project)
25
    @path_with_namespace = path_with_namespace
26
    @project = project
27
  end
28

29 30
  def raw_repository
    return nil unless path_with_namespace
31

32
    @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
33 34
  end

35 36 37 38
  def update_autocrlf_option
    raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
  end

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

46
  def exists?
47
    return @exists unless @exists.nil?
48

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

  def empty?
59 60 61
    return @empty unless @empty.nil?

    @empty = cache.fetch(:empty?) { raw_repository.empty? }
62 63
  end

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

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

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

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

    commits = Gitlab::Git::Commit.where(options)
106
    commits = Commit.decorate(commits, @project) if commits.present?
107 108 109
    commits
  end

110 111
  def commits_between(from, to)
    commits = Gitlab::Git::Commit.between(raw_repository, from, to)
112
    commits = Commit.decorate(commits, @project) if commits.present?
113 114 115
    commits
  end

116 117 118
  def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
    ref ||= root_ref

119
    # Limited to 1000 commits for now, could be parameterized?
120 121
    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?
122

123 124
    git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
    commits = git_log_results.map { |c| commit(c) }
125
    commits
126 127
  end

128
  def find_branch(name)
129
    raw_repository.branches.find { |branch| branch.name == name }
130 131 132
  end

  def find_tag(name)
133
    raw_repository.tags.find { |tag| tag.name == name }
134 135
  end

136 137 138 139 140 141 142 143 144 145
  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
146

147
    after_create_branch
148
    find_branch(branch_name)
149 150
  end

151 152 153 154 155 156
  def add_tag(user, tag_name, target, message = nil)
    oldrev = Gitlab::Git::BLANK_SHA
    ref    = Gitlab::Git::TAG_REF_PREFIX + tag_name
    target = commit(target).try(:id)

    return false unless target
157

158 159
    options = { message: message, tagger: user_to_committer(user) } if message

160 161
    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
      rugged.tags.create(tag_name, target, options)
162
    end
163

164
    find_tag(tag_name)
165 166
  end

167
  def rm_branch(user, branch_name)
168
    before_remove_branch
169

170 171 172 173 174 175 176 177
    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
178

179
    after_remove_branch
180
    true
181 182
  end

183
  def rm_tag(tag_name)
Y
Yorick Peterse 已提交
184
    before_remove_tag
185

R
Robert Schilling 已提交
186 187 188 189 190 191
    begin
      rugged.tags.delete(tag_name)
      true
    rescue Rugged::ReferenceError
      false
    end
192 193
  end

194
  def branch_names
195
    cache.fetch(:branch_names) { branches.map(&:name) }
196 197
  end

198 199 200 201
  def branch_exists?(branch_name)
    branch_names.include?(branch_name)
  end

202
  def tag_names
203
    cache.fetch(:tag_names) { raw_repository.tag_names }
204 205
  end

206
  def commit_count
207
    cache.fetch(:commit_count) do
208
      begin
209
        raw_repository.commit_count(self.root_ref)
210 211 212
      rescue
        0
      end
213
    end
214 215
  end

Y
Yorick Peterse 已提交
216
  def branch_count
217
    @branch_count ||= cache.fetch(:branch_count) { branches.size }
Y
Yorick Peterse 已提交
218 219 220 221 222 223
  end

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

224 225 226
  # Return repo size in megabytes
  # Cached in redis
  def size
227
    cache.fetch(:size) { raw_repository.size }
228
  end
229

230
  def diverging_commit_counts(branch)
231
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
J
Jeff Stubler 已提交
232
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
233 234
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
235 236 237 238 239
      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)
240

241 242 243
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
244

245
  def cache_keys
246
    %i(size branch_names tag_names commit_count
247 248
       readme version contribution_guide changelog
       license_blob license_key)
249
  end
250

251 252 253 254 255 256 257 258
  def build_cache
    cache_keys.each do |key|
      unless cache.exist?(key)
        send(key)
      end
    end
  end

D
Douwe Maan 已提交
259 260 261 262 263 264 265
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
266
    @local_branches = nil
D
Douwe Maan 已提交
267 268
  end

269
  def expire_cache(branch_name = nil, revision = nil)
270
    cache_keys.each do |key|
271 272
      cache.expire(key)
    end
273

274
    expire_branch_cache(branch_name)
275
    expire_avatar_cache(branch_name, revision)
276 277 278 279

    # This ensures this particular cache is flushed after the first commit to a
    # new repository.
    expire_emptiness_caches if empty?
280 281
    expire_branch_count_cache
    expire_tag_count_cache
282
  end
283

284 285 286 287 288 289 290 291 292 293 294 295
  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}")
296
    end
D
Dmitriy Zaporozhets 已提交
297 298
  end

299 300 301 302 303
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

304 305 306 307 308 309 310 311
  # 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

312 313 314 315 316
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

Y
Yorick Peterse 已提交
317 318 319 320 321 322 323 324 325 326
  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

327 328 329 330
  def lookup_cache
    @lookup_cache ||= {}
  end

331 332 333 334
  def expire_branch_names
    cache.expire(:branch_names)
  end

335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
  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

352 353 354 355 356 357 358 359
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
360 361
    expire_root_ref_cache
    expire_emptiness_caches
362 363
  end

364 365
  # Runs code just before a repository is deleted.
  def before_delete
366 367
    expire_exists_cache

368 369 370 371
    expire_cache if exists?

    expire_root_ref_cache
    expire_emptiness_caches
372
    expire_exists_cache
373 374 375 376 377 378 379 380 381
  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 已提交
382 383
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
384
    expire_cache
385
    expire_tags_cache
Y
Yorick Peterse 已提交
386 387 388 389 390 391 392
    expire_tag_count_cache
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
393 394
  end

395 396 397 398 399
  def before_import
    expire_emptiness_caches
    expire_exists_cache
  end

400 401 402
  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
403
    expire_exists_cache
404 405 406
  end

  # Runs code after a new commit has been pushed.
407 408
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
409 410 411 412
  end

  # Runs code after a new branch has been created.
  def after_create_branch
413
    expire_branches_cache
414
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
415
    expire_branch_count_cache
416 417
  end

418 419 420 421 422
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
  end

423 424 425
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
426
    expire_branch_count_cache
427
    expire_branches_cache
428 429
  end

430
  def method_missing(m, *args, &block)
431 432 433 434 435 436
    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
437 438
  end

439 440
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
441
  end
D
Dmitriy Zaporozhets 已提交
442 443

  def blob_at(sha, path)
444 445 446
    unless Gitlab::Git.blank_ref?(sha)
      Gitlab::Git::Blob.find(self, sha, path)
    end
D
Dmitriy Zaporozhets 已提交
447
  end
448

449 450 451 452
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

453
  def readme
454
    cache.fetch(:readme) { tree(:head).readme }
455
  end
456

457
  def version
458
    cache.fetch(:version) do
459
      tree(:head).blobs.find do |file|
460
        file.name.casecmp('version').zero?
461 462 463 464
      end
    end
  end

465
  def contribution_guide
466 467 468 469 470 471
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
472 473 474

  def changelog
    cache.fetch(:changelog) do
475
      file_on_head(/\A(changelog|history|changes|news)/i)
476
    end
477 478
  end

479
  def license_blob
480
    return nil unless head_exists?
481

482
    cache.fetch(:license_blob) do
483
      file_on_head(/\A(licen[sc]e|copying)(\..+|\z)/i)
484 485
    end
  end
Z
Zeger-Jan van de Weg 已提交
486

487
  def license_key
488
    return nil unless head_exists?
489 490

    cache.fetch(:license_key) do
491
      Licensee.license(path).try(:key)
492
    end
493 494
  end

495 496 497 498 499 500 501 502
  def gitignore
    return nil if !exists? || empty?

    cache.fetch(:gitignore) do
      file_on_head(/\A\.gitignore\z/)
    end
  end

503
  def gitlab_ci_yml
504
    return nil unless head_exists?
505 506 507 508

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

515
  def head_commit
516 517 518 519 520
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
521 522 523 524
  end

  def tree(sha = :head, path = nil)
    if sha == :head
525 526 527 528 529
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
530 531 532 533
    end

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

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

D
Dmitriy Zaporozhets 已提交
538 539 540 541 542
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
543
  end
D
Dmitriy Zaporozhets 已提交
544 545 546 547 548 549 550 551

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
552
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
553 554 555 556 557 558 559
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
560 561

  def last_commit_for_path(sha, path)
562
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
563 564
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
565
  end
566

P
P.S.V.R 已提交
567 568 569 570
  def next_branch(name, opts={})
    branch_ids = self.branch_names.map do |n|
      next 1 if n == name
      result = n.match(/\A#{name}-([0-9]+)\z/)
571 572 573
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
574
    highest_branch_id = branch_ids.max || 0
575

P
P.S.V.R 已提交
576 577 578
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
579 580
  end

581
  # Remove archives older than 2 hours
582 583 584 585 586 587 588 589 590 591 592 593 594 595
  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
596 597

  def contributors
598
    commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
599

D
Dmitriy Zaporozhets 已提交
600
    commits.group_by(&:author_email).map do |email, commits|
601 602
      contributor = Gitlab::Contributor.new
      contributor.email = email
603

D
Dmitriy Zaporozhets 已提交
604
      commits.each do |commit|
605
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
606
          contributor.name = commit.author_name
607 608
        end

609
        contributor.commits += 1
610 611
      end

612 613
      contributor
    end
614
  end
D
Dmitriy Zaporozhets 已提交
615 616

  def blob_for_diff(commit, diff)
617
    blob_at(commit.id, diff.file_path)
D
Dmitriy Zaporozhets 已提交
618 619 620 621 622 623 624
  end

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

626 627
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
628 629 630 631 632 633 634 635 636 637 638 639 640 641
    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 已提交
642

643 644 645
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
646

647 648
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
649
  end
650

651 652 653 654
  def local_branches
    @local_branches ||= rugged.branches.each(:local).map do |branch|
      Gitlab::Git::Branch.new(branch.name, branch.target)
    end
655 656
  end

657 658
  alias_method :branches, :local_branches

659 660 661 662 663
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
664
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
665 666
  end

S
Stan Hu 已提交
667
  def commit_dir(user, path, message, branch)
668
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
669 670 671 672 673 674 675 676 677 678 679 680 681
      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
682

S
Stan Hu 已提交
683 684 685
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
686 687 688 689 690 691 692
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
      }
693

694 695
      options[:file] = {
        content: content,
S
Stan Hu 已提交
696 697
        path: path,
        update: update
698
      }
699

700 701
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
702 703
  end

704
  def remove_file(user, path, message, branch)
705
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
706
      committer = user_to_committer(user)
707 708 709 710 711 712 713
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref
      }
714

715 716 717
      options[:file] = {
        path: path
      }
718

719 720
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
721 722
  end

S
Stan Hu 已提交
723
  def user_to_committer(user)
724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741
    {
      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

742
  def merge(user, source_sha, target_branch, options = {})
743 744 745 746 747 748 749 750 751
    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?

752 753 754 755 756 757
    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
      )
758

759
      Rugged::Commit.create(rugged, actual_options)
760
    end
761 762
  end

763 764
  def revert(user, commit, base_branch, revert_tree_id = nil)
    source_sha = find_branch(base_branch).target
765
    revert_tree_id ||= check_revert_content(commit, base_branch)
766

767
    return false unless revert_tree_id
768

769
    commit_with_hooks(user, base_branch) do |ref|
770
      committer = user_to_committer(user)
771
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
772
        message: commit.revert_message,
773 774
        author: committer,
        committer: committer,
775
        tree: revert_tree_id,
776
        parents: [rugged.lookup(source_sha)],
R
Rubén Dávila 已提交
777
        update_ref: ref)
778
    end
779 780
  end

P
P.S.V.R 已提交
781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802
  def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
    source_sha = find_branch(base_branch).target
    cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)

    return false unless cherry_pick_tree_id

    commit_with_hooks(user, base_branch) do |ref|
      committer = user_to_committer(user)
      source_sha = Rugged::Commit.create(rugged,
        message: commit.message,
        author: {
          email: commit.author_email,
          name: commit.author_name,
          time: commit.authored_date
        },
        committer: committer,
        tree: cherry_pick_tree_id,
        parents: [rugged.lookup(source_sha)],
        update_ref: ref)
    end
  end

803 804 805
  def check_revert_content(commit, base_branch)
    source_sha = find_branch(base_branch).target
    args       = [commit.id, source_sha]
806
    args << { mainline: 1 } if commit.merge_commit?
807 808 809 810 811 812 813 814 815 816

    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

P
P.S.V.R 已提交
817 818 819
  def check_cherry_pick_content(commit, base_branch)
    source_sha = find_branch(base_branch).target
    args       = [commit.id, source_sha]
820
    args << 1 if commit.merge_commit?
P
P.S.V.R 已提交
821 822 823 824 825 826 827 828 829 830

    cherry_pick_index = rugged.cherrypick_commit(*args)
    return false if cherry_pick_index.conflicts?

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

    tree_id
  end

831 832
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
833 834
  end

F
Florent (HP) 已提交
835 836 837 838 839
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
840
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
841 842 843 844 845
    else
      nil
    end
  end

S
Stan Hu 已提交
846
  def merge_base(first_commit_id, second_commit_id)
847 848
    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 已提交
849
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
850 851
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
852 853
  end

854 855 856 857 858
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end


859 860
  def search_files(query, ref)
    offset = 2
861
    args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
862 863 864
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

D
Dmitriy Zaporozhets 已提交
865
  def parse_search_result(result)
866 867
    ref = nil
    filename = nil
868
    basename = nil
869 870
    startline = 0

871
    result.each_line.each_with_index do |line, index|
872 873 874
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
875 876
        extname = File.extname(filename)
        basename = filename.sub(/#{extname}$/, '')
877 878 879 880
        break
      end
    end

881
    data = ""
882

883 884 885
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
886 887 888

    OpenStruct.new(
      filename: filename,
889
      basename: basename,
890 891 892 893 894 895
      ref: ref,
      startline: startline,
      data: data
    )
  end

896
  def fetch_ref(source_path, source_ref, target_ref)
897
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
898 899 900
    Gitlab::Popen.popen(args, path_to_repo)
  end

901
  def with_tmp_ref(oldrev = nil)
902 903 904
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

905
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
906 907 908 909
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
910 911 912 913 914 915
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
916 917
    update_autocrlf_option

918 919
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
920
    target_branch = find_branch(branch)
921
    was_empty = empty?
922

923 924
    if !was_empty && target_branch
      oldrev = target_branch.target
925 926
    end

927 928 929 930 931 932 933
    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
934

935
      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
936
        if was_empty || !target_branch
937 938
          # Create branch
          rugged.references.create(ref, newrev)
939
        else
940 941 942 943 944 945 946 947 948
          # 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
949 950
        end
      end
951 952

      newrev
953 954 955
    end
  end

956 957 958 959 960
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

961 962 963 964 965 966 967 968 969 970
  def copy_gitattributes(ref)
    actual_ref = ref || root_ref
    begin
      raw_repository.copy_gitattributes(actual_ref)
      true
    rescue Gitlab::Git::Repository::InvalidRef
      false
    end
  end

971
  def main_language
972
    return unless head_exists?
973 974

    Linguist::Repository.new(rugged, rugged.head.target_id).language
975 976
  end

977
  def avatar
978 979
    return nil unless exists?

980 981 982 983 984 985 986
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
        blob_at_branch('master', file)
      end
    end
  end

987 988
  private

989 990 991
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
992 993 994 995

  def head_exists?
    exists? && !empty? && !rugged.head_unborn?
  end
996 997 998 999

  def file_on_head(regex)
    tree(:head).blobs.find { |file| file.name =~ regex }
  end
1000
end