repository.rb 31.3 KB
Newer Older
1 2
require 'securerandom'

3
class Repository
4 5 6 7
  include Gitlab::ShellAdapter

  attr_accessor :path_with_namespace, :project

8 9
  class CommitError < StandardError; end

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
  # Methods that cache data from the Git repository.
  #
  # Each entry in this Array should have a corresponding method with the exact
  # same name. The cache key used by those methods must also match method's
  # name.
  #
  # For example, for entry `:readme` there's a method called `readme` which
  # stores its data in the `readme` cache key.
  CACHED_METHODS = %i(size commit_count readme version contribution_guide
                      changelog license_blob license_key gitignore koding_yml
                      gitlab_ci_yml branch_names tag_names branch_count
                      tag_count avatar exists? empty? root_ref)

  # Certain method caches should be refreshed when certain types of files are
  # changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
  # the corresponding methods to call for refreshing caches.
  METHOD_CACHES_FOR_FILE_TYPES = {
    readme: :readme,
    changelog: :changelog,
    license: %i(license_blob license_key),
    contributing: :contribution_guide,
    version: :version,
    gitignore: :gitignore,
    koding: :koding_yml,
    gitlab_ci: :gitlab_ci_yml,
    avatar: :avatar
  }

  # Wraps around the given method and caches its output in Redis and an instance
  # variable.
  #
  # This only works for methods that do not take any arguments.
  def self.cache_method(name, fallback: nil)
    original = :"_uncached_#{name}"
44

45
    alias_method(original, name)
46

47 48 49 50
    define_method(name) do
      cache_method_output(name, fallback: fallback) { __send__(original) }
    end
  end
51

52 53 54 55
  def self.storages
    Gitlab.config.repositories.storages
  end

56
  def initialize(path_with_namespace, project)
57
    @path_with_namespace = path_with_namespace
58
    @project = project
59
  end
60

61 62
  def raw_repository
    return nil unless path_with_namespace
63

64
    @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
65 66
  end

67 68 69 70
  def update_autocrlf_option
    raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
  end

71
  # Return absolute path to repository
72
  def path_to_repo
73
    @path_to_repo ||= File.expand_path(
74
      File.join(@project.repository_storage_path, path_with_namespace + ".git")
75
    )
76 77
  end

78 79 80 81 82 83 84 85 86 87
  #
  # 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?
88 89 90
    return @has_visible_content unless @has_visible_content.nil?

    @has_visible_content = cache.fetch(:has_visible_content?) do
91
      branch_count > 0
92
    end
93 94
  end

L
Lin Jen-Shin 已提交
95
  def commit(ref = 'HEAD')
96
    return nil unless exists?
97

98 99 100 101 102 103
    commit =
      if ref.is_a?(Gitlab::Git::Commit)
        ref
      else
        Gitlab::Git::Commit.find(raw_repository, ref)
      end
104

105
    commit = ::Commit.new(commit, @project) if commit
106
    commit
107
  rescue Rugged::OdbError, Rugged::TreeError
108
    nil
109 110
  end

111
  def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil)
112
    options = {
113 114 115 116 117
      repo: raw_repository,
      ref: ref,
      path: path,
      limit: limit,
      offset: offset,
118 119
      after: after,
      before: before,
120 121
      # --follow doesn't play well with --skip. See:
      # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
122 123
      follow: false,
      skip_merges: skip_merges
124 125 126
    }

    commits = Gitlab::Git::Commit.where(options)
127
    commits = Commit.decorate(commits, @project) if commits.present?
128 129 130
    commits
  end

131 132
  def commits_between(from, to)
    commits = Gitlab::Git::Commit.between(raw_repository, from, to)
133
    commits = Commit.decorate(commits, @project) if commits.present?
134 135 136
    commits
  end

137
  def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
138 139 140 141
    unless exists? && has_visible_content? && query.present?
      return []
    end

142 143
    ref ||= root_ref

144 145 146 147
    args = %W(
      #{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset}
      --max-count #{limit} --grep=#{query} --regexp-ignore-case
    )
