repository.rb 25.8 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
    commit = Gitlab::Git::Commit.find(raw_repository, ref)
74
    commit = ::Commit.new(commit, @project) if commit
75
    commit
76
  rescue Rugged::OdbError
77
    nil
78 79
  end

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

    commits = Gitlab::Git::Commit.where(options)
96
    commits = Commit.decorate(commits, @project) if commits.present?
97 98 99
    commits
  end

100 101
  def commits_between(from, to)
    commits = Gitlab::Git::Commit.between(raw_repository, from, to)
102
    commits = Commit.decorate(commits, @project) if commits.present?
103 104 105
    commits
  end

106 107 108
  def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
    ref ||= root_ref

109
    # Limited to 1000 commits for now, could be parameterized?
110 111
    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?
112

113 114
    git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
    commits = git_log_results.map { |c| commit(c) }
115
    commits
116 117
  end

118
  def find_branch(name)
119
    raw_repository.branches.find { |branch| branch.name == name }
120 121 122
  end

  def find_tag(name)
123
    tags.find { |tag| tag.name == name }
124 125
  end

126 127 128 129 130 131 132 133 134 135
  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
136

137
    after_create_branch
138
    find_branch(branch_name)
139 140
  end

141 142 143 144 145 146
  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
147

148 149
    options = { message: message, tagger: user_to_committer(user) } if message

150 151
    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
      rugged.tags.create(tag_name, target, options)
152
    end
153

154
    find_tag(tag_name)
155 156
  end

157
  def rm_branch(user, branch_name)
158
    before_remove_branch
159

160 161 162 163 164 165 166 167
    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
168

169
    after_remove_branch
170
    true
171 172
  end

173
  def rm_tag(tag_name)
Y
Yorick Peterse 已提交
174
    before_remove_tag
175

R
Robert Schilling 已提交
176 177 178 179 180 181
    begin
      rugged.tags.delete(tag_name)
      true
    rescue Rugged::ReferenceError
      false
    end
182 183
  end

184 185 186 187
  def ref_names
    branch_names + tag_names
  end

188
  def branch_names
P
Paco Guzman 已提交
189
    @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) }
190 191
  end

192 193 194 195
  def branch_exists?(branch_name)
    branch_names.include?(branch_name)
  end

196 197 198 199
  def ref_exists?(ref)
    rugged.references.exist?(ref)
  end

D
Douwe Maan 已提交
200 201 202 203
  # 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.
204 205 206 207 208
  def keep_around(sha)
    return unless sha && commit(sha)

    return if kept_around?(sha)

209 210 211 212 213 214
    # This will still fail if the file is corrupted (e.g. 0 bytes)
    begin
      rugged.references.create(keep_around_ref_name(sha), sha, force: true)
    rescue Rugged::ReferenceError => ex
      Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
    end
215 216 217
  end

  def kept_around?(sha)
218 219 220 221 222
    begin
      ref_exists?(keep_around_ref_name(sha))
    rescue Rugged::ReferenceError
      false
    end
223 224
  end

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

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

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

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

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

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

      number_commits_ahead = raw_repository.
262
        count_commits_between(root_ref_hash, branch.target)
263

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

392 393
    expire_cache if exists?

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

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
422 423
  end

424 425 426 427 428
  def before_import
    expire_emptiness_caches
    expire_exists_cache
  end

429 430 431
  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
432
    expire_exists_cache
433 434 435
  end

  # Runs code after a new commit has been pushed.
436 437
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
438 439 440 441
  end

  # Runs code after a new branch has been created.
  def after_create_branch
442
    expire_branches_cache
443
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
444
    expire_branch_count_cache
445 446
  end

447 448 449 450 451
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
  end

452 453 454
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
455
    expire_branch_count_cache
456
    expire_branches_cache
457 458
  end

459
  def method_missing(m, *args, &block)
460 461 462 463 464 465
    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
466 467
  end

468 469
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
470
  end
D
Dmitriy Zaporozhets 已提交
471 472

  def blob_at(sha, path)
473
    unless Gitlab::Git.blank_ref?(sha)
474
      Blob.decorate(Gitlab::Git::Blob.find(self, sha, path))
475
    end
D
Dmitriy Zaporozhets 已提交
476
  end
477

478 479 480 481
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

482
  def readme
