repository.rb 24.1 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 475

  def changelog
    cache.fetch(:changelog) do
      tree(:head).blobs.find do |file|
476
        file.name =~ /\A(changelog|history|changes|news)/i
477 478
      end
    end
479 480
  end

481
  def license_blob
482
    return nil unless head_exists?
483

484
    cache.fetch(:license_blob) do
485 486
      tree(:head).blobs.find do |file|
        file.name =~ /\A(licen[sc]e|copying)(\..+|\z)/i
487
      end
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
  def gitlab_ci_yml
500
    return nil unless head_exists?
501 502 503 504

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

511
  def head_commit
512 513 514 515 516
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
517 518 519 520
  end

  def tree(sha = :head, path = nil)
    if sha == :head
521 522 523 524 525
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
526 527 528 529
    end

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

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

D
Dmitriy Zaporozhets 已提交
534 535 536 537 538
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
539
  end
D
Dmitriy Zaporozhets 已提交
540 541 542 543 544 545 546 547

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

      if submodule
        submodule['url']
      end
    end
  end
556 557

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

P
P.S.V.R 已提交
563 564 565 566
  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/)
567 568 569
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
570
    highest_branch_id = branch_ids.max || 0
571

P
P.S.V.R 已提交
572 573 574
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
575 576
  end

577
  # Remove archives older than 2 hours
578 579 580 581 582 583 584 585 586 587 588 589 590 591
  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
592 593

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

D
Dmitriy Zaporozhets 已提交
596
    commits.group_by(&:author_email).map do |email, commits|
597 598
      contributor = Gitlab::Contributor.new
      contributor.email = email
599

D
Dmitriy Zaporozhets 已提交
600
      commits.each do |commit|
601
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
602
          contributor.name = commit.author_name
603 604
        end

605
        contributor.commits += 1
606 607
      end

608 609
      contributor
    end
610
  end
D
Dmitriy Zaporozhets 已提交
611 612

  def blob_for_diff(commit, diff)
613
    blob_at(commit.id, diff.file_path)
D
Dmitriy Zaporozhets 已提交
614 615 616 617 618 619 620
  end

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

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

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

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

647 648 649 650
  def local_branches
    @local_branches ||= rugged.branches.each(:local).map do |branch|
      Gitlab::Git::Branch.new(branch.name, branch.target)
    end
651 652
  end

653 654
  alias_method :branches, :local_branches

655 656 657 658 659
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
660
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
661 662
  end

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

S
Stan Hu 已提交
679 680 681
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      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
      options[:file] = {
        content: content,
S
Stan Hu 已提交
692 693
        path: path,
        update: update
694
      }
695

696 697
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
698 699
  end

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

711 712 713
      options[:file] = {
        path: path
      }
714

715 716
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
717 718
  end

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

738
  def merge(user, source_sha, target_branch, options = {})
739 740 741 742 743 744 745 746 747
    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?

748 749 750 751 752 753
    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
      )
754

755
      Rugged::Commit.create(rugged, actual_options)
756
    end
757 758
  end

759 760
  def revert(user, commit, base_branch, revert_tree_id = nil)
    source_sha = find_branch(base_branch).target
761
    revert_tree_id ||= check_revert_content(commit, base_branch)
762

763
    return false unless revert_tree_id
764

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

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

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

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

    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

827 828
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
829 830
  end

F
Florent (HP) 已提交
831 832 833 834 835
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
836
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
837 838 839 840 841
    else
      nil
    end
  end

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

850 851 852 853 854
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end


855 856
  def search_files(query, ref)
    offset = 2
857
    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})
858 859 860
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

D
Dmitriy Zaporozhets 已提交
861
  def parse_search_result(result)
862 863
    ref = nil
    filename = nil
864
    basename = nil
865 866
    startline = 0

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

877
    data = ""
878

879 880 881
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
882 883 884

    OpenStruct.new(
      filename: filename,
885
      basename: basename,
886 887 888 889 890 891
      ref: ref,
      startline: startline,
      data: data
    )
  end

892
  def fetch_ref(source_path, source_ref, target_ref)
893
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
894 895 896
    Gitlab::Popen.popen(args, path_to_repo)
  end

897
  def with_tmp_ref(oldrev = nil)
898 899 900
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

901
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
902 903 904 905
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
906 907 908 909 910 911
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
912 913
    update_autocrlf_option

914 915
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
916
    target_branch = find_branch(branch)
917
    was_empty = empty?
918

919 920
    if !was_empty && target_branch
      oldrev = target_branch.target
921 922
    end

923 924 925 926 927 928 929
    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
930

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

      newrev
949 950 951
    end
  end

952 953 954 955 956
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

957 958 959 960 961 962 963 964 965 966
  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

967
  def main_language
968
    return unless head_exists?
969 970

    Linguist::Repository.new(rugged, rugged.head.target_id).language
971 972
  end

973
  def avatar
974 975
    return nil unless exists?

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

983 984
  private

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

  def head_exists?
    exists? && !empty? && !rugged.head_unborn?
  end
992
end