148
    args = args.concat(%W(-- #{path})) if path.present?
149

150 151
    git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines
    git_log_results.map { |c| commit(c.chomp) }.compact
152 153
  end

154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
  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)
169 170 171
  end

  def find_tag(name)
172
    tags.find { |tag| tag.name == name }
173 174
  end

175 176 177 178 179 180 181 182
  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
183
      update_ref!(ref, target, oldrev)
184
    end
185

186
    after_create_branch
187
    find_branch(branch_name)
188 189
  end

190 191 192 193 194 195
  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
196

197 198
    options = { message: message, tagger: user_to_committer(user) } if message

199 200
    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
      rugged.tags.create(tag_name, target, options)
201
    end
202

203
    find_tag(tag_name)
204 205
  end

206
  def rm_branch(user, branch_name)
207
    before_remove_branch
208

209
    branch = find_branch(branch_name)
210
    oldrev = branch.try(:dereferenced_target).try(:id)
211 212 213 214
    newrev = Gitlab::Git::BLANK_SHA
    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name

    GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
215
      update_ref!(ref, newrev, oldrev)
216
    end
217

218
    after_remove_branch
219
    true
220 221
  end

222
  def rm_tag(tag_name)
Y
Yorick Peterse 已提交
223
    before_remove_tag
224

R
Robert Schilling 已提交
225 226 227 228 229 230
    begin
      rugged.tags.delete(tag_name)
      true
    rescue Rugged::ReferenceError
      false
    end
231 232
  end

233 234 235 236
  def ref_names
    branch_names + tag_names
  end

237 238 239 240
  def branch_exists?(branch_name)
    branch_names.include?(branch_name)
  end

241 242
  def ref_exists?(ref)
    rugged.references.exist?(ref)
243 244
  rescue Rugged::ReferenceError
    false
245 246
  end

247 248 249 250 251
  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.
252
    command = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z)
253
    _, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin|
254 255 256 257 258
      stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00")
    end

    return if status.zero?

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

D
Douwe Maan 已提交
262 263 264 265
  # 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.
266 267 268 269 270
  def keep_around(sha)
    return unless sha && commit(sha)

    return if kept_around?(sha)

271 272 273 274 275
    # 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}"
276 277 278
    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}"
279
    end
280 281 282
  end

  def kept_around?(sha)
283
    ref_exists?(keep_around_ref_name(sha))
284 285
  end

286
  def diverging_commit_counts(branch)
287
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
J
Jeff Stubler 已提交
288
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
289 290
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
291
      number_commits_behind = raw_repository.
292
        count_commits_between(branch.dereferenced_target.sha, root_ref_hash)
293 294

      number_commits_ahead = raw_repository.
295
        count_commits_between(root_ref_hash, branch.dereferenced_target.sha)
296

297 298 299
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
300

301 302 303
  def expire_tags_cache
    expire_method_caches(%i(tag_names tag_count))
    @tags = nil
304
  end
305

306 307 308
  def expire_branches_cache
    expire_method_caches(%i(branch_names branch_count))
    @local_branches = nil
309 310
  end

311 312
  def expire_statistics_caches
    expire_method_caches(%i(size commit_count))
313 314
  end

315 316
  def expire_all_method_caches
    expire_method_caches(CACHED_METHODS)
D
Douwe Maan 已提交
317 318
  end

319 320 321 322 323 324 325 326 327
  # Expires the caches of a specific set of methods
  def expire_method_caches(methods)
    methods.each do |key|
      cache.expire(key)

      ivar = cache_instance_variable_name(key)

      remove_instance_variable(ivar) if instance_variable_defined?(ivar)
    end
D
Douwe Maan 已提交
328 329
  end

330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
  def expire_avatar_cache
    expire_method_caches(%i(avatar))
  end

  # Refreshes the method caches of this repository.
  #
  # types - An Array of file types (e.g. `:readme`) used to refresh extra
  #         caches.
  def refresh_method_caches(types)
    to_refresh = []

    types.each do |type|
      methods = METHOD_CACHES_FOR_FILE_TYPES[type.to_sym]

      to_refresh.concat(Array(methods)) if methods
345
    end
346

347
    expire_method_caches(to_refresh)
348

349
    to_refresh.each { |method| send(method) }
350
  end
351

352 353 354 355 356 357 358 359 360 361 362 363
  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}")
364
    end
D
Dmitriy Zaporozhets 已提交
365 366
  end

