repository.rb 25.5 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 initialize(path_with_namespace, project)
15
    @path_with_namespace = path_with_namespace
16
    @project = project
17
  end
18

19 20
  def raw_repository
    return nil unless path_with_namespace
21

22
    @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
23 24
  end

25 26 27 28
  def update_autocrlf_option
    raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
  end

29
  # Return absolute path to repository
30
  def path_to_repo
31
    @path_to_repo ||= File.expand_path(
32
      File.join(@project.repository_storage_path, path_with_namespace + ".git")
33
    )
34 35
  end

36
  def exists?
37
    return @exists unless @exists.nil?
38

39 40 41 42 43 44 45
    @exists = cache.fetch(:exists?) do
      begin
        raw_repository && raw_repository.rugged ? true : false
      rescue Gitlab::Git::Repository::NoRepository
        false
      end
    end
46 47 48
  end

  def empty?
49 50 51
    return @empty unless @empty.nil?

    @empty = cache.fetch(:empty?) { raw_repository.empty? }
52 53
  end

54 55 56 57 58 59 60 61 62 63
  #
  # 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?
64 65 66
    return @has_visible_content unless @has_visible_content.nil?

    @has_visible_content = cache.fetch(:has_visible_content?) do
67
      branch_count > 0
68
    end
69 70
  end

L
Lin Jen-Shin 已提交
71
  def commit(ref = 'HEAD')
72
    return nil unless exists?
73 74 75 76 77 78
    commit =
      if ref.is_a?(Gitlab::Git::Commit)
        ref
      else
        Gitlab::Git::Commit.find(raw_repository, ref)
      end
79
    commit = ::Commit.new(commit, @project) if commit
80
    commit
81
  rescue Rugged::OdbError
82
    nil
83 84
  end

85
  def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil)
86
    options = {
87 88 89 90 91
      repo: raw_repository,
      ref: ref,
      path: path,
      limit: limit,
      offset: offset,
92 93
      after: after,
      before: before,
94 95
      # --follow doesn't play well with --skip. See:
      # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
96 97
      follow: false,
      skip_merges: skip_merges
98 99 100
    }

    commits = Gitlab::Git::Commit.where(options)
101
    commits = Commit.decorate(commits, @project) if commits.present?
102 103 104
    commits
  end

105 106
  def commits_between(from, to)
    commits = Gitlab::Git::Commit.between(raw_repository, from, to)
107
    commits = Commit.decorate(commits, @project) if commits.present?
108 109 110
    commits
  end

111 112 113
  def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
    ref ||= root_ref

114
    # Limited to 1000 commits for now, could be parameterized?
115 116
    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?
117

118 119
    git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
    commits = git_log_results.map { |c| commit(c) }
120
    commits
121 122
  end

123
  def find_branch(name)
124
    raw_repository.branches.find { |branch| branch.name == name }
125 126 127
  end

  def find_tag(name)
128
    tags.find { |tag| tag.name == name }
129 130
  end

131 132 133 134 135 136 137 138 139 140
  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
141

142
    after_create_branch
143
    find_branch(branch_name)
144 145
  end

146 147 148 149 150 151
  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
152

153 154
    options = { message: message, tagger: user_to_committer(user) } if message

155 156
    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
      rugged.tags.create(tag_name, target, options)
157
    end
158

159
    find_tag(tag_name)
160 161
  end

162
  def rm_branch(user, branch_name)
163
    before_remove_branch
164

165 166 167 168 169 170 171 172
    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
173

174
    after_remove_branch
175
    true
176 177
  end

178
  def rm_tag(tag_name)
Y
Yorick Peterse 已提交
179
    before_remove_tag
180

R
Robert Schilling 已提交
181 182 183 184 185 186
    begin
      rugged.tags.delete(tag_name)
      true
    rescue Rugged::ReferenceError
      false
    end
187 188
  end

189 190 191 192
  def ref_names
    branch_names + tag_names
  end

193
  def branch_names
P
Paco Guzman 已提交
194
    @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) }
195 196
  end

197 198 199 200
  def branch_exists?(branch_name)
    branch_names.include?(branch_name)
  end

201 202 203 204
  def ref_exists?(ref)
    rugged.references.exist?(ref)
  end

D
Douwe Maan 已提交
205 206 207 208
  # 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.
209 210 211 212 213 214 215 216 217 218 219 220
  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