483
    cache.fetch(:readme) { tree(:head).readme }
484
  end
485

486
  def version
487
    cache.fetch(:version) do
488
      tree(:head).blobs.find do |file|
489
        file.name.casecmp('version').zero?
490 491 492 493
      end
    end
  end

494
  def contribution_guide
495 496 497 498 499 500
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
501 502 503

  def changelog
    cache.fetch(:changelog) do
504
      file_on_head(/\A(changelog|history|changes|news)/i)
505
    end
506 507
  end

508
  def license_blob
509
    return nil unless head_exists?
510

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

516
  def license_key
517
    return nil unless head_exists?
518 519

    cache.fetch(:license_key) do
520
      Licensee.license(path).try(:key)
521
    end
522 523
  end

524 525 526 527 528 529 530 531
  def gitignore
    return nil if !exists? || empty?

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

532
  def gitlab_ci_yml
533
    return nil unless head_exists?
534 535 536 537

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

544
  def head_commit
545 546 547 548 549
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
550 551 552 553
  end

  def tree(sha = :head, path = nil)
    if sha == :head
554 555 556 557 558
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
559 560 561 562
    end

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

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

D
Dmitriy Zaporozhets 已提交
567 568 569 570 571
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
572
  end
D
Dmitriy Zaporozhets 已提交
573 574 575 576 577 578 579 580

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

      if submodule
        submodule['url']
      end
    end
  end
589 590

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

P
P.S.V.R 已提交
596 597 598 599
  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/)
600 601 602
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
603
    highest_branch_id = branch_ids.max || 0
604

P
P.S.V.R 已提交
605 606 607
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
608 609
  end

610
  # Remove archives older than 2 hours
611 612 613 614 615 616 617 618 619 620 621 622 623 624
  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
625

626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
  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

641
  def contributors
642
    commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
643

D
Dmitriy Zaporozhets 已提交
644
    commits.group_by(&:author_email).map do |email, commits|
645 646
      contributor = Gitlab::Contributor.new
      contributor.email = email
647

D
Dmitriy Zaporozhets 已提交
648
      commits.each do |commit|
649
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
650
          contributor.name = commit.author_name
651 652
        end

653
        contributor.commits += 1
654 655
      end

656 657
      contributor
    end
658
  end
D
Dmitriy Zaporozhets 已提交
659

660 661
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
662 663 664 665 666 667 668 669 670 671 672 673 674 675
    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 已提交
676

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

681 682
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
683
  end
684

685
  def local_branches
686 687 688
    @local_branches ||= rugged.branches.each(:local).map do |branch|
      Gitlab::Git::Branch.new(branch.name, branch.target)
    end
689 690
  end

691 692
  alias_method :branches, :local_branches

693 694 695 696 697
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
698
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
699 700
  end

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

      options[:commit] = {
        message: message,
        branch: ref,
711
        update_ref: false,
S
Stan Hu 已提交
712 713 714 715 716
      }

      raw_repository.mkdir(path, options)
    end
  end
717

S
Stan Hu 已提交
718 719 720
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
721 722 723 724 725 726
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
727
        update_ref: false,
728
      }
729

730 731
      options[:file] = {
        content: content,
S
Stan Hu 已提交
732 733
        path: path,
        update: update
734
      }
735

736 737
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
738 739
  end

740
  def update_file(user, path, content, branch:, previous_path:, message:)
741 742
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
743 744 745 746 747
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
748 749
        branch: ref,
        update_ref: false
750 751
      }

752
      options[:file] = {
753 754
        content: content,
        path: path,
755
        update: true
756 757
      }

758 759
      if previous_path
        options[:file][:previous_path] = previous_path
760
        Gitlab::Git::Blob.rename(raw_repository, options)
T
tiagonbotelho 已提交
761
      else
762
        Gitlab::Git::Blob.commit(raw_repository, options)
T
tiagonbotelho 已提交
763
      end
764 765 766
    end
  end

767
  def remove_file(user, path, message, branch)
768
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
769
      committer = user_to_committer(user)
770 771 772 773 774
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
775 776
        branch: ref,
        update_ref: false,
777
      }
778

779 780 781
      options[:file] = {
        path: path
      }
782

783 784
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
785 786
  end

S
Stan Hu 已提交
787
  def user_to_committer(user)
