repository.rb 30 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 15 16 17 18 19 20 21 22 23 24 25 26 27
  def self.storages
    Gitlab.config.repositories.storages
  end

  def self.remove_storage_from_path(repo_path)
    storages.find do |_, storage_path|
      if repo_path.start_with?(storage_path)
        return repo_path.sub(storage_path, '')
      end
    end

    repo_path
  end

28
  def initialize(path_with_namespace, project)
29
    @path_with_namespace = path_with_namespace
30
    @project = project
31
  end
32

33 34
  def raw_repository
    return nil unless path_with_namespace
35

36
    @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
37 38
  end

39 40 41 42
  def update_autocrlf_option
    raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
  end

43
  # Return absolute path to repository
44
  def path_to_repo
45
    @path_to_repo ||= File.expand_path(
46
      File.join(@project.repository_storage_path, path_with_namespace + ".git")
47
    )
48 49
  end

50
  def exists?
51
    return @exists unless @exists.nil?
52

53 54 55 56 57 58 59
    @exists = cache.fetch(:exists?) do
      begin
        raw_repository && raw_repository.rugged ? true : false
      rescue Gitlab::Git::Repository::NoRepository
        false
      end
    end
60 61 62
  end

  def empty?
63 64 65
    return @empty unless @empty.nil?

    @empty = cache.fetch(:empty?) { raw_repository.empty? }
66 67
  end

68 69 70 71 72 73 74 75 76 77
  #
  # 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?
78 79 80
    return @has_visible_content unless @has_visible_content.nil?

    @has_visible_content = cache.fetch(:has_visible_content?) do
81
      branch_count > 0
82
    end
83 84
  end

85
  def commit(ref = 'HEAD')
86
    return nil unless exists?
87

88 89 90 91 92 93
    commit =
      if ref.is_a?(Gitlab::Git::Commit)
        ref
      else
        Gitlab::Git::Commit.find(raw_repository, ref)
      end
94

95
    commit = ::Commit.new(commit, @project) if commit
96
    commit
97
  rescue Rugged::OdbError,  Rugged::TreeError
98
    nil
99 100
  end

101
  def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil)
102
    options = {
103 104 105 106 107
      repo: raw_repository,
      ref: ref,
      path: path,
      limit: limit,
      offset: offset,
108 109
      after: after,
      before: before,
110 111
      # --follow doesn't play well with --skip. See:
      # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
112 113
      follow: false,
      skip_merges: skip_merges
114 115 116
    }

    commits = Gitlab::Git::Commit.where(options)
117
    commits = Commit.decorate(commits, @project) if commits.present?
118 119 120
    commits
  end

121 122
  def commits_between(from, to)
    commits = Gitlab::Git::Commit.between(raw_repository, from, to)
123
    commits = Commit.decorate(commits, @project) if commits.present?
124 125 126
    commits
  end

127
  def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
128 129 130 131
    unless exists? && has_visible_content? && query.present?
      return []
    end

132 133
    ref ||= root_ref

134 135 136 137
    args = %W(
      #{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset}
      --max-count #{limit} --grep=#{query} --regexp-ignore-case
    )
138
    args = args.concat(%W(-- #{path})) if path.present?
139

140 141
    git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines
    git_log_results.map { |c| commit(c.chomp) }.compact
142 143
  end

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
  def find_branch(name, fresh_repo: true)
    # Since the Repository object may have in-memory index changes, invalidating the memoized Repository object may
    # cause unintended side effects. Because finding a branch is a read-only operation, we can safely instantiate
    # a new repo here to ensure a consistent state to avoid a libgit2 bug where concurrent access (e.g. via git gc)
    # may cause the branch to "disappear" erroneously or have the wrong SHA.
    #
    # See: https://github.com/libgit2/libgit2/issues/1534 and https://gitlab.com/gitlab-org/gitlab-ce/issues/15392
    raw_repo =
      if fresh_repo
        Gitlab::Git::Repository.new(path_to_repo)
      else
        raw_repository
      end

    raw_repo.find_branch(name)
159 160 161
  end

  def find_tag(name)
162
    tags.find { |tag| tag.name == name }
163 164
  end

165 166 167 168 169 170 171 172
  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
173
      update_ref!(ref, target, oldrev)
174
    end
175

176
    after_create_branch
177
    find_branch(branch_name)
178 179
  end

180 181 182 183 184 185
  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
186

187 188
    options = { message: message, tagger: user_to_committer(user) } if message

189 190
    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
      rugged.tags.create(tag_name, target, options)
191
    end
192

193
    find_tag(tag_name)
194 195
  end

196
  def rm_branch(user, branch_name)
197
    before_remove_branch
198

199
    branch = find_branch(branch_name)
200
    oldrev = branch.try(:dereferenced_target).try(:id)
201 202 203 204
    newrev = Gitlab::Git::BLANK_SHA
    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name

    GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
205
      update_ref!(ref, newrev, oldrev)
206
    end
207

208
    after_remove_branch
209
    true
210 211
  end

212
  def rm_tag(tag_name)
213
    before_remove_tag
214

Robert Schilling's avatar
Robert Schilling committed
215 216 217 218 219 220
    begin
      rugged.tags.delete(tag_name)
      true
    rescue Rugged::ReferenceError
      false
    end
221 222
  end

223 224 225 226
  def ref_names
    branch_names + tag_names
  end

227
  def branch_names
228
    @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) }
