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

3
class Repository
4 5
  include Gitlab::ShellAdapter

6
  attr_accessor :path_with_namespace, :project
7

8
  CommitError = Class.new(StandardError)
9

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
  # 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
  }

38 39 40
  ROUTE_MAP_PATH = '.gitlab/route-map.yml'
  GITLAB_CI_YML_PATH = '.gitlab-ci.yml'

41 42 43 44 45 46
  # 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}"
47

48
    alias_method(original, name)
49

50 51
    define_method(name) do
      cache_method_output(name, fallback: fallback) { __send__(original) }
52
    end
53
  end
54 55 56 57 58

  def self.storages
    Gitlab.config.repositories.storages
  end

59
  def initialize(path_with_namespace, project)
60
    @path_with_namespace = path_with_namespace
61
    @project = project
62
  end
63

64 65
  def raw_repository
    return nil unless path_with_namespace
66

67
    @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
68 69
  end

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

77 78 79 80 81 82 83 84 85 86
  #
  # 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?
87
    branch_count > 0
88 89
  end

L
Lin Jen-Shin 已提交
90
  def commit(ref = 'HEAD')
91
    return nil unless exists?
92

93 94 95 96 97 98
    commit =
      if ref.is_a?(Gitlab::Git::Commit)
        ref
      else
        Gitlab::Git::Commit.find(raw_repository, ref)
      end
99

100
    commit = ::Commit.new(commit, @project) if commit
101
    commit
102
  rescue Rugged::OdbError, Rugged::TreeError
103
    nil
104 105
  end

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

    commits = Gitlab::Git::Commit.where(options)
122
    commits = Commit.decorate(commits, @project) if commits.present?
123 124 125
    commits
  end

126 127
  def commits_between(from, to)
    commits = Gitlab::Git::Commit.between(raw_repository, from, to)
128
    commits = Commit.decorate(commits, @project) if commits.present?
129 130 131
    commits
  end

132
  def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
133 134 135 136
    unless exists? && has_visible_content? && query.present?
      return []
    end

137 138
    ref ||= root_ref

139 140 141 142
    args = %W(
      #{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset}
      --max-count #{limit} --grep=#{query} --regexp-ignore-case
    )
143
    args = args.concat(%W(-- #{path})) if path.present?
144

145 146
    git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines
    git_log_results.map { |c| commit(c.chomp) }.compact
147 148
  end

149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
  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)
164 165 166
  end

  def find_tag(name)
167
    tags.find { |tag| tag.name == name }
168 169
  end

170 171
  def add_branch(user, branch_name, ref)
    newrev = commit(ref).try(:sha)
172

173
    return false unless newrev
174

175
    GitOperationService.new(user, self).add_branch(branch_name, newrev)
176

177
    after_create_branch
178
    find_branch(branch_name)
179 180
  end

181
  def add_tag(user, tag_name, target, message = nil)
182
    newrev = commit(target).try(:id)
183 184
    options = { message: message, tagger: user_to_committer(user) } if message

185 186 187
    return false unless newrev

    GitOperationService.new(user, self).add_tag(tag_name, newrev, options)
188

189
    find_tag(tag_name)
190 191
  end

192
  def rm_branch(user, branch_name)
193
    before_remove_branch
194 195
    branch = find_branch(branch_name)

196
    GitOperationService.new(user, self).rm_branch(branch)
197

198
    after_remove_branch
199
    true
200 201
  end

L
Lin Jen-Shin 已提交
202
  def rm_tag(user, tag_name)
Y
Yorick Peterse 已提交
203
    before_remove_tag
L
Lin Jen-Shin 已提交
204
    tag = find_tag(tag_name)
205

L
Lin Jen-Shin 已提交
206 207 208 209
    GitOperationService.new(user, self).rm_tag(tag)

    after_remove_tag
    true
210 211
  end

212 213 214 215
  def ref_names
    branch_names + tag_names
  end

216 217 218 219
  def branch_exists?(branch_name)
    branch_names.include?(branch_name)
  end

220 221
  def ref_exists?(ref)
    rugged.references.exist?(ref)
222 223
  rescue Rugged::ReferenceError
    false
224 225
  end

D
Douwe Maan 已提交
226 227 228 229
  # 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.
230 231 232 233 234
  def keep_around(sha)
    return unless sha && commit(sha)

    return if kept_around?(sha)

235 236 237 238 239
    # 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}"
240 241 242
    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}"
