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
       readme version contribution_guide changelog
A
Alfredo Sumaran 已提交
248
       license_blob license_key gitignore)
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

A
Alfredo Sumaran 已提交
259 260 261 262
  def expire_gitignore
    cache.expire(:gitignore)
  end

D
Douwe Maan 已提交
263 264 265 266 267 268 269
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
270
    @local_branches = nil
D
Douwe Maan 已提交
271 272
  end

273
  def expire_cache(branch_name = nil, revision = nil)
274
    cache_keys.each do |key|
275 276
      cache.expire(key)
    end
277

278
    expire_branch_cache(branch_name)
279
    expire_avatar_cache(branch_name, revision)
280 281 282 283

    # This ensures this particular cache is flushed after the first commit to a
    # new repository.
    expire_emptiness_caches if empty?
284 285
    expire_branch_count_cache
    expire_tag_count_cache
286
  end
287

288 289 290 291 292 293 294 295 296 297 298 299
  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}")
300
    end
D
Dmitriy Zaporozhets 已提交
301 302
  end

303 304 305 306 307
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

308 309 310 311 312 313 314 315
  # 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

316 317 318 319 320
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

Y
Yorick Peterse 已提交
321 322 323 324 325 326 327 328 329 330
  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

331 332 333 334
  def lookup_cache
    @lookup_cache ||= {}
  end

335 336 337 338
  def expire_branch_names
    cache.expire(:branch_names)
  end

339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
  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

356 357 358 359 360 361 362 363
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
364 365
    expire_root_ref_cache
    expire_emptiness_caches
366 367
  end

368 369
  # Runs code just before a repository is deleted.
  def before_delete
370 371
    expire_exists_cache

372 373 374 375
    expire_cache if exists?

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

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
397 398
  end

399 400 401 402 403
  def before_import
    expire_emptiness_caches
    expire_exists_cache
  end

404 405 406
  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
407
    expire_exists_cache
408 409 410
  end

  # Runs code after a new commit has been pushed.
411 412
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
413 414 415 416
  end

  # Runs code after a new branch has been created.
  def after_create_branch
417
    expire_branches_cache
418
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
419
    expire_branch_count_cache
420 421
  end

422 423 424 425 426
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
  end

427 428 429
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
430
    expire_branch_count_cache
431
    expire_branches_cache
432 433
  end

434
  def method_missing(m, *args, &block)
435 436 437 438 439 440
    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
441 442
  end

443 444
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
445
  end
D
Dmitriy Zaporozhets 已提交
446 447

  def blob_at(sha, path)
448 449 450
    unless Gitlab::Git.blank_ref?(sha)
      Gitlab::Git::Blob.find(self, sha, path)
    end
D
Dmitriy Zaporozhets 已提交
451
  end
452

453 454 455 456
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

457
  def readme
458
    cache.fetch(:readme) { tree(:head).readme }
459
  end
460

461
  def version
462
    cache.fetch(:version) do
463
      tree(:head).blobs.find do |file|
464
        file.name.casecmp('version').zero?
465 466 467 468
      end
    end
  end

469
  def contribution_guide
470 471 472 473 474 475
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
476 477 478

  def changelog
    cache.fetch(:changelog) do
479
      file_on_head(/\A(changelog|history|changes|news)/i)
480
    end
481 482
  end

483
  def license_blob
484
    return nil unless head_exists?
485

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

491
  def license_key
492
    return nil unless head_exists?
493 494

    cache.fetch(:license_key) do
495
      Licensee.license(path).try(:key)
496
    end
497 498
  end

499 500 501 502 503 504 505 506
  def gitignore
    return nil if !exists? || empty?

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

507
  def gitlab_ci_yml
508
    return nil unless head_exists?
509 510 511 512

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

519
  def head_commit
520 521 522 523 524
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
525 526 527 528
  end

  def tree(sha = :head, path = nil)
    if sha == :head
529 530 531 532 533
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
534 535 536 537
    end

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

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

D
Dmitriy Zaporozhets 已提交
542 543 544 545 546
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
547
  end
D
Dmitriy Zaporozhets 已提交
548 549 550 551 552 553 554 555

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

      if submodule
        submodule['url']
      end
    end
  end