229 230
  end

231 232 233 234
  def branch_exists?(branch_name)
    branch_names.include?(branch_name)
  end

235 236
  def ref_exists?(ref)
    rugged.references.exist?(ref)
237 238
  rescue Rugged::ReferenceError
    false
239 240
  end

241 242 243 244 245 246
  def update_ref!(name, newrev, oldrev)
    # We use 'git update-ref' because libgit2/rugged currently does not
    # offer 'compare and swap' ref updates. Without compare-and-swap we can
    # (and have!) accidentally reset the ref to an earlier state, clobbering
    # commits. See also https://github.com/libgit2/libgit2/issues/1534.
    command = %w[git update-ref --stdin -z]
247
    _, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin|
248 249 250 251 252
      stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00")
    end

    return if status.zero?

253
    raise CommitError.new("Could not update branch #{name.sub('refs/heads/', '')}. Please refresh and try again.")
254 255
  end

256 257 258 259
  # 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.
260 261 262 263 264
  def keep_around(sha)
    return unless sha && commit(sha)

    return if kept_around?(sha)

265 266 267 268 269
    # 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}"
270 271 272
    rescue Rugged::OSError => ex
      raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
      Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
273
    end
274 275 276
  end

  def kept_around?(sha)
277 278 279 280 281
    begin
      ref_exists?(keep_around_ref_name(sha))
    rescue Rugged::ReferenceError
      false
    end
282 283
  end

284
  def tag_names
285
    cache.fetch(:tag_names) { raw_repository.tag_names }
286 287
  end

288
  def commit_count
289
    cache.fetch(:commit_count) do
290
      begin
291
        raw_repository.commit_count(self.root_ref)
292 293 294
      rescue
        0
      end
295
    end
296 297
  end

298
  def branch_count
299
    @branch_count ||= cache.fetch(:branch_count) { branches.size }
300 301 302 303 304 305
  end

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

306 307 308
  # Return repo size in megabytes
  # Cached in redis
  def size
309
    cache.fetch(:size) { raw_repository.size }
310
  end
311

312
  def diverging_commit_counts(branch)
313
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
Jeff Stubler's avatar
Jeff Stubler committed
314
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
315 316
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
317
      number_commits_behind = raw_repository.
318
        count_commits_between(branch.dereferenced_target.sha, root_ref_hash)
319 320

      number_commits_ahead = raw_repository.
321
        count_commits_between(root_ref_hash, branch.dereferenced_target.sha)
322

323 324 325
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
326

327
  # Keys for data that can be affected for any commit push.
328
  def cache_keys
329
    %i(size commit_count
330
       readme version contribution_guide changelog
331
       license_blob license_key gitignore koding_yml)
332
  end
333

334 335 336 337 338
  # Keys for data on branch/tag operations.
  def cache_keys_for_branches_and_tags
    %i(branch_names tag_names branch_count tag_count)
  end

339
  def build_cache
340
    (cache_keys + cache_keys_for_branches_and_tags).each do |key|
341 342 343 344 345 346
      unless cache.exist?(key)
        send(key)
      end
    end
  end

347 348 349 350 351 352 353
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
354
    @branch_names = nil
355
    @local_branches = nil
356 357
  end