243
    end
244 245 246
  end

  def kept_around?(sha)
247
    ref_exists?(keep_around_ref_name(sha))
248 249
  end

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

      number_commits_ahead = raw_repository.
259
        count_commits_between(root_ref_hash, branch.dereferenced_target.sha)
260

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

265 266 267
  def expire_tags_cache
    expire_method_caches(%i(tag_names tag_count))
    @tags = nil
268
  end
269

270 271 272
  def expire_branches_cache
    expire_method_caches(%i(branch_names branch_count))
    @local_branches = nil
273 274
  end

275 276
  def expire_statistics_caches
    expire_method_caches(%i(size commit_count))
277 278
  end

279 280
  def expire_all_method_caches
    expire_method_caches(CACHED_METHODS)
D
Douwe Maan 已提交
281 282
  end

283 284 285 286 287 288 289 290 291
  # 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 已提交
292 293
  end

294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
  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
309
    end
310

311
    expire_method_caches(to_refresh)
312

313
    to_refresh.each { |method| send(method) }
314
  end
315

316 317 318 319 320 321 322 323 324 325 326 327
  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}")
328
    end
D
Dmitriy Zaporozhets 已提交
329 330
  end

331
  def expire_root_ref_cache
332
    expire_method_caches(%i(root_ref))
333 334
  end

335 336
  # Expires the cache(s) used to determine if a repository is empty or not.
  def expire_emptiness_caches
337
    return unless empty?
338

339
    expire_method_caches(%i(empty?))
340 341
  end

342 343 344 345
  def lookup_cache
    @lookup_cache ||= {}
  end

346
  def expire_exists_cache
347
    expire_method_caches(%i(exists?))
348 349
  end

350 351 352 353 354 355 356
  # 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
357
    expire_statistics_caches
358 359
  end

360 361 362
  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
363 364
    expire_root_ref_cache
    expire_emptiness_caches
Y
Yorick Peterse 已提交
365 366

    repository_event(:create_repository)
367 368
  end

369 370
  # Runs code just before a repository is deleted.
  def before_delete
371
    expire_exists_cache
372 373
    expire_all_method_caches
    expire_branch_cache if exists?
374
    expire_content_cache
Y
Yorick Peterse 已提交
375 376

    repository_event(:remove_repository)
377 378 379 380 381 382 383
  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 已提交
384 385

    repository_event(:change_default_branch)
386 387
  end

Y
Yorick Peterse 已提交
388 389
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
390 391
    expire_statistics_caches
    expire_emptiness_caches
392
    expire_tags_cache
Y
Yorick Peterse 已提交
393 394

    repository_event(:push_tag)
Y
Yorick Peterse 已提交
395 396 397 398 399
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
400
    expire_statistics_caches
Y
Yorick Peterse 已提交
401 402

    repository_event(:remove_tag)
403 404
  end

L
Lin Jen-Shin 已提交
405 406 407 408 409
  # Runs code after removing a tag.
  def after_remove_tag
    expire_tags_cache
  end

410
  def before_import
411
    expire_content_cache
412 413
  end

414 415 416 417 418
  # Runs code after the HEAD of a repository is changed.
  def after_change_head
    expire_method_caches(METHOD_CACHES_FOR_FILE_TYPES.keys)
  end

419 420
  # Runs code after a repository has been forked/imported.
  def after_import
421
    expire_content_cache