367
  def expire_root_ref_cache
368
    expire_method_caches(%i(root_ref))
369 370
  end

371 372
  # Expires the cache(s) used to determine if a repository is empty or not.
  def expire_emptiness_caches
373
    return unless empty?
374

375
    expire_method_caches(%i(empty?))
376 377 378
    expire_has_visible_content_cache
  end

379 380 381 382 383
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

384 385 386 387
  def lookup_cache
    @lookup_cache ||= {}
  end

388
  def expire_exists_cache
389
    expire_method_caches(%i(exists?))
390 391
  end

392 393 394 395 396 397 398
  # expire cache that doesn't depend on repository data (when expiring)
  def expire_content_cache
    expire_tags_cache
    expire_branches_cache
    expire_root_ref_cache
    expire_emptiness_caches
    expire_exists_cache
399
    expire_statistics_caches
400 401
  end

402 403 404
  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
405 406
    expire_root_ref_cache
    expire_emptiness_caches
Y
Yorick Peterse 已提交
407 408

    repository_event(:create_repository)
409 410
  end

411 412
  # Runs code just before a repository is deleted.
  def before_delete
413
    expire_exists_cache
414 415
    expire_all_method_caches
    expire_branch_cache if exists?
416
    expire_content_cache
Y
Yorick Peterse 已提交
417 418

    repository_event(:remove_repository)
419 420 421 422 423 424 425
  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
Y
Yorick Peterse 已提交
426 427

    repository_event(:change_default_branch)
428 429
  end

Y
Yorick Peterse 已提交
430 431
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
432 433
    expire_statistics_caches
    expire_emptiness_caches
434
    expire_tags_cache
Y
Yorick Peterse 已提交
435 436

    repository_event(:push_tag)
Y
Yorick Peterse 已提交
437 438 439 440 441
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
442
    expire_statistics_caches
Y
Yorick Peterse 已提交
443 444

    repository_event(:remove_tag)
445 446
  end

447
  def before_import
448
    expire_content_cache
449 450
  end

451 452
  # Runs code after a repository has been forked/imported.
  def after_import
453
    expire_content_cache
454 455
    expire_tags_cache
    expire_branches_cache
456 457 458
  end

  # Runs code after a new commit has been pushed.
459 460 461
  def after_push_commit(branch_name)
    expire_statistics_caches
    expire_branch_cache(branch_name)
Y
Yorick Peterse 已提交
462 463

    repository_event(:push_commit, branch: branch_name)
464 465 466 467
  end

  # Runs code after a new branch has been created.
  def after_create_branch
468
    expire_branches_cache
469
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
470 471

    repository_event(:push_branch)
472 473
  end

474 475 476
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
Y
Yorick Peterse 已提交
477 478

    repository_event(:remove_branch)
479 480
  end

481 482 483
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
484
    expire_branches_cache
485 486
  end

487
  def method_missing(m, *args, &block)
488 489 490 491 492 493
    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
494 495
  end

496 497
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
498
  end
D
Dmitriy Zaporozhets 已提交
499 500

  def blob_at(sha, path)
501
    unless Gitlab::Git.blank_ref?(sha)
502
      Blob.decorate(Gitlab::Git::Blob.find(self, sha, path))
503
    end
D
Dmitriy Zaporozhets 已提交
504
  end
505

506 507 508 509
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
  def root_ref
    if raw_repository
      raw_repository.root_ref
    else
      # When the repo does not exist we raise this error so no data is cached.
      raise Rugged::ReferenceError
    end
  end
  cache_method :root_ref

  def exists?
    refs_directory_exists?
  end
  cache_method :exists?

  def empty?
    raw_repository.empty?
  end
  cache_method :empty?

  # The size of this repository in megabytes.
  def size
    exists? ? raw_repository.size : 0.0
  end
  cache_method :size, fallback: 0.0

  def commit_count
    root_ref ? raw_repository.commit_count(root_ref) : 0
  end
  cache_method :commit_count, fallback: 0

  def branch_names
    branches.map(&:name)
  end
  cache_method :branch_names, fallback: []

  def tag_names
    raw_repository.tag_names
  end
  cache_method :tag_names, fallback: []

  def branch_count
    branches.size
  end
  cache_method :branch_count, fallback: 0

  def tag_count
    raw_repository.rugged.tags.count
  end
  cache_method :tag_count, fallback: 0

  def avatar
    if tree = file_on_head(:avatar)
      tree.path
    end
  end
  cache_method :avatar

