repository.rb 25.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
    @path_to_repo ||= File.expand_path(
42
      File.join(@project.repository_storage_path, path_with_namespace + ".git")
43
    )
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
    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 195 196 197
  def ref_names
    branch_names + tag_names
  end

198
  def branch_names
P
Paco Guzman 已提交
199
    @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) }
200 201
  end

202 203 204 205
  def branch_exists?(branch_name)
    branch_names.include?(branch_name)
  end

206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
  def ref_exists?(ref)
    rugged.references.exist?(ref)
  end

  def keep_around(sha)
    return unless sha && commit(sha)

    return if kept_around?(sha)

    rugged.references.create(keep_around_ref_name(sha), sha)
  end

  def kept_around?(sha)
    ref_exists?(keep_around_ref_name(sha))
  end

  def keep_around_ref_name(sha)
    "refs/keep-around/#{sha}"
  end

226
  def tag_names
227
    cache.fetch(:tag_names) { raw_repository.tag_names }
228 229
  end

230
  def commit_count
231
    cache.fetch(:commit_count) do
232
      begin
233
        raw_repository.commit_count(self.root_ref)
234 235 236
      rescue
        0
      end
237
    end
238 239
  end

Y
Yorick Peterse 已提交
240
  def branch_count
241
    @branch_count ||= cache.fetch(:branch_count) { branches.size }
Y
Yorick Peterse 已提交
242 243 244 245 246 247
  end

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

248 249 250
  # Return repo size in megabytes
  # Cached in redis
  def size
251
    cache.fetch(:size) { raw_repository.size }
252
  end
253

254
  def diverging_commit_counts(branch)
255
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
J
Jeff Stubler 已提交
256
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
257 258
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
259 260 261 262 263
      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)
264

265 266 267
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
268

269
  # Keys for data that can be affected for any commit push.
270
  def cache_keys
271
    %i(size commit_count
272
       readme version contribution_guide changelog
A
Alfredo Sumaran 已提交
273
       license_blob license_key gitignore)
274
  end
275

276 277 278 279 280
  # Keys for data on branch/tag operations.
  def cache_keys_for_branches_and_tags
    %i(branch_names tag_names branch_count tag_count)
  end

281
  def build_cache
282
    (cache_keys + cache_keys_for_branches_and_tags).each do |key|
283 284 285 286 287 288
      unless cache.exist?(key)
        send(key)
      end
    end
  end

D
Douwe Maan 已提交
289 290 291 292 293 294 295
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
P
Paco Guzman 已提交
296
    @branch_names = nil
297
    @local_branches = nil
D
Douwe Maan 已提交
298 299
  end

300
  def expire_cache(branch_name = nil, revision = nil)
301
    cache_keys.each do |key|
302 303
      cache.expire(key)
    end
304

305
    expire_branch_cache(branch_name)
306
    expire_avatar_cache(branch_name, revision)
307 308 309 310

    # This ensures this particular cache is flushed after the first commit to a
    # new repository.
    expire_emptiness_caches if empty?
311
  end
312

313 314 315 316 317 318 319 320 321 322 323 324
  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}")
325
    end
D
Dmitriy Zaporozhets 已提交
326 327
  end

328 329 330 331 332
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

333 334 335 336 337 338 339 340
  # 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

341 342 343 344 345
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

Y
Yorick Peterse 已提交
346 347 348 349 350 351 352 353 354 355
  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

356 357 358 359
  def lookup_cache
    @lookup_cache ||= {}
  end

360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
  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

377 378 379 380 381 382 383 384
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
385 386
    expire_root_ref_cache
    expire_emptiness_caches
387 388
  end

389 390
  # Runs code just before a repository is deleted.
  def before_delete
391 392
    expire_exists_cache

393 394 395 396
    expire_cache if exists?

    expire_root_ref_cache
    expire_emptiness_caches
397
    expire_exists_cache
398 399 400 401 402 403 404 405 406
  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 已提交
407 408
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
409
    expire_cache
410
    expire_tags_cache
Y
Yorick Peterse 已提交
411 412 413 414 415 416 417
    expire_tag_count_cache
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
418 419
  end

420 421 422 423 424
  def before_import
    expire_emptiness_caches
    expire_exists_cache
  end

425 426 427
  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
428
    expire_exists_cache
429 430 431
  end

  # Runs code after a new commit has been pushed.
432 433
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
434 435 436 437
  end

  # Runs code after a new branch has been created.
  def after_create_branch
438
    expire_branches_cache
439
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
440
    expire_branch_count_cache
441 442
  end

443 444 445 446 447
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
  end

448 449 450
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
451
    expire_branch_count_cache