422 423
    expire_tags_cache
    expire_branches_cache
424 425 426
  end

  # Runs code after a new commit has been pushed.
427 428 429
  def after_push_commit(branch_name)
    expire_statistics_caches
    expire_branch_cache(branch_name)
Y
Yorick Peterse 已提交
430 431

    repository_event(:push_commit, branch: branch_name)
432 433 434 435
  end

  # Runs code after a new branch has been created.
  def after_create_branch
436
    expire_branches_cache
Y
Yorick Peterse 已提交
437 438

    repository_event(:push_branch)
439 440
  end

441 442 443
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
Y
Yorick Peterse 已提交
444 445

    repository_event(:remove_branch)
446 447
  end

448 449
  # Runs code after an existing branch has been removed.
  def after_remove_branch
450
    expire_branches_cache
451 452
  end

453
  def method_missing(m, *args, &block)
454 455 456 457 458 459
    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
460 461
  end

462 463
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
464
  end
D
Dmitriy Zaporozhets 已提交
465 466

  def blob_at(sha, path)
467
    unless Gitlab::Git.blank_ref?(sha)
468
      Blob.decorate(Gitlab::Git::Blob.find(self, sha, path))
469
    end
D
Dmitriy Zaporozhets 已提交
470
  end
471

472 473 474 475
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
  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

534
  def readme
535 536 537
    if head = tree(:head)
      head.readme
    end
538
  end
539
  cache_method :readme
540

541
  def version
542
    file_on_head(:version)
543
  end
544
  cache_method :version
545

546
  def contribution_guide
547
    file_on_head(:contributing)
548
  end
549
  cache_method :contribution_guide
550 551

  def changelog
552
    file_on_head(:changelog)
553
  end
554
  cache_method :changelog
555

556
  def license_blob
557
    file_on_head(:license)
558
  end
559
  cache_method :license_blob
Z
Zeger-Jan van de Weg 已提交
560

561
  def license_key
562
    return unless exists?
563

564
    Licensee.license(path).try(:key)
565
  end
566
  cache_method :license_key
567

568
  def gitignore
569
    file_on_head(:gitignore)
570
  end
571
  cache_method :gitignore
572

573
  def koding_yml
574
    file_on_head(:koding)
575
  end
576
  cache_method :koding_yml
577

578
  def gitlab_ci_yml
579
    file_on_head(:gitlab_ci)
580
  end
581
  cache_method :gitlab_ci_yml
582

583
  def head_commit
584 585 586 587
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
588 589 590
    if head_commit
      @head_tree ||= Tree.new(self, head_commit.sha, nil)
    end
591 592
  end

593
  def tree(sha = :head, path = nil, recursive: false)
594
    if sha == :head
595 596
      return unless head_commit

597 598 599 600 601
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
602 603
    end

604
    Tree.new(self, sha, path, recursive: recursive)
605
  end
D
Dmitriy Zaporozhets 已提交
606 607

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

D
Dmitriy Zaporozhets 已提交
610 611 612 613 614
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
615
  end
D
Dmitriy Zaporozhets 已提交
616 617 618 619 620 621 622 623

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
624
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
625 626 627 628 629 630 631
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
632 633

  def last_commit_for_path(sha, path)
H
Hiroyuki Sato 已提交
634
    sha = last_commit_id_for_path(sha, path)
635
    commit(sha)
636
  end
637

H
Hiroyuki Sato 已提交
638 639
  def last_commit_id_for_path(sha, path)
    key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}"
H
Hiroyuki Sato 已提交
640

H
Hiroyuki Sato 已提交
641
    cache.fetch(key) do
H
Hiroyuki Sato 已提交
642 643
      args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
      Gitlab::Popen.popen(args, path_to_repo).first.strip
H
Hiroyuki Sato 已提交
644 645 646
    end
  end

647
  def next_branch(name, opts = {})