358
  def expire_cache(branch_name = nil, revision = nil)
359
    cache_keys.each do |key|
360 361
      cache.expire(key)
    end
362

363
    expire_branch_cache(branch_name)
364
    expire_avatar_cache(branch_name, revision)
365 366 367 368

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

371 372 373 374 375 376 377 378 379 380 381 382
  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}")
383
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
384 385
  end

386 387 388 389 390
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

391 392 393 394 395 396 397 398
  # 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

399 400 401 402 403
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

404 405 406 407 408 409 410 411 412 413
  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

414 415 416 417
  def lookup_cache
    @lookup_cache ||= {}
  end

418 419 420 421 422 423 424 425
  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)
426
      return unless commit.raw_diffs(deltas_only: true).
427 428 429 430 431 432 433 434
        any? { |diff| AVATAR_FILES.include?(diff.new_path) }
    end

    cache.expire(:avatar)

    @avatar = nil
  end

435 436 437 438 439
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

440 441 442 443 444 445 446 447 448 449 450
  # expire cache that doesn't depend on repository data (when expiring)
  def expire_content_cache
    expire_tags_cache
    expire_tag_count_cache
    expire_branches_cache
    expire_branch_count_cache
    expire_root_ref_cache
    expire_emptiness_caches
    expire_exists_cache
  end

451 452 453
  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
454 455
    expire_root_ref_cache
    expire_emptiness_caches
Yorick Peterse's avatar
Yorick Peterse committed
456 457

    repository_event(:create_repository)
458 459
  end

460 461
  # Runs code just before a repository is deleted.
  def before_delete
462 463
    expire_exists_cache

464 465
    expire_cache if exists?

466
    expire_content_cache
Yorick Peterse's avatar
Yorick Peterse committed
467 468

    repository_event(:remove_repository)
469 470 471 472 473 474 475
  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
Yorick Peterse's avatar
Yorick Peterse committed
476 477

    repository_event(:change_default_branch)
478 479
  end

480 481
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
482
    expire_cache
483
    expire_tags_cache
484
    expire_tag_count_cache
Yorick Peterse's avatar
Yorick Peterse committed
485 486

    repository_event(:push_tag)
487 488 489 490 491 492
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
Yorick Peterse's avatar
Yorick Peterse committed
493 494

    repository_event(:remove_tag)
495 496
  end

497
  def before_import
498
    expire_content_cache
499 500
  end

501 502
  # Runs code after a repository has been forked/imported.
  def after_import
503 504
    expire_content_cache
    build_cache
505 506 507
  end

  # Runs code after a new commit has been pushed.
508 509
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
Yorick Peterse's avatar
Yorick Peterse committed
510 511

    repository_event(:push_commit, branch: branch_name)
512 513 514 515
  end

  # Runs code after a new branch has been created.
  def after_create_branch
516
    expire_branches_cache
517
    expire_has_visible_content_cache
518
    expire_branch_count_cache
Yorick Peterse's avatar
Yorick Peterse committed
519 520

    repository_event(:push_branch)
521 522
  end

523 524 525
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
Yorick Peterse's avatar
Yorick Peterse committed
526 527

    repository_event(:remove_branch)
528 529
  end

530 531 532
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
533
    expire_branch_count_cache
534
    expire_branches_cache
535 536
  end

537
  def method_missing(m, *args, &block)
538 539 540 541 542 543
    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
544 545
  end

546 547
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
548
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
549 550

  def blob_at(sha, path)
551
    unless Gitlab::Git.blank_ref?(sha)
552
      Blob.decorate(Gitlab::Git::Blob.find(self, sha, path))
553
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
554
  end
555

556 557 558 559
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

560
  def readme
561
    cache.fetch(:readme) { tree(:head).readme }
562
  end
563

564
  def version
565
    cache.fetch(:version) do
566
      tree(:head).blobs.find do |file|
567
        file.name.casecmp('version').zero?
568 569 570 571
      end
    end
  end

572
  def contribution_guide
573 574 575 576 577 578
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
579 580 581

  def changelog
    cache.fetch(:changelog) do
582
      file_on_head(/\A(changelog|history|changes|news)/i)
583
    end
584 585
  end

586
  def license_blob
587
    return nil unless head_exists?
588

589
    cache.fetch(:license_blob) do
590
      file_on_head(/\A(licen[sc]e|copying)(\..+|\z)/i)
