repository.rb 26 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

14
  def self.clean_old_archives
15 16
    Gitlab::Metrics.measure(:clean_old_archives) do
      repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
17

18
      return unless File.directory?(repository_downloads_path)
19

20 21
      Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
    end
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(ref = 'HEAD')
82
    return nil unless exists?
83
    commit = Gitlab::Git::Commit.find(raw_repository, ref)
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)
184
    before_remove_tag
185

Robert Schilling's avatar
Robert Schilling committed
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
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
  def ref_exists?(ref)
    rugged.references.exist?(ref)
  end

210 211 212 213
  # Makes sure a commit is kept around when Git garbage collection runs.
  # Git GC will delete commits from the repository that are no longer in any
  # branches or tags, but we want to keep some of these commits around, for
  # example if they have comments or CI builds.
214 215 216 217 218 219 220 221 222 223 224 225
  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

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

240
  def branch_count
241
    @branch_count ||= cache.fetch(:branch_count) { branches.size }
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
Jeff Stubler's avatar
Jeff Stubler committed
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
Alfredo Sumaran's avatar
Alfredo Sumaran committed
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

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)
296
    @branch_names = nil
297
    @local_branches = nil
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
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
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

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

407 408
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
409
    expire_cache
410
    expire_tags_cache
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
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
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
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
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
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
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
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
559 560

  def blob_at_branch(branch_name, path)
561
    last_commit = commit(branch_name)
562

563 564 565 566 567
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
568
  end
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)
577
    if submodules(ref).any?
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

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

599
    highest_branch_id = branch_ids.max || 0
600

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

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
640
    commits.group_by(&:author_email).map do |email, commits|
641 642
      contributor = Gitlab::Contributor.new
      contributor.email = email
643

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
644
      commits.each do |commit|
645
        if contributor.name.blank?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
646
          contributor.name = commit.author_name
647 648
        end

649
        contributor.commits += 1
650 651
      end

652 653
      contributor
    end
654
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
655

656 657
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
658 659 660 661 662 663 664 665 666 667 668 669 670 671
    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
672

673 674 675
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
676

677 678
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
679
  end
680

681 682 683 684
  def local_branches
    @local_branches ||= rugged.branches.each(:local).map do |branch|
      Gitlab::Git::Branch.new(branch.name, branch.target)
    end
685 686
  end

687 688
  alias_method :branches, :local_branches

689 690 691 692 693
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
694
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
695 696
  end

Stan Hu's avatar
Stan Hu committed
697
  def commit_dir(user, path, message, branch)
698
    commit_with_hooks(user, branch) do |ref|
Stan Hu's avatar
Stan Hu committed
699 700 701 702 703 704 705 706 707 708 709 710 711
      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
712

Stan Hu's avatar
Stan Hu committed
713 714 715
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
716 717 718 719 720 721 722
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
      }
723

724 725
      options[:file] = {
        content: content,
Stan Hu's avatar
Stan Hu committed
726 727
        path: path,
        update: update
728
      }
729

730 731
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
732 733
  end

734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750
  def update_file(user, path, previous_path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
      }

      options[:file] = {
        content: content,
        path: path,
        update: update
      }

tiagonbotelho's avatar
tiagonbotelho committed
751 752 753 754 755 756 757 758 759 760
      if previous_path
        options[:file].merge!(previous_path: previous_path)

        Gitlab::Git::Blob.rename(raw_repository, options)
      else
        puts "#" * 90
        puts "World"
        puts "#" * 90
        Gitlab::Git::Blob.commit(raw_repository, options)
      end
761 762 763
    end
  end

764
  def remove_file(user, path, message, branch)
765
    commit_with_hooks(user, branch) do |ref|
Stan Hu's avatar
Stan Hu committed
766
      committer = user_to_committer(user)
767 768 769 770 771 772 773
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref
      }
774

775 776 777
      options[:file] = {
        path: path
      }
778

779 780
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
781 782
  end

Stan Hu's avatar
Stan Hu committed
783
  def user_to_committer(user)
784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
    {
      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

802
  def merge(user, source_sha, target_branch, options = {})
803 804 805 806 807 808 809 810 811
    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?

812 813 814 815 816 817
    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
      )
818