452
    expire_branches_cache
453 454
  end

455
  def method_missing(m, *args, &block)
456 457 458 459 460 461
    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
462 463
  end

464 465
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
466
  end
D
Dmitriy Zaporozhets 已提交
467 468

  def blob_at(sha, path)
469
    unless Gitlab::Git.blank_ref?(sha)
470
      Blob.decorate(Gitlab::Git::Blob.find(self, sha, path))
471
    end
D
Dmitriy Zaporozhets 已提交
472
  end
473

474 475 476 477
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

478
  def readme
479
    cache.fetch(:readme) { tree(:head).readme }
480
  end
481

482
  def version
483
    cache.fetch(:version) do
484
      tree(:head).blobs.find do |file|
485
        file.name.casecmp('version').zero?
486 487 488 489
      end
    end
  end

490
  def contribution_guide
491 492 493 494 495 496
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
497 498 499

  def changelog
    cache.fetch(:changelog) do
500
      file_on_head(/\A(changelog|history|changes|news)/i)
501
    end
502 503
  end

504
  def license_blob
505
    return nil unless head_exists?
506

507
    cache.fetch(:license_blob) do
508
      file_on_head(/\A(licen[sc]e|copying)(\..+|\z)/i)
509 510
    end
  end
Z
Zeger-Jan van de Weg 已提交
511

512
  def license_key
513
    return nil unless head_exists?
514 515

    cache.fetch(:license_key) do
516
      Licensee.license(path).try(:key)
517
    end
518 519
  end

520 521 522 523 524 525 526 527
  def gitignore
    return nil if !exists? || empty?

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

528
  def gitlab_ci_yml
529
    return nil unless head_exists?
530 531 532 533

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

540
  def head_commit
541 542 543 544 545
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
546 547 548 549
  end

  def tree(sha = :head, path = nil)
    if sha == :head
550 551 552 553 554
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
555 556 557 558
    end

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

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

D
Dmitriy Zaporozhets 已提交
563 564 565 566 567
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
568
  end
D
Dmitriy Zaporozhets 已提交
569 570 571 572 573 574 575 576

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
577
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
578 579 580 581 582 583 584
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
585 586

  def last_commit_for_path(sha, path)
587
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
588 589
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
590
  end
591

P
P.S.V.R 已提交
592 593 594 595
  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/)
596 597 598
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
599
    highest_branch_id = branch_ids.max || 0
600

P
P.S.V.R 已提交
601 602 603
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
604 605
  end

606
  # Remove archives older than 2 hours
607 608 609 610 611 612 613 614 615 616 617 618 619 620
  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
621

622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
  def tags_sorted_by(value)
    case value
    when 'name'
      # Would be better to use `sort_by` but `version_sorter` only exposes
      # `sort` and `rsort`
      VersionSorter.rsort(tag_names).map { |tag_name| find_tag(tag_name) }
    when 'updated_desc'
      tags_sorted_by_committed_date.reverse
    when 'updated_asc'
      tags_sorted_by_committed_date
    else
      tags
    end
  end

637
  def contributors
638
    commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
639

D
Dmitriy Zaporozhets 已提交
640
    commits.group_by(&:author_email).map do |email, commits|
641 642
      contributor = Gitlab::Contributor.new
      contributor.email = email
643

D
Dmitriy Zaporozhets 已提交
644
      commits.each do |commit|
645
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
646
          contributor.name = commit.author_name
647 648
        end

649
        contributor.commits += 1
650 651
      end

652 653
      contributor
    end
654
  end
D
Dmitriy Zaporozhets 已提交
655 656

  def blob_for_diff(commit, diff)
657
    blob_at(commit.id, diff.file_path)
D
Dmitriy Zaporozhets 已提交
658 659 660 661 662 663 664
  end

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

666 667
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
668 669 670 671 672 673 674 675 676 677 678 679 680 681
    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 已提交
682

683 684 685
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
686

687 688
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
689
  end
690

691 692 693 694
  def local_branches
    @local_branches ||= rugged.branches.each(:local).map do |branch|
      Gitlab::Git::Branch.new(branch.name, branch.target)
    end
695 696
  end

697 698
  alias_method :branches, :local_branches

699 700 701 702 703
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
704
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
705 706
  end

S
Stan Hu 已提交
707
  def commit_dir(user, path, message, branch)
708
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
709 710 711 712 713 714 715 716 717 718 719 720 721
      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
722

S
Stan Hu 已提交
723 724 725
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
726 727 728 729 730 731 732
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
      }
733

734 735
      options[:file] = {
        content: content,
S
Stan Hu 已提交
736 737
        path: path,
        update: update
738
      }