564 565

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

P
P.S.V.R 已提交
571 572 573 574
  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/)
575 576 577
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
578
    highest_branch_id = branch_ids.max || 0
579

P
P.S.V.R 已提交
580 581 582
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
583 584
  end

585
  # Remove archives older than 2 hours
586 587 588 589 590 591 592 593 594 595 596 597 598 599
  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
600 601

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

D
Dmitriy Zaporozhets 已提交
604
    commits.group_by(&:author_email).map do |email, commits|
605 606
      contributor = Gitlab::Contributor.new
      contributor.email = email
607

D
Dmitriy Zaporozhets 已提交
608
      commits.each do |commit|
609
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
610
          contributor.name = commit.author_name
611 612
        end

613
        contributor.commits += 1
614 615
      end

616 617
      contributor
    end
618
  end
D
Dmitriy Zaporozhets 已提交
619 620

  def blob_for_diff(commit, diff)
621
    blob_at(commit.id, diff.file_path)
D
Dmitriy Zaporozhets 已提交
622 623 624 625 626 627 628
  end

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

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

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

651 652
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
653
  end
654

655 656 657 658
  def local_branches
    @local_branches ||= rugged.branches.each(:local).map do |branch|
      Gitlab::Git::Branch.new(branch.name, branch.target)
    end
659 660
  end

661 662
  alias_method :branches, :local_branches

663 664 665 666 667
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
668
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
669 670
  end

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

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

698 699
      options[:file] = {
        content: content,
S
Stan Hu 已提交
700 701
        path: path,
        update: update
702
      }
703

704 705
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
706 707
  end

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

719 720 721
      options[:file] = {
        path: path
      }
722

723 724
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
725 726
  end

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

746
  def merge(user, source_sha, target_branch, options = {})
747 748 749 750 751 752 753 754 755
    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?

756 757 758 759 760 761
    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
      )
762

763
      Rugged::Commit.create(rugged, actual_options)
764
    end
765 766
  end

767 768
  def revert(user, commit, base_branch, revert_tree_id = nil)
    source_sha = find_branch(base_branch).target
769
    revert_tree_id ||= check_revert_content(commit, base_branch)
770

771
    return false unless revert_tree_id
772

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

P
P.S.V.R 已提交
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806
  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

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

    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 已提交
821 822 823
  def check_cherry_pick_content(commit, base_branch)
    source_sha = find_branch(base_branch).target
    args       = [commit.id, source_sha]
824
    args << 1 if commit.merge_commit?
P
P.S.V.R 已提交
825 826 827 828 829 830 831 832 833 834

    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

835 836
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
837 838
  end

F
Florent (HP) 已提交
839 840 841 842 843
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
844
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
845 846 847 848 849
    else
      nil
    end
  end

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

858 859 860 861 862
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end


863 864
  def search_files(query, ref)
    offset = 2
865
    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})
866 867 868
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

D
Dmitriy Zaporozhets 已提交
869
  def parse_search_result(result)
870 871
    ref = nil
    filename = nil
872
    basename = nil
873 874
    startline = 0

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

885
    data = ""
886

887 888 889
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
890 891 892

    OpenStruct.new(
      filename: filename,
893
      basename: basename,
894 895 896 897 898 899
      ref: ref,
      startline: startline,
      data: data
    )
  end

900
  def fetch_ref(source_path, source_ref, target_ref)
901
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
902 903 904
    Gitlab::Popen.popen(args, path_to_repo)
  end

905
  def with_tmp_ref(oldrev = nil)
906 907 908
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

909
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
910 911 912 913
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
914 915 916 917 918 919
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
920 921
    update_autocrlf_option

922 923
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
924
    target_branch = find_branch(branch)
925
    was_empty = empty?
926

927 928
    if !was_empty && target_branch
      oldrev = target_branch.target
929 930
    end

931 932 933 934 935 936 937
    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
938

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

      newrev
957 958 959
    end
  end

960 961 962 963 964
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

965 966 967 968 969 970 971 972 973 974
  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

975
  def avatar
976 977
    return nil unless exists?

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

985 986
  private

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

  def head_exists?
    exists? && !empty? && !rugged.head_unborn?
  end
994 995 996 997

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