repository.rb 24.0 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 198
  end

  def tag_names
199
    cache.fetch(:tag_names) { raw_repository.tag_names }
200 201
  end

202
  def commit_count
203
    cache.fetch(:commit_count) do
204
      begin
205
        raw_repository.commit_count(self.root_ref)
206 207 208
      rescue
        0
      end
209
    end
210 211
  end

Y
Yorick Peterse 已提交
212
  def branch_count
213
    @branch_count ||= cache.fetch(:branch_count) { branches.size }
Y
Yorick Peterse 已提交
214 215 216 217 218 219
  end

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

220 221 222
  # Return repo size in megabytes
  # Cached in redis
  def size
223
    cache.fetch(:size) { raw_repository.size }
224
  end
225

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

237 238 239
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
240

241
  def cache_keys
242
    %i(size branch_names tag_names commit_count
243 244
       readme version contribution_guide changelog
       license_blob license_key)
245
  end
246

247 248 249 250 251 252 253 254
  def build_cache
    cache_keys.each do |key|
      unless cache.exist?(key)
        send(key)
      end
    end
  end

D
Douwe Maan 已提交
255 256 257 258 259 260 261
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
262
    @local_branches = nil
D
Douwe Maan 已提交
263 264
  end

265
  def expire_cache(branch_name = nil, revision = nil)
266
    cache_keys.each do |key|
267 268
      cache.expire(key)
    end
269

270
    expire_branch_cache(branch_name)
271
    expire_avatar_cache(branch_name, revision)
272 273 274 275

    # This ensures this particular cache is flushed after the first commit to a
    # new repository.
    expire_emptiness_caches if empty?
276 277
    expire_branch_count_cache
    expire_tag_count_cache
278
  end
279

280 281 282 283 284 285 286 287 288 289 290 291
  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}")
292
    end
D
Dmitriy Zaporozhets 已提交
293 294
  end

295 296 297 298 299
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

300 301 302 303 304 305 306 307
  # 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

308 309 310 311 312
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

Y
Yorick Peterse 已提交
313 314 315 316 317 318 319 320 321 322
  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

323 324 325 326
  def lookup_cache
    @lookup_cache ||= {}
  end

327 328 329 330
  def expire_branch_names
    cache.expire(:branch_names)
  end

331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
  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

348 349 350 351 352 353 354 355
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
356 357
    expire_root_ref_cache
    expire_emptiness_caches
358 359
  end

360 361
  # Runs code just before a repository is deleted.
  def before_delete
362 363
    expire_exists_cache

364 365 366 367
    expire_cache if exists?

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

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
389 390
  end

391 392 393 394 395
  def before_import
    expire_emptiness_caches
    expire_exists_cache
  end

396 397 398
  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
399
    expire_exists_cache
400 401 402
  end

  # Runs code after a new commit has been pushed.
403 404
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
405 406 407 408
  end

  # Runs code after a new branch has been created.
  def after_create_branch
409
    expire_branches_cache
410
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
411
    expire_branch_count_cache
412 413
  end

414 415 416 417 418
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
  end

419 420 421
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
422
    expire_branch_count_cache
423
    expire_branches_cache
424 425
  end

426
  def method_missing(m, *args, &block)
427 428 429 430 431 432
    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
433 434
  end

435 436
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
437
  end
D
Dmitriy Zaporozhets 已提交
438 439

  def blob_at(sha, path)
440 441 442
    unless Gitlab::Git.blank_ref?(sha)
      Gitlab::Git::Blob.find(self, sha, path)
    end
D
Dmitriy Zaporozhets 已提交
443
  end
444

445 446 447 448
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

449
  def readme
450
    cache.fetch(:readme) { tree(:head).readme }
451
  end
452

453
  def version
454
    cache.fetch(:version) do
455
      tree(:head).blobs.find do |file|
456
        file.name.casecmp('version').zero?
457 458 459 460
      end
    end
  end

461
  def contribution_guide
462 463 464 465 466 467
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
468 469 470 471

  def changelog
    cache.fetch(:changelog) do
      tree(:head).blobs.find do |file|
472
        file.name =~ /\A(changelog|history|changes|news)/i
473 474
      end
    end
475 476
  end

477 478
  def license_blob
    return nil if !exists? || empty?
479

480
    cache.fetch(:license_blob) do
481 482
      tree(:head).blobs.find do |file|
        file.name =~ /\A(licen[sc]e|copying)(\..+|\z)/i
483
      end
484 485
    end
  end
Z
Zeger-Jan van de Weg 已提交
486

487 488 489 490
  def license_key
    return nil if !exists? || empty?

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

495
  def gitlab_ci_yml
496
    return nil if !exists? || empty?
497 498 499 500

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

507
  def head_commit
508 509 510 511 512
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
513 514 515 516
  end

  def tree(sha = :head, path = nil)
    if sha == :head
517 518 519 520 521
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
522 523 524 525
    end

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

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

D
Dmitriy Zaporozhets 已提交
530 531 532 533 534
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
535
  end
D
Dmitriy Zaporozhets 已提交
536 537 538 539 540 541 542 543

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
544
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
545 546 547 548 549 550 551
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
552 553

  def last_commit_for_path(sha, path)
554
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
555 556
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
557
  end
558

P
P.S.V.R 已提交
559 560 561 562
  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/)
563 564 565
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
566
    highest_branch_id = branch_ids.max || 0
567

P
P.S.V.R 已提交
568 569 570
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
571 572
  end

573
  # Remove archives older than 2 hours
574 575 576 577 578 579 580 581 582 583 584 585 586 587
  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
588 589

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

D
Dmitriy Zaporozhets 已提交
592
    commits.group_by(&:author_email).map do |email, commits|
593 594
      contributor = Gitlab::Contributor.new
      contributor.email = email