819
      Rugged::Commit.create(rugged, actual_options)
820
    end
821 822
  end

823 824
  def revert(user, commit, base_branch, revert_tree_id = nil)
    source_sha = find_branch(base_branch).target
825
    revert_tree_id ||= check_revert_content(commit, base_branch)
826

827
    return false unless revert_tree_id
828

829
    commit_with_hooks(user, base_branch) do |ref|
830
      committer = user_to_committer(user)
831
      source_sha = Rugged::Commit.create(rugged,
832
        message: commit.revert_message,
833 834
        author: committer,
        committer: committer,
835
        tree: revert_tree_id,
836
        parents: [rugged.lookup(source_sha)],
Rubén Dávila's avatar
Rubén Dávila committed
837
        update_ref: ref)
838
    end
839 840
  end

841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
  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

863 864 865
  def check_revert_content(commit, base_branch)
    source_sha = find_branch(base_branch).target
    args       = [commit.id, source_sha]
866
    args << { mainline: 1 } if commit.merge_commit?
867 868 869 870 871 872 873 874 875 876

    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

877 878 879
  def check_cherry_pick_content(commit, base_branch)
    source_sha = find_branch(base_branch).target
    args       = [commit.id, source_sha]
880
    args << 1 if commit.merge_commit?
881 882 883 884 885 886 887 888 889 890

    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

891 892
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
893 894
  end

895 896 897 898 899
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
900
      is_ancestor?(branch_commit.id, root_ref_commit.id)
901 902 903 904 905
    else
      nil
    end
  end

906
  def merge_base(first_commit_id, second_commit_id)
907 908
    first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
    second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
909
    rugged.merge_base(first_commit_id, second_commit_id)
Douwe Maan's avatar
Douwe Maan committed
910 911
  rescue Rugged::ReferenceError
    nil
912 913
  end

914 915 916 917
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end

918 919
  def search_files(query, ref)
    offset = 2
920
    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})
921 922 923
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

924
  def parse_search_result(result)
925 926
    ref = nil
    filename = nil
927
    basename = nil
928 929
    startline = 0

930
    result.each_line.each_with_index do |line, index|
931 932 933
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
934
        extname = Regexp.escape(File.extname(filename))
935
        basename = filename.sub(/#{extname}$/, '')
936 937 938 939
        break
      end
    end

940
    data = ""
941

942 943 944
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
945 946 947

    OpenStruct.new(
      filename: filename,
948
      basename: basename,
949 950 951 952 953 954
      ref: ref,
      startline: startline,
      data: data
    )
  end

955
  def fetch_ref(source_path, source_ref, target_ref)
956
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
957 958 959
    Gitlab::Popen.popen(args, path_to_repo)
  end

960
  def with_tmp_ref(oldrev = nil)
961 962 963
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

964
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
965 966 967 968
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
969 970 971 972 973 974
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
975 976
    update_autocrlf_option

977 978
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
979
    target_branch = find_branch(branch)
980
    was_empty = empty?
981

982 983
    if !was_empty && target_branch
      oldrev = target_branch.target
984 985
    end

986 987 988 989 990 991 992
    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
993

994
      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
995
        if was_empty || !target_branch
996 997
          # Create branch
          rugged.references.create(ref, newrev)
998
        else
999 1000 1001 1002 1003 1004 1005 1006 1007
          # 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
1008 1009
        end
      end
1010 1011

      newrev
1012 1013 1014
    end
  end

1015 1016 1017 1018 1019
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

1020 1021 1022 1023
  def gitattribute(path, name)
    raw_repository.attributes(path)[name]
  end

1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
  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

1034
  def avatar
1035 1036
    return nil unless exists?

1037 1038 1039 1040 1041 1042 1043
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
        blob_at_branch('master', file)
      end
    end
  end

1044 1045
  private

1046 1047 1048
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
1049 1050 1051 1052

  def head_exists?
    exists? && !empty? && !rugged.head_unborn?
  end
1053 1054 1055 1056

  def file_on_head(regex)
    tree(:head).blobs.find { |file| file.name =~ regex }
  end
1057 1058 1059 1060

  def tags_sorted_by_committed_date
    tags.sort_by { |tag| commit(tag.target).committed_date }
  end
1061 1062 1063 1064

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