739

740 741
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
742 743
  end

744
  def remove_file(user, path, message, branch)
745
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
746
      committer = user_to_committer(user)
747 748 749 750 751 752 753
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref
      }
754

755 756 757
      options[:file] = {
        path: path
      }
758

759 760
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
761 762
  end

S
Stan Hu 已提交
763
  def user_to_committer(user)
764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
    {
      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

782
  def merge(user, source_sha, target_branch, options = {})
783 784 785 786 787 788 789 790 791
    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?

792 793 794 795 796 797
    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
      )
798

799
      Rugged::Commit.create(rugged, actual_options)
800
    end
801 802
  end

803 804
  def revert(user, commit, base_branch, revert_tree_id = nil)
    source_sha = find_branch(base_branch).target
805
    revert_tree_id ||= check_revert_content(commit, base_branch)
806

807
    return false unless revert_tree_id
808

809
    commit_with_hooks(user, base_branch) do |ref|
810
      committer = user_to_committer(user)
811
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
812
        message: commit.revert_message,
813 814
        author: committer,
        committer: committer,
815
        tree: revert_tree_id,
816
        parents: [rugged.lookup(source_sha)],
R
Rubén Dávila 已提交
817
        update_ref: ref)
818
    end
819 820
  end

P
P.S.V.R 已提交
821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842
  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

843 844 845
  def check_revert_content(commit, base_branch)
    source_sha = find_branch(base_branch).target
    args       = [commit.id, source_sha]
846
    args << { mainline: 1 } if commit.merge_commit?
847 848 849 850 851 852 853 854 855 856

    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 已提交
857 858 859
  def check_cherry_pick_content(commit, base_branch)
    source_sha = find_branch(base_branch).target
    args       = [commit.id, source_sha]
860
    args << 1 if commit.merge_commit?
P
P.S.V.R 已提交
861 862 863 864 865 866 867 868 869 870

    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

871 872
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
873 874
  end

F
Florent (HP) 已提交
875 876 877 878 879
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
880
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
881 882 883 884 885
    else
      nil
    end
  end

S
Stan Hu 已提交
886
  def merge_base(first_commit_id, second_commit_id)
887 888
    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 已提交
889
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
890 891
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
892 893
  end

894 895 896 897
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end

898 899
  def search_files(query, ref)
    offset = 2
900
    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})
901 902 903
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

D
Dmitriy Zaporozhets 已提交
904
  def parse_search_result(result)
905 906
    ref = nil
    filename = nil
907
    basename = nil
908 909
    startline = 0

910
    result.each_line.each_with_index do |line, index|
911 912 913
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
914 915
        extname = File.extname(filename)
        basename = filename.sub(/#{extname}$/, '')
916 917 918 919
        break
      end
    end

920
    data = ""
921

922 923 924
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
925 926 927

    OpenStruct.new(
      filename: filename,
928
      basename: basename,
929 930 931 932 933 934
      ref: ref,
      startline: startline,
      data: data
    )
  end

935
  def fetch_ref(source_path, source_ref, target_ref)
936
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
937 938 939
    Gitlab::Popen.popen(args, path_to_repo)
  end

940
  def with_tmp_ref(oldrev = nil)
941 942 943
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

944
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
945 946 947 948
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
949 950 951 952 953 954
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
955 956
    update_autocrlf_option

957 958
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
959
    target_branch = find_branch(branch)
960
    was_empty = empty?
961

962 963
    if !was_empty && target_branch
      oldrev = target_branch.target
964 965
    end

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

974
      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
975
        if was_empty || !target_branch
976 977
          # Create branch
          rugged.references.create(ref, newrev)
978
        else
979 980 981 982 983 984 985 986 987
          # 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
988 989
        end
      end
990 991

      newrev
992 993 994
    end
  end

995 996 997 998 999
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

1000 1001 1002 1003
  def gitattribute(path, name)
    raw_repository.attributes(path)[name]
  end

1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
  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

1014
  def avatar
1015 1016
    return nil unless exists?

1017 1018 1019 1020 1021 1022 1023
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
        blob_at_branch('master', file)
      end
    end
  end

1024 1025
  private

1026 1027 1028
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
1029 1030 1031 1032

  def head_exists?
    exists? && !empty? && !rugged.head_unborn?
  end
1033 1034 1035 1036

  def file_on_head(regex)
    tree(:head).blobs.find { |file| file.name =~ regex }
  end
1037 1038 1039 1040

  def tags_sorted_by_committed_date
    tags.sort_by { |tag| commit(tag.target).committed_date }
  end
1041
end