591 592
    end
  end
593

594
  def license_key
595
    return nil unless head_exists?
596 597

    cache.fetch(:license_key) do
598
      Licensee.license(path).try(:key)
599
    end
600 601
  end

602 603 604 605 606 607 608 609
  def gitignore
    return nil if !exists? || empty?

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

610 611 612 613 614 615 616 617
  def koding_yml
    return nil unless head_exists?

    cache.fetch(:koding_yml) do
      file_on_head(/\A\.koding\.yml\z/)
    end
  end

618
  def gitlab_ci_yml
619
    return nil unless head_exists?
620 621 622 623

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

630
  def head_commit
631 632 633 634 635
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
636 637 638 639
  end

  def tree(sha = :head, path = nil)
    if sha == :head
640 641 642 643 644
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
645 646 647 648
    end

    Tree.new(self, sha, path)
  end
649 650

  def blob_at_branch(branch_name, path)
651
    last_commit = commit(branch_name)
652

653 654 655 656 657
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
658
  end
659 660 661 662 663 664 665 666

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
667
    if submodules(ref).any?
668 669 670 671 672 673 674
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
675 676

  def last_commit_for_path(sha, path)
677
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
678 679
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
680
  end
681

682
  def next_branch(name, opts = {})
683 684 685
    branch_ids = self.branch_names.map do |n|
      next 1 if n == name
      result = n.match(/\A#{name}-([0-9]+)\z/)
686 687 688
      result[1].to_i if result
    end.compact

689
    highest_branch_id = branch_ids.max || 0
690

691 692 693
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
694 695
  end

696
  # Remove archives older than 2 hours
697 698
  def branches_sorted_by(value)
    case value
699 700
    when 'name'
      branches.sort_by(&:name)
701
    when 'updated_desc'
702
      branches.sort do |a, b|
703
        commit(b.dereferenced_target).committed_date <=> commit(a.dereferenced_target).committed_date
704
      end
705
    when 'updated_asc'
706
      branches.sort do |a, b|
707
        commit(a.dereferenced_target).committed_date <=> commit(b.dereferenced_target).committed_date
708 709 710 711 712
      end
    else
      branches
    end
  end
713

714 715 716
  def tags_sorted_by(value)
    case value
    when 'name'
717
      VersionSorter.rsort(tags) { |tag| tag.name }
718 719 720 721 722 723 724 725 726
    when 'updated_desc'
      tags_sorted_by_committed_date.reverse
    when 'updated_asc'
      tags_sorted_by_committed_date
    else
      tags
    end
  end

727
  def contributors
728
    commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
729

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
730
    commits.group_by(&:author_email).map do |email, commits|
731 732
      contributor = Gitlab::Contributor.new
      contributor.email = email
733

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
734
      commits.each do |commit|
735
        if contributor.name.blank?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
736
          contributor.name = commit.author_name
737 738
        end

739
        contributor.commits += 1
740 741
      end

742 743
      contributor
    end
744
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
745

746 747
  def ref_name_for_sha(ref_path, sha)
    args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{ref_path} --contains #{sha})
748 749 750 751 752 753

    # Not found -> ["", 0]
    # Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0]
    Gitlab::Popen.popen(args, path_to_repo).first.split.last
  end

754 755
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
756 757 758 759 760 761 762 763 764 765 766 767 768 769
    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
770

771 772 773
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
774

775 776
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
777
  end
778

779
  def local_branches
780
    @local_branches ||= raw_repository.local_branches
781 782
  end

783 784
  alias_method :branches, :local_branches

785 786 787 788 789
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
790
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
791 792
  end

793
  def commit_dir(user, path, message, branch, author_email: nil, author_name: nil)
794
    update_branch_with_hooks(user, branch) do |ref|
795 796 797 798 799 800
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        }
Stan Hu's avatar
Stan Hu committed
801 802
      }

803 804
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))

Stan Hu's avatar
Stan Hu committed
805 806 807
      raw_repository.mkdir(path, options)
    end
  end
808

809
  def commit_file(user, path, content, message, branch, update, author_email: nil, author_name: nil)
810
    update_branch_with_hooks(user, branch) do |ref|
811 812 813 814 815 816 817 818 819 820 821
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          content: content,
          path: path,
          update: update
        }
822
      }
823

824
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
825