P
P.S.V.R 已提交
648 649 650
    branch_ids = self.branch_names.map do |n|
      next 1 if n == name
      result = n.match(/\A#{name}-([0-9]+)\z/)
651 652 653
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
654
    highest_branch_id = branch_ids.max || 0
655

P
P.S.V.R 已提交
656 657 658
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
659 660
  end

661
  # Remove archives older than 2 hours
662 663
  def branches_sorted_by(value)
    case value
664 665
    when 'name'
      branches.sort_by(&:name)
666
    when 'updated_desc'
667
      branches.sort do |a, b|
668
        commit(b.dereferenced_target).committed_date <=> commit(a.dereferenced_target).committed_date
669
      end
670
    when 'updated_asc'
671
      branches.sort do |a, b|
672
        commit(a.dereferenced_target).committed_date <=> commit(b.dereferenced_target).committed_date
673 674 675 676 677
      end
    else
      branches
    end
  end
678

679 680 681
  def tags_sorted_by(value)
    case value
    when 'name'
682
      VersionSorter.rsort(tags) { |tag| tag.name }
683 684 685 686 687 688 689 690 691
    when 'updated_desc'
      tags_sorted_by_committed_date.reverse
    when 'updated_asc'
      tags_sorted_by_committed_date
    else
      tags
    end
  end

692
  def contributors
693
    commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
694

D
Dmitriy Zaporozhets 已提交
695
    commits.group_by(&:author_email).map do |email, commits|
696 697
      contributor = Gitlab::Contributor.new
      contributor.email = email
698

D
Dmitriy Zaporozhets 已提交
699
      commits.each do |commit|
700
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
701
          contributor.name = commit.author_name
702 703
        end

704
        contributor.commits += 1
705 706
      end

707 708
      contributor
    end
709
  end
D
Dmitriy Zaporozhets 已提交
710

711 712
  def ref_name_for_sha(ref_path, sha)
    args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{ref_path} --contains #{sha})
713 714 715 716 717 718

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

719 720
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
721 722 723 724 725 726 727 728 729 730 731 732 733 734
    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 已提交
735

736 737 738
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
739

740 741
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
742
  end
743

744
  def local_branches
745
    @local_branches ||= raw_repository.local_branches
746 747
  end

748 749
  alias_method :branches, :local_branches

750 751 752 753
  def tags
    @tags ||= raw_repository.tags
  end

754
  # rubocop:disable Metrics/ParameterLists
L
Lin Jen-Shin 已提交
755
  def commit_dir(
756 757
    user, path,
    message:, branch_name:,
758
    author_email: nil, author_name: nil,
759
    start_branch_name: nil, start_project: project)
760 761
    check_tree_entry_for_dir(branch_name, path)

762 763 764
    if start_branch_name
      start_project.repository.
        check_tree_entry_for_dir(start_branch_name, path)
L
Lin Jen-Shin 已提交
765 766 767 768
    end

    commit_file(
      user,
769
      "#{path}/.gitkeep",
L
Lin Jen-Shin 已提交
770
      '',
771 772 773
      message: message,
      branch_name: branch_name,
      update: false,
L
Lin Jen-Shin 已提交
774 775
      author_email: author_email,
      author_name: author_name,
776 777
      start_branch_name: start_branch_name,
      start_project: start_project)
S
Stan Hu 已提交
778
  end
779
  # rubocop:enable Metrics/ParameterLists
780

L
Lin Jen-Shin 已提交
781 782
  # rubocop:disable Metrics/ParameterLists
  def commit_file(
783 784
    user, path, content,
    message:, branch_name:, update: true,
785
    author_email: nil, author_name: nil,
786
    start_branch_name: nil, start_project: project)
787 788 789 790 791 792 793
    unless update
      error_message = "Filename already exists; update not allowed"

      if tree_entry_at(branch_name, path)
        raise Gitlab::Git::Repository::InvalidBlobName.new(error_message)
      end

794 795
      if start_branch_name &&
          start_project.repository.tree_entry_at(start_branch_name, path)
796
        raise Gitlab::Git::Repository::InvalidBlobName.new(error_message)
797 798 799
      end
    end

800 801 802
    multi_action(
      user: user,
      message: message,
803
      branch_name: branch_name,
804 805
      author_email: author_email,
      author_name: author_name,
806 807
      start_branch_name: start_branch_name,
      start_project: start_project,
L
Lin Jen-Shin 已提交
808 809 810
      actions: [{ action: :create,
                  file_path: path,
                  content: content }])
811
  end
L
Lin Jen-Shin 已提交
812
  # rubocop:enable Metrics/ParameterLists
813

L
Lin Jen-Shin 已提交
814 815 816
  # rubocop:disable Metrics/ParameterLists
  def update_file(
    user, path, content,
817
    message:, branch_name:, previous_path:,
818
    author_email: nil, author_name: nil,
819
    start_branch_name: nil, start_project: project)
820 821 822 823 824 825 826 827 828
    action = if previous_path && previous_path != path
               :move
             else
               :update
             end

    multi_action(
      user: user,
      message: message,
829
      branch_name: branch_name,
830 831
      author_email: author_email,
      author_name: author_name,
832 833
      start_branch_name: start_branch_name,
      start_project: start_project,
L
Lin Jen-Shin 已提交
834 835 836 837
      actions: [{ action: action,
                  file_path: path,
                  content: content,
                  previous_path: previous_path }])
838
  end
L
Lin Jen-Shin 已提交
839
  # rubocop:enable Metrics/ParameterLists
840

841
  # rubocop:disable Metrics/ParameterLists
L
Lin Jen-Shin 已提交
842
  def remove_file(
843 844
    user, path,
    message:, branch_name:,
845
    author_email: nil, author_name: nil,
846
    start_branch_name: nil, start_project: project)
847 848 849
    multi_action(
      user: user,
      message: message,
850
      branch_name: branch_name,
851 852
      author_email: author_email,
      author_name: author_name,
853 854
      start_branch_name: start_branch_name,
      start_project: start_project,
L
Lin Jen-Shin 已提交
855 856
      actions: [{ action: :delete,
                  file_path: path }])
857
  end
858
  # rubocop:enable Metrics/ParameterLists
859

860
  # rubocop:disable Metrics/ParameterLists
L
Lin Jen-Shin 已提交
861
  def multi_action(
862
    user:, branch_name:, message:, actions:,
863
    author_email: nil, author_name: nil,
864
    start_branch_name: nil, start_project: project)
865
    GitOperationService.new(user, self).with_branch(
866
      branch_name,
867 868
      start_branch_name: start_branch_name,
      start_project: start_project) do |start_commit|
M
Marc Siegfriedt 已提交
869
      index = rugged.index
870

871 872 873
      parents = if start_commit
                  index.read_tree(start_commit.raw_commit.tree)
                  [start_commit.sha]
874 875 876
                else
                  []
                end
M
Marc Siegfriedt 已提交
877

878 879
      actions.each do |act|
        git_action(index, act)
M
Marc Siegfriedt 已提交
880 881 882 883 884 885 886 887 888 889 890 891
      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
892
  # rubocop:enable Metrics/ParameterLists
M
Marc Siegfriedt 已提交
893

894 895
  def get_committer_and_author(user, email: nil, name: nil)
    committer = user_to_committer(user)
D
Dan Dunckel 已提交
896
    author = Gitlab::Git::committer_hash(email: email, name: name) || committer
897

898
    {
899 900
      author: author,
      committer: committer
901 902 903
    }
  end

904
  def user_to_committer(user)
905
    Gitlab::Git.committer_hash(email: user.email, name: user.name)
906 907
  end

908 909 910 911 912 913 914 915 916 917 918
  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

S
Sean McGivern 已提交
919
  def merge(user, source, merge_request, options = {})
920
    GitOperationService.new(user, self).with_branch(
921 922
      merge_request.target_branch) do |start_commit|
      our_commit = start_commit.sha
S
Sean McGivern 已提交
923
      their_commit = source
924

925 926
      raise 'Invalid merge target' unless our_commit
      raise 'Invalid merge source' unless their_commit
927

928 929
      merge_index = rugged.merge_commits(our_commit, their_commit)
      break if merge_index.conflicts?
930

931 932 933 934
      actual_options = options.merge(
        parents: [our_commit, their_commit],
        tree: merge_index.write_tree(rugged),
      )
935

936 937 938
      commit_id = Rugged::Commit.create(rugged, actual_options)
      merge_request.update(in_progress_merge_commit_sha: commit_id)
      commit_id
939
    end
940 941
  rescue Repository::CommitError # when merge_index.conflicts?
    false
942 943
  end

944
  def revert(
945
    user, commit, branch_name, revert_tree_id = nil,
946
    start_branch_name: nil, start_project: project)
947
    revert_tree_id ||= check_revert_content(commit, branch_name)
948

949
    return false unless revert_tree_id
950

951
    GitOperationService.new(user, self).with_branch(
952
      branch_name,
953 954
      start_branch_name: start_branch_name,
      start_project: start_project) do |start_commit|
955

956
      committer = user_to_committer(user)
957

L
Lin Jen-Shin 已提交
958
      Rugged::Commit.create(rugged,
959
        message: commit.revert_message(user),
960 961
        author: committer,
        committer: committer,
962
        tree: revert_tree_id,
963
        parents: [start_commit.sha])
964
    end
965 966
  end

967
  def cherry_pick(
968
    user, commit, branch_name, cherry_pick_tree_id = nil,
969
    start_branch_name: nil, start_project: project)
970
    cherry_pick_tree_id ||= check_cherry_pick_content(commit, branch_name)
P
P.S.V.R 已提交
971 972 973

    return false unless cherry_pick_tree_id

974
    GitOperationService.new(user, self).with_branch(
975
      branch_name,
976 977
      start_branch_name: start_branch_name,
      start_project: start_project) do |start_commit|
978

P
P.S.V.R 已提交
979
      committer = user_to_committer(user)
980

L
Lin Jen-Shin 已提交
981
      Rugged::Commit.create(rugged,
P
P.S.V.R 已提交
982 983 984 985 986 987 988 989
        message: commit.message,
        author: {
          email: commit.author_email,
          name: commit.author_name,
          time: commit.authored_date
        },
        committer: committer,
        tree: cherry_pick_tree_id,
990
        parents: [start_commit.sha])
P
P.S.V.R 已提交
991 992 993
    end
  end

994 995
  def resolve_conflicts(user, branch_name, params)
    GitOperationService.new(user, self).with_branch(branch_name) do
996 997 998 999 1000 1001
      committer = user_to_committer(user)

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

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

    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

1016 1017 1018 1019
  def check_cherry_pick_content(target_commit, branch_name)
    source_sha = commit(branch_name).sha
    args       = [target_commit.sha, source_sha]
    args << 1 if target_commit.merge_commit?
P
P.S.V.R 已提交
1020 1021 1022 1023 1024 1025 1026 1027 1028 1029

    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

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

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

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

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

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

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

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

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

V
Valery Sizov 已提交
1070 1071 1072 1073 1074 1075 1076
  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

1077
  def with_repo_branch_commit(start_repository, start_branch_name)
1078
    branch_name_or_sha =
1079 1080
      if start_repository == self
        start_branch_name
1081 1082
      else
        tmp_ref = "refs/tmp/#{SecureRandom.hex}/head"
1083

1084
        fetch_ref(
1085 1086
          start_repository.path_to_repo,
          "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}",
1087 1088 1089
          tmp_ref
        )

1090
        start_repository.commit(start_branch_name).sha
1091
      end
1092

1093
    yield(commit(branch_name_or_sha))
1094 1095

  ensure
1096
    rugged.references.delete(tmp_ref) if tmp_ref
1097 1098
  end

1099
  def fetch_ref(source_path, source_ref, target_ref)
1100
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
1101 1102 1103
    Gitlab::Popen.popen(args, path_to_repo)
  end

1104 1105 1106 1107
  def create_ref(ref, ref_path)
    fetch_ref(path_to_repo, ref, ref_path)
  end

1108 1109 1110 1111 1112
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

1113 1114 1115 1116
  def gitattribute(path, name)
    raw_repository.attributes(path)[name]
  end

1117 1118 1119 1120 1121 1122 1123 1124 1125 1126
  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

1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139
  # 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)
1140

1141 1142 1143 1144 1145 1146 1147 1148 1149
    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
1150 1151 1152 1153
      end
    end
  end

1154 1155 1156
  def cache_instance_variable_name(key)
    :"@#{key.to_s.tr('?!', '')}"
  end
1157

1158 1159 1160 1161
  def file_on_head(type)
    if head = tree(:head)
      head.blobs.find do |file|
        Gitlab::FileDetector.type_of(file.name) == type
1162 1163
      end
    end
1164
  end
1165

1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189
  protected

  def tree_entry_at(branch_name, path)
    branch_exists?(branch_name) &&
      # tree_entry is private
      raw_repository.send(:tree_entry, commit(branch_name), path)
  end

  def check_tree_entry_for_dir(branch_name, path)
    return unless branch_exists?(branch_name)

    entry = tree_entry_at(branch_name, path)

    return unless entry

    if entry[:type] == :blob
      raise Gitlab::Git::Repository::InvalidBlobName.new(
        "Directory already exists as a file")
    else
      raise Gitlab::Git::Repository::InvalidBlobName.new(
        "Directory already exists")
    end
  end

1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203
  def route_map_file(sha)
    blob = blob_at(sha, ROUTE_MAP_PATH)
    return unless blob
    blob.load_all_data!(self)
    blob.data
  end

  def ci_yaml_file(sha)
    blob = blob_at(sha, GITLAB_CI_YML_PATH)
    return unless blob
    blob.load_all_data!(self)
    blob.data
  end

1204 1205
  private

1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249
  def git_action(index, action)
    path = normalize_path(action[:file_path])

    if action[:action] == :move
      previous_path = normalize_path(action[:previous_path])
    end

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

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

      content = if action[:encoding] == 'base64'
                  Base64.decode64(action[:content])
                else
                  action[:content]
                end

      oid = rugged.write(content, :blob)

      index.add(path: path, oid: oid, mode: mode)
    when :delete
      index.remove(path)
    end
  end

  def normalize_path(path)
    pathname = Gitlab::Git::PathHelper.normalize_path(path)

    if pathname.each_filename.include?('..')
      raise Gitlab::Git::Repository::InvalidBlobName.new('Invalid path')
    end

    pathname.to_s
  end

1250 1251 1252 1253
  def refs_directory_exists?
    return false unless path_with_namespace

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

1256
  def cache
1257
    @cache ||= RepositoryCache.new(path_with_namespace, @project.id)
1258
  end
1259 1260

  def tags_sorted_by_committed_date
1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272
    tags.sort_by do |tag|
      # Annotated tags can point to any object (e.g. a blob), but generally
      # tags point to a commit. If we don't have a commit, then just default
      # to putting the tag at the end of the list.
      target = tag.dereferenced_target

      if target
        target.committed_date
      else
        Time.now
      end
    end
1273
  end
D
Douwe Maan 已提交
1274 1275 1276 1277

  def keep_around_ref_name(sha)
    "refs/keep-around/#{sha}"
  end
Y
Yorick Peterse 已提交
1278 1279 1280 1281

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