595

D
Dmitriy Zaporozhets 已提交
596
      commits.each do |commit|
597
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
598
          contributor.name = commit.author_name
599 600
        end

601
        contributor.commits += 1
602 603
      end

604 605
      contributor
    end
606
  end
D
Dmitriy Zaporozhets 已提交
607 608

  def blob_for_diff(commit, diff)
609
    blob_at(commit.id, diff.file_path)
D
Dmitriy Zaporozhets 已提交
610 611 612 613 614 615 616
  end

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

618 619
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
620 621 622 623 624 625 626 627 628 629 630 631 632 633
    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 已提交
634

635 636 637
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
638

639 640
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
641
  end
642

643 644 645 646
  def local_branches
    @local_branches ||= rugged.branches.each(:local).map do |branch|
      Gitlab::Git::Branch.new(branch.name, branch.target)
    end
647 648
  end

649 650
  alias_method :branches, :local_branches

651 652 653 654 655
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
656
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
657 658
  end

S
Stan Hu 已提交
659
  def commit_dir(user, path, message, branch)
660
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
661 662 663 664 665 666 667 668 669 670 671 672 673
      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
674

S
Stan Hu 已提交
675 676 677
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
678 679 680 681 682 683 684
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
      }
685

686 687
      options[:file] = {
        content: content,
S
Stan Hu 已提交
688 689
        path: path,
        update: update
690
      }
691

692 693
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
694 695
  end

696
  def remove_file(user, path, message, branch)
697
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
698
      committer = user_to_committer(user)
699 700 701 702 703 704 705
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref
      }
706

707 708 709
      options[:file] = {
        path: path
      }
710

711 712
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
713 714
  end

S
Stan Hu 已提交
715
  def user_to_committer(user)
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733
    {
      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

734
  def merge(user, source_sha, target_branch, options = {})
735 736 737 738 739 740 741 742 743
    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?

744 745 746 747 748 749
    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
      )
750

751
      Rugged::Commit.create(rugged, actual_options)
752
    end
753 754
  end

755 756
  def revert(user, commit, base_branch, revert_tree_id = nil)
    source_sha = find_branch(base_branch).target
757
    revert_tree_id ||= check_revert_content(commit, base_branch)
758

759
    return false unless revert_tree_id
760

761
    commit_with_hooks(user, base_branch) do |ref|
762
      committer = user_to_committer(user)
763
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
764
        message: commit.revert_message,
765 766
        author: committer,
        committer: committer,
767
        tree: revert_tree_id,
768
        parents: [rugged.lookup(source_sha)],
R
Rubén Dávila 已提交
769
        update_ref: ref)
770
    end
771 772
  end

P
P.S.V.R 已提交
773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
  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

795 796 797
  def check_revert_content(commit, base_branch)
    source_sha = find_branch(base_branch).target
    args       = [commit.id, source_sha]
798
    args << { mainline: 1 } if commit.merge_commit?
799 800 801 802 803 804 805 806 807 808

    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 已提交
809 810 811
  def check_cherry_pick_content(commit, base_branch)
    source_sha = find_branch(base_branch).target
    args       = [commit.id, source_sha]
812
    args << 1 if commit.merge_commit?
P
P.S.V.R 已提交
813 814 815 816 817 818 819 820 821 822

    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

823 824
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
825 826
  end

F
Florent (HP) 已提交
827 828 829 830 831
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
832
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
833 834 835 836 837
    else
      nil
    end
  end

S
Stan Hu 已提交
838
  def merge_base(first_commit_id, second_commit_id)
839 840
    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 已提交
841
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
842 843
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
844 845
  end

846 847 848 849 850
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end


851 852
  def search_files(query, ref)
    offset = 2
853
    args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{Regexp.escape(query)} #{ref || root_ref})
854 855 856
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

D
Dmitriy Zaporozhets 已提交
857
  def parse_search_result(result)
858 859
    ref = nil
    filename = nil
860
    basename = nil
861 862
    startline = 0

863
    result.each_line.each_with_index do |line, index|
864 865 866
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
867 868
        extname = File.extname(filename)
        basename = filename.sub(/#{extname}$/, '')
869 870 871 872
        break
      end
    end

873
    data = ""
874

875 876 877
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
878 879 880

    OpenStruct.new(
      filename: filename,
881
      basename: basename,
882 883 884 885 886 887
      ref: ref,
      startline: startline,
      data: data
    )
  end

888
  def fetch_ref(source_path, source_ref, target_ref)
889
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
890 891 892
    Gitlab::Popen.popen(args, path_to_repo)
  end

893
  def with_tmp_ref(oldrev = nil)
894 895 896
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

897
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
898 899 900 901
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
902 903 904 905 906 907
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
908 909
    update_autocrlf_option

910 911
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
912
    target_branch = find_branch(branch)
913
    was_empty = empty?
914

915 916
    if !was_empty && target_branch
      oldrev = target_branch.target
917 918
    end

919 920 921 922 923 924 925
    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
926

927
      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
928
        if was_empty || !target_branch
929 930
          # Create branch
          rugged.references.create(ref, newrev)
931
        else
932 933 934 935 936 937 938 939 940
          # 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
941 942
        end
      end
943 944

      newrev
945 946 947
    end
  end

948 949 950 951 952
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

953 954 955 956 957 958 959 960 961 962
  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

963
  def main_language
J
Jeroen Bobbeldijk 已提交
964
    return if empty? || rugged.head_unborn?
965 966

    Linguist::Repository.new(rugged, rugged.head.target_id).language
967 968
  end

969
  def avatar
970 971
    return nil unless exists?

972 973 974 975 976 977 978
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
        blob_at_branch('master', file)
      end
    end
  end

979 980
  private

981 982 983
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
984
end