221
  def tag_names
222
    cache.fetch(:tag_names) { raw_repository.tag_names }
223 224
  end

225
  def commit_count
226
    cache.fetch(:commit_count) do
227
      begin
228
        raw_repository.commit_count(self.root_ref)
229 230 231
      rescue
        0
      end
232
    end
233 234
  end

Y
Yorick Peterse 已提交
235
  def branch_count
236
    @branch_count ||= cache.fetch(:branch_count) { branches.size }
Y
Yorick Peterse 已提交
237 238 239 240 241 242
  end

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

243 244 245
  # Return repo size in megabytes
  # Cached in redis
  def size
246
    cache.fetch(:size) { raw_repository.size }
247
  end
248

249
  def diverging_commit_counts(branch)
250
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
J
Jeff Stubler 已提交
251
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
252 253
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
254
      number_commits_behind = raw_repository.
255
        count_commits_between(branch.target.sha, root_ref_hash)
256 257

      number_commits_ahead = raw_repository.
258
        count_commits_between(root_ref_hash, branch.target.sha)
259

260 261 262
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
263

264
  # Keys for data that can be affected for any commit push.
265
  def cache_keys
266
    %i(size commit_count
267
       readme version contribution_guide changelog
A
Alfredo Sumaran 已提交
268
       license_blob license_key gitignore)
269
  end
270

271 272 273 274 275
  # Keys for data on branch/tag operations.
  def cache_keys_for_branches_and_tags
    %i(branch_names tag_names branch_count tag_count)
  end

276
  def build_cache
277
    (cache_keys + cache_keys_for_branches_and_tags).each do |key|
278 279 280 281 282 283
      unless cache.exist?(key)
        send(key)
      end
    end
  end

D
Douwe Maan 已提交
284 285 286 287 288 289 290
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
P
Paco Guzman 已提交
291
    @branch_names = nil
292
    @local_branches = nil
D
Douwe Maan 已提交
293 294
  end

295
  def expire_cache(branch_name = nil, revision = nil)
296
    cache_keys.each do |key|
297 298
      cache.expire(key)
    end
299

300
    expire_branch_cache(branch_name)
301
    expire_avatar_cache(branch_name, revision)
302 303 304 305

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

308 309 310 311 312 313 314 315 316 317 318 319
  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}")
320
    end
D
Dmitriy Zaporozhets 已提交
321 322
  end

323 324 325 326 327
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

328 329 330 331 332 333 334 335
  # 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

336 337 338 339 340
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

Y
Yorick Peterse 已提交
341 342 343 344 345 346 347 348 349 350
  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

351 352 353 354
  def lookup_cache
    @lookup_cache ||= {}
  end

355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
  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

372 373 374 375 376 377 378 379
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
380 381
    expire_root_ref_cache
    expire_emptiness_caches
382 383
  end

384 385
  # Runs code just before a repository is deleted.
  def before_delete
386 387
    expire_exists_cache

388 389
    expire_cache if exists?

390 391 392 393 394
    # expire cache that don't depend on repository data (when expiring)
    expire_tags_cache
    expire_tag_count_cache
    expire_branches_cache
    expire_branch_count_cache
395 396
    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 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
H
Hannes Rosenögger 已提交
672

673 674 675
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
676

677 678
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
679
  end
680

681
  def local_branches
682
    @local_branches ||= raw_repository.local_branches
683 684
  end

685 686
  alias_method :branches, :local_branches

687 688 689 690 691
  def tags
    @tags ||= raw_repository.tags
  end

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

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

      options[:commit] = {
        message: message,
        branch: ref,
705
        update_ref: false,
S
Stan Hu 已提交
706 707 708 709 710
      }

      raw_repository.mkdir(path, options)
    end
  end
711

S
Stan Hu 已提交
712 713 714
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
715 716 717 718 719 720
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
721
        update_ref: false,
722
      }
723

724 725
      options[:file] = {
        content: content,
S
Stan Hu 已提交
726 727
        path: path,
        update: update
728
      }
729

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

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

746
      options[:file] = {
747 748
        content: content,
        path: path,
749
        update: true
750 751
      }

752 753
      if previous_path
        options[:file][:previous_path] = previous_path
754
        Gitlab::Git::Blob.rename(raw_repository, options)
T
tiagonbotelho 已提交
755
      else
756
        Gitlab::Git::Blob.commit(raw_repository, options)