568
  def readme
569 570 571
    if head = tree(:head)
      head.readme
    end
572
  end
573
  cache_method :readme
574

575
  def version
576
    file_on_head(:version)
577
  end
578
  cache_method :version
579

580
  def contribution_guide
581
    file_on_head(:contributing)
582
  end
583
  cache_method :contribution_guide
584 585

  def changelog
586
    file_on_head(:changelog)
587
  end
588
  cache_method :changelog
589

590
  def license_blob
591
    file_on_head(:license)
592
  end
593
  cache_method :license_blob
Z
Zeger-Jan van de Weg 已提交
594

595
  def license_key
596
    return unless exists?
597

598
    Licensee.license(path).try(:key)
599
  end
600
  cache_method :license_key
601

602
  def gitignore
603
    file_on_head(:gitignore)
604
  end
605
  cache_method :gitignore
606

607
  def koding_yml
608
    file_on_head(:koding)
609
  end
610
  cache_method :koding_yml
611

612
  def gitlab_ci_yml
613
    file_on_head(:gitlab_ci)
614
  end
615
  cache_method :gitlab_ci_yml
616

617
  def head_commit
618 619 620 621
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
622 623 624
    if head_commit
      @head_tree ||= Tree.new(self, head_commit.sha, nil)
    end
625 626
  end

627
  def tree(sha = :head, path = nil, recursive: false)
628
    if sha == :head
629 630
      return unless head_commit

631 632 633 634 635
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
636 637
    end

638
    Tree.new(self, sha, path, recursive: recursive)
639
  end
D
Dmitriy Zaporozhets 已提交
640 641

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

D
Dmitriy Zaporozhets 已提交
644 645 646 647 648
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
649
  end
D
Dmitriy Zaporozhets 已提交
650 651 652 653 654 655 656 657

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
658
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
659 660 661 662 663 664 665
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
666 667

  def last_commit_for_path(sha, path)
668
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
669 670
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
671
  end
672

673
  def next_branch(name, opts = {})
P
P.S.V.R 已提交
674 675 676
    branch_ids = self.branch_names.map do |n|
      next 1 if n == name
      result = n.match(/\A#{name}-([0-9]+)\z/)
677 678 679
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
680
    highest_branch_id = branch_ids.max || 0
681

P
P.S.V.R 已提交
682 683 684
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
685 686
  end

687
  # Remove archives older than 2 hours
688 689
  def branches_sorted_by(value)
    case value
690 691
    when 'name'
      branches.sort_by(&:name)
692
    when 'updated_desc'
693
      branches.sort do |a, b|
694
        commit(b.dereferenced_target).committed_date <=> commit(a.dereferenced_target).committed_date
695
      end
696
    when 'updated_asc'
697
      branches.sort do |a, b|
698
        commit(a.dereferenced_target).committed_date <=> commit(b.dereferenced_target).committed_date
699 700 701 702 703
      end
    else
      branches
    end
  end
704

705 706 707
  def tags_sorted_by(value)
    case value
    when 'name'
708
      VersionSorter.rsort(tags) { |tag| tag.name }
709 710 711 712 713 714 715 716 717
    when 'updated_desc'
      tags_sorted_by_committed_date.reverse
    when 'updated_asc'
      tags_sorted_by_committed_date
    else
      tags
    end
  end

718
  def contributors
719
    commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
720

D
Dmitriy Zaporozhets 已提交
721
    commits.group_by(&:author_email).map do |email, commits|
722 723
      contributor = Gitlab::Contributor.new
      contributor.email = email
724

D
Dmitriy Zaporozhets 已提交
725
      commits.each do |commit|
726
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
727
          contributor.name = commit.author_name
728 729
        end

730
        contributor.commits += 1
731 732
      end

733 734
      contributor
    end
735
  end
D
Dmitriy Zaporozhets 已提交
736

737 738
  def ref_name_for_sha(ref_path, sha)
    args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{ref_path} --contains #{sha})
739 740 741 742 743 744

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

745 746
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
747 748 749 750 751 752 753 754 755 756 757 758 759 760
    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 已提交
761

762 763 764
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
765

766 767
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
768
  end
769

770
  def local_branches
771
    @local_branches ||= raw_repository.local_branches
772 773
  end

774 775
  alias_method :branches, :local_branches

776 777 778 779
  def tags
    @tags ||= raw_repository.tags
  end

780
  def commit_dir(user, path, message, branch, author_email: nil, author_name: nil)
781
    update_branch_with_hooks(user, branch) do |ref|
782 783 784 785 786 787
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        }
S
Stan Hu 已提交
788 789
      }