826 827
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
828 829
  end

830
  def update_file(user, path, content, branch:, previous_path:, message:, author_email: nil, author_name: nil)
831
    update_branch_with_hooks(user, branch) do |ref|
832 833 834 835 836 837 838 839 840 841 842
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          content: content,
          path: path,
          update: true
        }
843 844
      }

845
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
846

847
      if previous_path && previous_path != path
848
        options[:file][:previous_path] = previous_path
849
        Gitlab::Git::Blob.rename(raw_repository, options)
tiagonbotelho's avatar
tiagonbotelho committed
850
      else
851
        Gitlab::Git::Blob.commit(raw_repository, options)
tiagonbotelho's avatar
tiagonbotelho committed
852
      end
853 854 855
    end
  end

856
  def remove_file(user, path, message, branch, author_email: nil, author_name: nil)
857
    update_branch_with_hooks(user, branch) do |ref|
858 859 860 861 862 863 864 865 866
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          path: path
        }
867
      }
868

869
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
870

871 872
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
873 874
  end

Marc Siegfriedt's avatar
Marc Siegfriedt committed
875 876 877 878 879 880 881
  def multi_action(user:, branch:, message:, actions:, author_email: nil, author_name: nil)
    update_branch_with_hooks(user, branch) do |ref|
      index = rugged.index
      parents = []
      branch = find_branch(ref)

      if branch
882
        last_commit = branch.dereferenced_target
Marc Siegfriedt's avatar
Marc Siegfriedt committed
883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920
        index.read_tree(last_commit.raw_commit.tree)
        parents = [last_commit.sha]
      end

      actions.each do |action|
        case action[:action]
        when :create, :update, :move
          mode =
            case action[:action]
            when :update
              index.get(action[:file_path])[:mode]
            when :move
              index.get(action[:previous_path])[:mode]
            end
          mode ||= 0o100644

          index.remove(action[:previous_path]) if action[:action] == :move

          content = action[:encoding] == 'base64' ? Base64.decode64(action[:content]) : action[:content]
          oid = rugged.write(content, :blob)

          index.add(path: action[:file_path], oid: oid, mode: mode)
        when :delete
          index.remove(action[:file_path])
        end
      end

      options = {
        tree: index.write_tree(rugged),
        message: message,
        parents: parents
      }
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))

      Rugged::Commit.create(rugged, options)
    end
  end

921 922
  def get_committer_and_author(user, email: nil, name: nil)
    committer = user_to_committer(user)
923
    author = Gitlab::Git::committer_hash(email: email, name: name) || committer
924

925
    {
926 927
      author: author,
      committer: committer
928 929 930
    }
  end

931 932 933 934
  def user_to_committer(user)
    Gitlab::Git::committer_hash(email: user.email, name: user.name)
  end

935 936 937 938 939 940 941 942 943 944 945
  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

946 947 948
  def merge(user, merge_request, options = {})
    our_commit = rugged.branches[merge_request.target_branch].target
    their_commit = rugged.lookup(merge_request.diff_head_sha)
949 950 951 952 953 954 955

    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?

956
    update_branch_with_hooks(user, merge_request.target_branch) do
957 958 959 960
      actual_options = options.merge(
        parents: [our_commit, their_commit],
        tree: merge_index.write_tree(rugged),
      )
961

962 963 964
      commit_id = Rugged::Commit.create(rugged, actual_options)
      merge_request.update(in_progress_merge_commit_sha: commit_id)
      commit_id
965
    end
966 967
  end

968
  def revert(user, commit, base_branch, revert_tree_id = nil)
969
    source_sha = find_branch(base_branch).dereferenced_target.sha
970
    revert_tree_id ||= check_revert_content(commit, base_branch)
971

972
    return false unless revert_tree_id
973

974
    update_branch_with_hooks(user, base_branch) do
975
      committer = user_to_committer(user)
976
      source_sha = Rugged::Commit.create(rugged,
977
        message: commit.revert_message,
978 979
        author: committer,
        committer: committer,
980
        tree: revert_tree_id,
981
        parents: [rugged.lookup(source_sha)])
982
    end
983 984
  end

985
  def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
986
    source_sha = find_branch(base_branch).dereferenced_target.sha
987 988 989 990
    cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)

    return false unless cherry_pick_tree_id

991
    update_branch_with_hooks(user, base_branch) do