788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805
    {
      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

806 807 808
  def merge(user, merge_request, options = {})
    our_commit = rugged.branches[merge_request.target_branch].target
    their_commit = rugged.lookup(merge_request.diff_head_sha)
809 810 811 812 813 814 815

    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?

816
    commit_with_hooks(user, merge_request.target_branch) do
817 818 819 820
      actual_options = options.merge(
        parents: [our_commit, their_commit],
        tree: merge_index.write_tree(rugged),
      )
821

822 823 824
      commit_id = Rugged::Commit.create(rugged, actual_options)
      merge_request.update(in_progress_merge_commit_sha: commit_id)
      commit_id
825
    end
826 827
  end

828
  def revert(user, commit, base_branch, revert_tree_id = nil)
829
    source_sha = find_branch(base_branch).target
830
    revert_tree_id ||= check_revert_content(commit, base_branch)
831

832
    return false unless revert_tree_id
833

834
    commit_with_hooks(user, base_branch) do
835
      committer = user_to_committer(user)
836
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
837
        message: commit.revert_message,
838 839
        author: committer,
        committer: committer,
840
        tree: revert_tree_id,
841
        parents: [rugged.lookup(source_sha)])
842
    end
843 844
  end

P
P.S.V.R 已提交
845
  def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
846
    source_sha = find_branch(base_branch).target
P
P.S.V.R 已提交
847 848 849 850
    cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)

    return false unless cherry_pick_tree_id

851
    commit_with_hooks(user, base_branch) do
P
P.S.V.R 已提交
852 853 854 855 856 857 858 859 860 861
      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,
862
        parents: [rugged.lookup(source_sha)])
P
P.S.V.R 已提交
863 864 865
    end
  end

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

    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 已提交
880
  def check_cherry_pick_content(commit, base_branch)
881
    source_sha = find_branch(base_branch).target
P
P.S.V.R 已提交
882
    args       = [commit.id, source_sha]
883
    args << 1 if commit.merge_commit?
P
P.S.V.R 已提交
884 885 886 887 888 889 890 891 892 893

    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

894 895
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
896 897
  end

F
Florent (HP) 已提交
898 899 900 901 902
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
903
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
904 905 906 907 908
    else
      nil
    end
  end

S
Stan Hu 已提交
909
  def merge_base(first_commit_id, second_commit_id)
910 911
    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 已提交
912
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
913 914
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
915 916
  end

917 918 919 920
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end

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

D
Dmitriy Zaporozhets 已提交
927
  def parse_search_result(result)
928 929
    ref = nil
    filename = nil
930
    basename = nil
931 932
    startline = 0

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

943
    data = ""
944

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

    OpenStruct.new(
      filename: filename,
951
      basename: basename,
952 953 954 955 956 957
      ref: ref,
      startline: startline,
      data: data
    )
  end

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

963
  def commit_with_hooks(current_user, branch)
964 965
    update_autocrlf_option

966 967
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
968
    target_branch = find_branch(branch)
969
    was_empty = empty?
970

971 972
    if !was_empty && target_branch
      oldrev = target_branch.target
973 974
    end

975 976
    # Make commit
    newrev = yield(ref)
977

978 979 980
    unless newrev
      raise CommitError.new('Failed to create commit')
    end
981

982 983 984 985 986 987 988
    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
989

990 991 992
        # Make sure target branch was not changed during pre-receive hook
        if current_head == oldrev
          rugged.references.update(ref, newrev)
993
        else
994
          raise CommitError.new('Commit was rejected because branch received new push')
995 996 997
        end
      end
    end
998 999

    newrev
1000 1001
  end

1002 1003 1004 1005 1006
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

1007 1008 1009 1010
  def gitattribute(path, name)
    raw_repository.attributes(path)[name]
  end

1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
  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

1021
  def avatar
1022 1023
    return nil unless exists?

1024 1025 1026 1027 1028 1029 1030
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
        blob_at_branch('master', file)
      end
    end
  end

1031 1032
  private

1033 1034 1035
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
1036 1037 1038 1039

  def head_exists?
    exists? && !empty? && !rugged.head_unborn?
  end
1040 1041 1042 1043

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

  def tags_sorted_by_committed_date
1046
    tags.sort_by { |tag| commit(tag.target).committed_date }
1047
  end
D
Douwe Maan 已提交
1048 1049 1050 1051

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