790 791
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))

S
Stan Hu 已提交
792 793 794
      raw_repository.mkdir(path, options)
    end
  end
795

796
  def commit_file(user, path, content, message, branch, update, author_email: nil, author_name: nil)
797
    update_branch_with_hooks(user, branch) do |ref|
798 799 800 801 802 803 804 805 806 807 808
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          content: content,
          path: path,
          update: update
        }
809
      }
810

811
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
812

813 814
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
815 816
  end

817
  def update_file(user, path, content, branch:, previous_path:, message:, author_email: nil, author_name: nil)
818
    update_branch_with_hooks(user, branch) do |ref|
819 820 821 822 823 824 825 826 827 828 829
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          content: content,
          path: path,
          update: true
        }
830 831
      }

832
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
833

834
      if previous_path && previous_path != path
835
        options[:file][:previous_path] = previous_path
836
        Gitlab::Git::Blob.rename(raw_repository, options)
T
tiagonbotelho 已提交
837
      else
838
        Gitlab::Git::Blob.commit(raw_repository, options)
T
tiagonbotelho 已提交
839
      end
840 841 842
    end
  end

843
  def remove_file(user, path, message, branch, author_email: nil, author_name: nil)
844
    update_branch_with_hooks(user, branch) do |ref|
845 846 847 848 849 850 851 852 853
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          path: path
        }
854
      }
855

856
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
857

858 859
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
860 861
  end

M
Marc Siegfriedt 已提交
862 863 864 865 866 867 868
  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
869
        last_commit = branch.dereferenced_target
M
Marc Siegfriedt 已提交
870 871 872 873 874 875 876 877 878 879 880 881 882 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
        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

908 909
  def get_committer_and_author(user, email: nil, name: nil)
    committer = user_to_committer(user)
D
Dan Dunckel 已提交
910
    author = Gitlab::Git::committer_hash(email: email, name: name) || committer
911

912
    {
913 914
      author: author,
      committer: committer
915 916 917
    }
  end

918 919 920 921
  def user_to_committer(user)
    Gitlab::Git::committer_hash(email: user.email, name: user.name)
  end

922 923 924 925 926 927 928 929 930 931 932
  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

933 934 935
  def merge(user, merge_request, options = {})
    our_commit = rugged.branches[merge_request.target_branch].target
    their_commit = rugged.lookup(merge_request.diff_head_sha)
936 937 938 939 940 941 942

    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?

943
    update_branch_with_hooks(user, merge_request.target_branch) do
944 945 946 947
      actual_options = options.merge(
        parents: [our_commit, their_commit],
        tree: merge_index.write_tree(rugged),
      )
948

949 950 951
      commit_id = Rugged::Commit.create(rugged, actual_options)
      merge_request.update(in_progress_merge_commit_sha: commit_id)
      commit_id
952
    end
953 954
  end

955
  def revert(user, commit, base_branch, revert_tree_id = nil)
956
    source_sha = find_branch(base_branch).dereferenced_target.sha
957
    revert_tree_id ||= check_revert_content(commit, base_branch)
958

959
    return false unless revert_tree_id
960

961
    update_branch_with_hooks(user, base_branch) do
962
      committer = user_to_committer(user)
963
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
964
        message: commit.revert_message,
965 966
        author: committer,
        committer: committer,
967
        tree: revert_tree_id,
968
        parents: [rugged.lookup(source_sha)])
969
    end
970 971
  end

P
P.S.V.R 已提交
972
  def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
973
    source_sha = find_branch(base_branch).dereferenced_target.sha