992 993 994 995 996 997 998 999 1000 1001
      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,
1002
        parents: [rugged.lookup(source_sha)])
1003 1004 1005
    end
  end

1006
  def resolve_conflicts(user, branch, params)
1007
    update_branch_with_hooks(user, branch) do
1008 1009 1010 1011 1012 1013
      committer = user_to_committer(user)

      Rugged::Commit.create(rugged, params.merge(author: committer, committer: committer))
    end
  end

1014
  def check_revert_content(commit, base_branch)
1015
    source_sha = find_branch(base_branch).dereferenced_target.sha
1016
    args       = [commit.id, source_sha]
1017
    args << { mainline: 1 } if commit.merge_commit?
1018 1019 1020 1021 1022 1023 1024 1025 1026 1027

    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

1028
  def check_cherry_pick_content(commit, base_branch)
1029
    source_sha = find_branch(base_branch).dereferenced_target.sha
1030
    args       = [commit.id, source_sha]
1031
    args << 1 if commit.merge_commit?
1032 1033 1034 1035 1036 1037 1038 1039 1040 1041

    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

1042 1043
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
1044 1045
  end

1046 1047 1048 1049 1050
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
1051 1052
      same_head = branch_commit.id == root_ref_commit.id
      !same_head && is_ancestor?(branch_commit.id, root_ref_commit.id)
1053 1054 1055 1056 1057
    else
      nil
    end
  end

1058
  def merge_base(first_commit_id, second_commit_id)
1059 1060
    first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
    second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
1061
    rugged.merge_base(first_commit_id, second_commit_id)
Douwe Maan's avatar
Douwe Maan committed
1062 1063
  rescue Rugged::ReferenceError
    nil
1064 1065
  end

1066 1067 1068 1069
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end

1070 1071
  def search_files(query, ref)
    offset = 2
1072
    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})
1073 1074 1075
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

1076
  def fetch_ref(source_path, source_ref, target_ref)
1077
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
1078 1079 1080
    Gitlab::Popen.popen(args, path_to_repo)
  end

1081 1082 1083 1084
  def create_ref(ref, ref_path)
    fetch_ref(path_to_repo, ref, ref_path)
  end

1085
  def update_branch_with_hooks(current_user, branch)
1086 1087
    update_autocrlf_option

1088
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
1089
    target_branch = find_branch(branch)
1090
    was_empty = empty?
1091

1092 1093
    # Make commit
    newrev = yield(ref)
1094

1095 1096 1097
    unless newrev
      raise CommitError.new('Failed to create commit')
    end
1098

1099 1100 1101
    if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil?
      oldrev = Gitlab::Git::BLANK_SHA
    else
1102
      oldrev = rugged.merge_base(newrev, target_branch.dereferenced_target.sha)
1103
    end
1104

1105
    GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
1106
      update_ref!(ref, newrev, oldrev)
1107

1108
      if was_empty || !target_branch
1109 1110 1111
        # If repo was empty expire cache
        after_create if was_empty
        after_create_branch
1112 1113
      end
    end
1114 1115

    newrev
1116 1117
  end

1118 1119 1120 1121 1122
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

1123 1124 1125 1126
  def gitattribute(path, name)
    raw_repository.attributes(path)[name]
  end

1127 1128 1129 1130 1131 1132 1133 1134 1135 1136
  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

1137
  def avatar
1138 1139
    return nil unless exists?

1140 1141
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
1142
        blob_at_branch(root_ref, file)
1143 1144 1145 1146
      end
    end
  end

1147 1148
  private

1149
  def cache
1150
    @cache ||= RepositoryCache.new(path_with_namespace, @project.id)
1151
  end
1152 1153 1154 1155

  def head_exists?
    exists? && !empty? && !rugged.head_unborn?
  end
1156 1157 1158 1159

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

  def tags_sorted_by_committed_date
1162
    tags.sort_by { |tag| tag.dereferenced_target.committed_date }
1163
  end
1164 1165 1166 1167

  def keep_around_ref_name(sha)
    "refs/keep-around/#{sha}"
  end
Yorick Peterse's avatar
Yorick Peterse committed
1168 1169 1170 1171

  def repository_event(event, tags = {})
    Gitlab::Metrics.add_event(event, { path: path_with_namespace }.merge(tags))
  end
1172
end