T
tiagonbotelho 已提交
757
      end
758 759 760
    end
  end

761
  def remove_file(user, path, message, branch)
762
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
763
      committer = user_to_committer(user)
764 765 766 767 768
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
769 770
        branch: ref,
        update_ref: false,
771
      }
772

773 774 775
      options[:file] = {
        path: path
      }
776

777 778
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
779 780
  end

S
Stan Hu 已提交
781
  def user_to_committer(user)
782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799
    {
      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

800 801 802
  def merge(user, merge_request, options = {})
    our_commit = rugged.branches[merge_request.target_branch].target
    their_commit = rugged.lookup(merge_request.diff_head_sha)
803 804 805 806 807 808 809

    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?

810
    commit_with_hooks(user, merge_request.target_branch) do
811 812 813 814
      actual_options = options.merge(
        parents: [our_commit, their_commit],
        tree: merge_index.write_tree(rugged),
      )
815

816 817 818
      commit_id = Rugged::Commit.create(rugged, actual_options)
      merge_request.update(in_progress_merge_commit_sha: commit_id)
      commit_id
819
    end
820 821
  end

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

826
    return false unless revert_tree_id
827

828
    commit_with_hooks(user, base_branch) do
829
      committer = user_to_committer(user)
830
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
831
        message: commit.revert_message,
832 833
        author: committer,
        committer: committer,
834
        tree: revert_tree_id,
835
        parents: [rugged.lookup(source_sha)])
836
    end
837 838
  end

P
P.S.V.R 已提交
839
  def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
840
    source_sha = find_branch(base_branch).target.sha
P
P.S.V.R 已提交
841 842 843 844
    cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)

    return false unless cherry_pick_tree_id

845
    commit_with_hooks(user, base_branch) do
P
P.S.V.R 已提交
846 847 848 849 850 851 852 853 854 855
      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,
856
        parents: [rugged.lookup(source_sha)])
P
P.S.V.R 已提交
857 858 859
    end
  end

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

    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 已提交
874
  def check_cherry_pick_content(commit, base_branch)
875
    source_sha = find_branch(base_branch).target.sha
P
P.S.V.R 已提交
876
    args       = [commit.id, source_sha]
877
    args << 1 if commit.merge_commit?
P
P.S.V.R 已提交
878 879 880 881 882 883 884 885 886 887

    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

888 889
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
890 891
  end

F
Florent (HP) 已提交
892 893 894 895 896
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
897
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
898 899 900 901 902
    else
      nil
    end
  end

S
Stan Hu 已提交
903
  def merge_base(first_commit_id, second_commit_id)
904 905
    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 已提交
906
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
907 908
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
909 910
  end

911 912 913 914
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end

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

D
Dmitriy Zaporozhets 已提交
921
  def parse_search_result(result)
922 923
    ref = nil
    filename = nil
924
    basename = nil
925 926
    startline = 0

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

937
    data = ""
938

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

    OpenStruct.new(
      filename: filename,
945
      basename: basename,
946 947 948 949 950 951
      ref: ref,
      startline: startline,
      data: data
    )
  end

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

957
  def commit_with_hooks(current_user, branch)
958 959
    update_autocrlf_option

960 961
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
962
    target_branch = find_branch(branch)
963
    was_empty = empty?
964

965 966
    if !was_empty && target_branch
      oldrev = target_branch.target
967 968
    end

969 970
    # Make commit
    newrev = yield(ref)
971

972 973 974
    unless newrev
      raise CommitError.new('Failed to create commit')
    end
975

976 977 978 979 980 981 982
    GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
      if was_empty || !target_branch
        # Create branch
        rugged.references.create(ref, newrev)
      else
        # Update head
        current_head = find_branch(branch).target
983

984 985 986
        # Make sure target branch was not changed during pre-receive hook
        if current_head == oldrev
          rugged.references.update(ref, newrev)
987
        else
988
          raise CommitError.new('Commit was rejected because branch received new push')
989 990 991
        end
      end
    end
992 993

    newrev
994 995
  end

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

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

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

1015
  def avatar
1016 1017
    return nil unless exists?

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

1025 1026
  private

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

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

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

  def tags_sorted_by_committed_date
1040
    tags.sort_by { |tag| tag.target.committed_date }
1041
  end
D
Douwe Maan 已提交
1042 1043 1044 1045

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