P
P.S.V.R 已提交
974 975 976 977
    cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)

    return false unless cherry_pick_tree_id

978
    update_branch_with_hooks(user, base_branch) do
P
P.S.V.R 已提交
979 980 981 982 983 984 985 986 987 988
      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,
989
        parents: [rugged.lookup(source_sha)])
P
P.S.V.R 已提交
990 991 992
    end
  end

993
  def resolve_conflicts(user, branch, params)
994
    update_branch_with_hooks(user, branch) do
995 996 997 998 999 1000
      committer = user_to_committer(user)

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

1001
  def check_revert_content(commit, base_branch)
1002
    source_sha = find_branch(base_branch).dereferenced_target.sha
1003
    args       = [commit.id, source_sha]
1004
    args << { mainline: 1 } if commit.merge_commit?
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014

    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 已提交
1015
  def check_cherry_pick_content(commit, base_branch)
1016
    source_sha = find_branch(base_branch).dereferenced_target.sha
P
P.S.V.R 已提交
1017
    args       = [commit.id, source_sha]
1018
    args << 1 if commit.merge_commit?
P
P.S.V.R 已提交
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028

    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

1029 1030
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
1031 1032
  end

F
Florent (HP) 已提交
1033 1034 1035 1036 1037
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
1038 1039
      same_head = branch_commit.id == root_ref_commit.id
      !same_head && is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
1040 1041 1042 1043 1044
    else
      nil
    end
  end

S
Stan Hu 已提交
1045
  def merge_base(first_commit_id, second_commit_id)
1046 1047
    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 已提交
1048
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
1049 1050
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
1051 1052
  end

1053 1054 1055 1056
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end

V
Valery Sizov 已提交
1057 1058 1059 1060 1061 1062
  def empty_repo?
    !exists? || !has_visible_content?
  end

  def search_files_by_content(query, ref)
    return [] if empty_repo? || query.blank?
V
Valery Sizov 已提交
1063

1064
    offset = 2
1065
    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})
1066 1067 1068
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

V
Valery Sizov 已提交
1069 1070 1071 1072 1073 1074 1075
  def search_files_by_name(query, ref)
    return [] if empty_repo? || query.blank?

    args = %W(#{Gitlab.config.git.bin_path} ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)})
    Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:strip)
  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 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162
  # Caches the supplied block both in a cache and in an instance variable.
  #
  # The cache key and instance variable are named the same way as the value of
  # the `key` argument.
  #
  # This method will return `nil` if the corresponding instance variable is also
  # set to `nil`. This ensures we don't keep yielding the block when it returns
  # `nil`.
  #
  # key - The name of the key to cache the data in.
  # fallback - A value to fall back to in the event of a Git error.
  def cache_method_output(key, fallback: nil, &block)
    ivar = cache_instance_variable_name(key)

    if instance_variable_defined?(ivar)
      instance_variable_get(ivar)
    else
      begin
        instance_variable_set(ivar, cache.fetch(key, &block))
      rescue Rugged::ReferenceError, Gitlab::Git::Repository::NoRepository
        # if e.g. HEAD or the entire repository doesn't exist we want to
        # gracefully handle this and not cache anything.
        fallback
      end
    end
  end
1163

1164 1165 1166 1167 1168 1169 1170 1171
  def cache_instance_variable_name(key)
    :"@#{key.to_s.tr('?!', '')}"
  end

  def file_on_head(type)
    if head = tree(:head)
      head.blobs.find do |file|
        Gitlab::FileDetector.type_of(file.name) == type
1172 1173 1174 1175
      end
    end
  end

1176 1177
  private

1178 1179 1180 1181 1182 1183
  def refs_directory_exists?
    return false unless path_with_namespace

    File.exist?(File.join(path_to_repo, 'refs'))
  end

1184
  def cache
1185
    @cache ||= RepositoryCache.new(path_with_namespace, @project.id)
1186
  end
1187

1188
  def tags_sorted_by_committed_date
1189
    tags.sort_by { |tag| tag.dereferenced_target.committed_date }
1190
  end
D
Douwe Maan 已提交
1191 1192 1193 1194

  def keep_around_ref_name(sha)
    "refs/keep-around/#{sha}"
  end
Y
Yorick Peterse 已提交
1195 1196 1197 1198

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