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

3
class Repository
L
Lin Jen-Shin 已提交
4 5 6
  REF_MERGE_REQUEST = 'merge-requests'.freeze
  REF_KEEP_AROUND = 'keep-around'.freeze
  REF_ENVIRONMENTS = 'environments'.freeze
7 8 9 10

  RESERVED_REFS_NAMES = %W[
    heads
    tags
11
    replace
12 13 14 15 16
    #{REF_ENVIRONMENTS}
    #{REF_KEEP_AROUND}
    #{REF_ENVIRONMENTS}
  ].freeze

17
  include Gitlab::ShellAdapter
18
  include RepositoryMirroring
19

20
  attr_accessor :full_path, :disk_path, :project
21

22 23
  delegate :ref_name_for_sha, to: :raw_repository

24
  CreateTreeError = Class.new(StandardError)
25

26 27 28 29 30 31
  # 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.
  #
32 33 34
  # For example, for entry `:commit_count` there's a method called `commit_count` which
  # stores its data in the `commit_count` cache key.
  CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide
35 36
                      changelog license_blob license_key gitignore koding_yml
                      gitlab_ci_yml branch_names tag_names branch_count
S
Sean McGivern 已提交
37 38
                      tag_count avatar exists? empty? root_ref has_visible_content?
                      issue_template_names merge_request_template_names).freeze
39 40 41

  # Methods that use cache_method but only memoize the value
  MEMOIZED_CACHED_METHODS = %i(license empty_repo?).freeze
42 43 44 45 46

  # 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 = {
47
    readme: :rendered_readme,
48
    changelog: :changelog,
49
    license: %i(license_blob license_key license),
50 51 52 53
    contributing: :contribution_guide,
    gitignore: :gitignore,
    koding: :koding_yml,
    gitlab_ci: :gitlab_ci_yml,
S
Sean McGivern 已提交
54 55 56
    avatar: :avatar,
    issue_template: :issue_template_names,
    merge_request_template: :merge_request_template_names
D
Douwe Maan 已提交
57
  }.freeze
58 59 60 61 62

  # 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.
63
  def self.cache_method(name, fallback: nil, memoize_only: false)
64
    original = :"_uncached_#{name}"
65

66
    alias_method(original, name)
67

68
    define_method(name) do
69 70 71
      cache_method_output(name, fallback: fallback, memoize_only: memoize_only) do
        __send__(original) # rubocop:disable GitlabSecurity/PublicSend
      end
72
    end
73
  end
74

75
  def initialize(full_path, project, disk_path: nil)
76
    @full_path = full_path
77
    @disk_path = disk_path || full_path
78
    @project = project
79
  end
80

81
  def ==(other)
H
http://jneen.net/ 已提交
82 83 84
    @disk_path == other.disk_path
  end

85
  def raw_repository
86
    return nil unless full_path
87

88
    @raw_repository ||= initialize_raw_repository
89 90
  end

91 92
  alias_method :raw, :raw_repository

93
  # Return absolute path to repository
94
  def path_to_repo
95
    @path_to_repo ||= File.expand_path(
96
      File.join(repository_storage_path, disk_path + '.git')
97
    )
98 99
  end

100 101 102 103
  def inspect
    "#<#{self.class.name}:#{@disk_path}>"
  end

L
Lin Jen-Shin 已提交
104
  def commit(ref = 'HEAD')
105
    return nil unless exists?
106

107 108 109 110 111 112
    commit =
      if ref.is_a?(Gitlab::Git::Commit)
        ref
      else
        Gitlab::Git::Commit.find(raw_repository, ref)
      end
113

114
    commit = ::Commit.new(commit, @project) if commit
115
    commit
116
  rescue Rugged::OdbError, Rugged::TreeError
117
    nil
118 119
  end

120
  def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil)
121
    options = {
122 123 124 125 126
      repo: raw_repository,
      ref: ref,
      path: path,
      limit: limit,
      offset: offset,
127 128
      after: after,
      before: before,
129
      follow: Array(path).length == 1,
130
      skip_merges: skip_merges
131 132 133
    }

    commits = Gitlab::Git::Commit.where(options)
134
    commits = Commit.decorate(commits, @project) if commits.present?
135 136 137
    commits
  end

138 139
  def commits_between(from, to)
    commits = Gitlab::Git::Commit.between(raw_repository, from, to)
140
    commits = Commit.decorate(commits, @project) if commits.present?
141 142 143
    commits
  end

J
Jacob Vosmaer 已提交
144
  # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384
145
  def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
146 147 148 149
    unless exists? && has_visible_content? && query.present?
      return []
    end

150 151 152 153 154 155 156
    raw_repository.gitaly_migrate(:commits_by_message) do |is_enabled|
      if is_enabled
        find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
      else
        find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
      end
    end
157 158
  end

159 160 161 162 163 164 165
  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
166
    raw_repo = fresh_repo ? initialize_raw_repository : raw_repository
167 168

    raw_repo.find_branch(name)
169 170 171
  end

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

175
  def add_branch(user, branch_name, ref)
176
    branch = raw_repository.add_branch(branch_name, user: user, target: ref)
177

178
    after_create_branch
179 180 181 182

    branch
  rescue Gitlab::Git::Repository::InvalidRef
    false
183 184
  end

185
  def add_tag(user, tag_name, target, message = nil)
186
    raw_repository.add_tag(tag_name, user: user, target: target, message: message)
187 188
  rescue Gitlab::Git::Repository::InvalidRef
    false
189 190
  end

191
  def rm_branch(user, branch_name)
192
    before_remove_branch
193

194
    raw_repository.rm_branch(branch_name, user: user)
195

196
    after_remove_branch
197
    true
198 199
  end

L
Lin Jen-Shin 已提交
200
  def rm_tag(user, tag_name)
Y
Yorick Peterse 已提交
201
    before_remove_tag
202

203
    raw_repository.rm_tag(tag_name, user: user)
L
Lin Jen-Shin 已提交
204 205 206

    after_remove_tag
    true
207 208
  end

209 210 211 212
  def ref_names
    branch_names + tag_names
  end

213
  def branch_exists?(branch_name)
214 215 216 217 218 219 220
    return false unless raw_repository

    @branch_exists_memo ||= Hash.new do |hash, key|
      hash[key] = raw_repository.branch_exists?(key)
    end

    @branch_exists_memo[branch_name]
221 222
  end

223
  def ref_exists?(ref)
224 225
    !!raw_repository&.ref_exists?(ref)
  rescue ArgumentError
226
    false
227 228
  end

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

    return if kept_around?(sha)

238 239
    # This will still fail if the file is corrupted (e.g. 0 bytes)
    begin
240
      write_ref(keep_around_ref_name(sha), sha)
241
    rescue Rugged::ReferenceError => ex
242
      Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
243 244
    rescue Rugged::OSError => ex
      raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
245
      Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
246
    end
247 248 249
  end

  def kept_around?(sha)
250
    ref_exists?(keep_around_ref_name(sha))
251
  end
252

253 254 255 256
  def write_ref(ref_path, sha)
    rugged.references.create(ref_path, sha, force: true)
  end

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

265 266
      number_commits_ahead = raw_repository
        .count_commits_between(root_ref_hash, branch.dereferenced_target.sha)
267

268 269 270
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
271

272 273 274
  def expire_tags_cache
    expire_method_caches(%i(tag_names tag_count))
    @tags = nil
275
  end
276

277
  def expire_branches_cache
278
    expire_method_caches(%i(branch_names branch_count has_visible_content?))
279
    @local_branches = nil
280
    @branch_exists_memo = nil
281 282
  end

283 284
  def expire_statistics_caches
    expire_method_caches(%i(size commit_count))
285 286
  end

287 288
  def expire_all_method_caches
    expire_method_caches(CACHED_METHODS)
D
Douwe Maan 已提交
289 290
  end

291 292 293 294 295 296 297 298 299
  # 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 已提交
300 301
  end

302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
  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
317
    end
318

319
    expire_method_caches(to_refresh)
320

321
    to_refresh.each { |method| send(method) } # rubocop:disable GitlabSecurity/PublicSend
322
  end
323

324 325 326 327 328 329 330
  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}")
331
        cache.expire(:"commit_count_#{branch.name}")
332 333 334 335 336
      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}")
337
      cache.expire(:"commit_count_#{branch_name}")
338
    end
D
Dmitriy Zaporozhets 已提交
339 340
  end

341
  def expire_root_ref_cache
342
    expire_method_caches(%i(root_ref))
343 344
  end

345 346
  # Expires the cache(s) used to determine if a repository is empty or not.
  def expire_emptiness_caches
347
    return unless empty?
348

349
    expire_method_caches(%i(empty? has_visible_content?))
Y
Yorick Peterse 已提交
350 351
  end

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

356
  def expire_exists_cache
357
    expire_method_caches(%i(exists?))
358 359
  end

360 361 362 363 364 365 366
  # 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
367
    expire_statistics_caches
368 369 370 371 372
  end

  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
373 374
    expire_root_ref_cache
    expire_emptiness_caches
Y
Yorick Peterse 已提交
375 376

    repository_event(:create_repository)
377 378
  end

379 380
  # Runs code just before a repository is deleted.
  def before_delete
381
    expire_exists_cache
382 383
    expire_all_method_caches
    expire_branch_cache if exists?
384
    expire_content_cache
Y
Yorick Peterse 已提交
385 386

    repository_event(:remove_repository)
387 388 389 390 391 392 393
  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 已提交
394 395

    repository_event(:change_default_branch)
396 397
  end

Y
Yorick Peterse 已提交
398 399
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
400 401
    expire_statistics_caches
    expire_emptiness_caches
402
    expire_tags_cache
Y
Yorick Peterse 已提交
403 404

    repository_event(:push_tag)
Y
Yorick Peterse 已提交
405 406 407 408 409
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
410
    expire_statistics_caches
Y
Yorick Peterse 已提交
411 412

    repository_event(:remove_tag)
413 414
  end

L
Lin Jen-Shin 已提交
415 416 417 418 419
  # Runs code after removing a tag.
  def after_remove_tag
    expire_tags_cache
  end

420 421 422
  # Runs code after the HEAD of a repository is changed.
  def after_change_head
    expire_method_caches(METHOD_CACHES_FOR_FILE_TYPES.keys)
423 424
  end

425 426
  # Runs code after a repository has been forked/imported.
  def after_import
427
    expire_content_cache
428 429 430
  end

  # Runs code after a new commit has been pushed.
431 432 433
  def after_push_commit(branch_name)
    expire_statistics_caches
    expire_branch_cache(branch_name)
Y
Yorick Peterse 已提交
434 435

    repository_event(:push_commit, branch: branch_name)
436 437 438 439
  end

  # Runs code after a new branch has been created.
  def after_create_branch
440
    expire_branches_cache
Y
Yorick Peterse 已提交
441 442

    repository_event(:push_branch)
443 444
  end

445 446 447
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
Y
Yorick Peterse 已提交
448 449

    repository_event(:remove_branch)
450 451
  end

452 453
  # Runs code after an existing branch has been removed.
  def after_remove_branch
454
    expire_branches_cache
455 456
  end

457
  def method_missing(m, *args, &block)
458 459
    if m == :lookup && !block_given?
      lookup_cache[m] ||= {}
460
      lookup_cache[m][args.join(":")] ||= raw_repository.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
461
    else
462
      raw_repository.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
463
    end
464 465
  end

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

  def blob_at(sha, path)
471
    unless Gitlab::Git.blank_ref?(sha)
D
Douwe Maan 已提交
472
      Blob.decorate(Gitlab::Git::Blob.find(self, sha, path), project)
473
    end
D
Douwe Maan 已提交
474 475
  rescue Gitlab::Git::Repository::NoRepository
    nil
D
Dmitriy Zaporozhets 已提交
476
  end
477

478 479 480 481 482 483 484
  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
485
  end
486
  cache_method :root_ref
487

488
  # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/314
489
  def exists?
490
    return false unless full_path
491

492
    raw_repository.exists?
493 494 495
  end
  cache_method :exists?

D
Douwe Maan 已提交
496
  delegate :empty?, to: :raw_repository
497 498 499 500 501 502 503 504 505 506 507 508 509
  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

510
  def commit_count_for_ref(ref)
511
    return 0 unless exists?
512

513 514 515 516
    begin
      cache.fetch(:"commit_count_#{ref}") { raw_repository.commit_count(ref) }
    rescue Rugged::ReferenceError
      0
517 518 519
    end
  end

520
  delegate :branch_names, to: :raw_repository
521 522
  cache_method :branch_names, fallback: []

D
Douwe Maan 已提交
523
  delegate :tag_names, to: :raw_repository
524 525
  cache_method :tag_names, fallback: []

526
  delegate :branch_count, :tag_count, :has_visible_content?, to: :raw_repository
527 528
  cache_method :branch_count, fallback: 0
  cache_method :tag_count, fallback: 0
529
  cache_method :has_visible_content?, fallback: false
530 531

  def avatar
532 533 534 535 536
    # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38327
    Gitlab::GitalyClient.allow_n_plus_1_calls do
      if tree = file_on_head(:avatar)
        tree.path
      end
537 538
    end
  end
539
  cache_method :avatar
540

S
Sean McGivern 已提交
541 542 543 544 545 546 547 548 549 550
  def issue_template_names
    Gitlab::Template::IssueTemplate.dropdown_names(project)
  end
  cache_method :issue_template_names, fallback: []

  def merge_request_template_names
    Gitlab::Template::MergeRequestTemplate.dropdown_names(project)
  end
  cache_method :merge_request_template_names, fallback: []

551
  def readme
552 553
    if readme = tree(:head)&.readme
      ReadmeBlob.new(readme, self)
554
    end
555 556
  end

557
  def rendered_readme
T
Toon Claes 已提交
558
    MarkupHelper.markup_unsafe(readme.name, readme.data, project: project) if readme
559 560
  end
  cache_method :rendered_readme
561

562
  def contribution_guide
563
    file_on_head(:contributing)
564
  end
565
  cache_method :contribution_guide
566 567

  def changelog
568
    file_on_head(:changelog)
569
  end
570
  cache_method :changelog
571

572
  def license_blob
573
    file_on_head(:license)
574
  end
575
  cache_method :license_blob
Z
Zeger-Jan van de Weg 已提交
576

577
  def license_key
578
    return unless exists?
579

580
    Licensee.license(path).try(:key)
581
  end
582
  cache_method :license_key
583

D
Douwe Maan 已提交
584 585
  def license
    return unless license_key
586

587
    Licensee::License.new(license_key)
588
  end
589
  cache_method :license, memoize_only: true
590 591

  def gitignore
592
    file_on_head(:gitignore)
593
  end
594
  cache_method :gitignore
595 596

  def koding_yml
597
    file_on_head(:koding)
598
  end
599
  cache_method :koding_yml
600

601
  def gitlab_ci_yml
602
    file_on_head(:gitlab_ci)
603
  end
604
  cache_method :gitlab_ci_yml
605

606
  def head_commit
607 608 609 610
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
611 612 613
    if head_commit
      @head_tree ||= Tree.new(self, head_commit.sha, nil)
    end
614 615
  end

616
  def tree(sha = :head, path = nil, recursive: false)
617
    if sha == :head
618 619
      return unless head_commit

620 621 622 623 624
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
625 626
    end

627
    Tree.new(self, sha, path, recursive: recursive)
628
  end
D
Dmitriy Zaporozhets 已提交
629 630

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

D
Dmitriy Zaporozhets 已提交
633 634 635 636 637
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
638
  end
D
Dmitriy Zaporozhets 已提交
639

640
  def last_commit_for_path(sha, path)
641 642 643 644 645 646 647
    raw_repository.gitaly_migrate(:last_commit_for_path) do |is_enabled|
      if is_enabled
        last_commit_for_path_by_gitaly(sha, path)
      else
        last_commit_for_path_by_rugged(sha, path)
      end
    end
648
  end
649

H
Hiroyuki Sato 已提交
650 651
  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 已提交
652

H
Hiroyuki Sato 已提交
653
    cache.fetch(key) do
654 655 656 657 658 659 660
      raw_repository.gitaly_migrate(:last_commit_for_path) do |is_enabled|
        if is_enabled
          last_commit_for_path_by_gitaly(sha, path).id
        else
          last_commit_id_for_path_by_shelling_out(sha, path)
        end
      end
H
Hiroyuki Sato 已提交
661 662 663
    end
  end

664
  def next_branch(name, opts = {})
P
P.S.V.R 已提交
665 666 667
    branch_ids = self.branch_names.map do |n|
      next 1 if n == name
      result = n.match(/\A#{name}-([0-9]+)\z/)
668 669 670
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
671
    highest_branch_id = branch_ids.max || 0
672

P
P.S.V.R 已提交
673 674 675
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
676 677
  end

678
  def branches_sorted_by(value)
679
    raw_repository.local_branches(sort_by: value)
680
  end
681

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

695
  def contributors
696
    commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
697

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

D
Dmitriy Zaporozhets 已提交
702
      commits.each do |commit|
703
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
704
          contributor.name = commit.author_name
705 706
        end

707
        contributor.commits += 1
708 709
      end

710 711
      contributor
    end
712
  end
D
Dmitriy Zaporozhets 已提交
713

714
  def refs_contains_sha(ref_type, sha)
715 716
    args = %W(#{ref_type} --contains #{sha})
    names = run_git(args).first
717 718 719 720 721 722 723 724 725 726 727 728 729

    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 已提交
730

731 732 733
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
734

735 736
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
737
  end
738

739
  def local_branches
740
    @local_branches ||= raw_repository.local_branches
741 742
  end

743 744
  alias_method :branches, :local_branches

745 746 747 748
  def tags
    @tags ||= raw_repository.tags
  end

D
Douwe Maan 已提交
749 750 751
  def create_dir(user, path, **options)
    options[:user] = user
    options[:actions] = [{ action: :create_dir, file_path: path }]
752

D
Douwe Maan 已提交
753
    multi_action(**options)
S
Stan Hu 已提交
754 755
  end

D
Douwe Maan 已提交
756 757 758
  def create_file(user, path, content, **options)
    options[:user] = user
    options[:actions] = [{ action: :create, file_path: path, content: content }]
759

D
Douwe Maan 已提交
760
    multi_action(**options)
S
Stan Hu 已提交
761
  end
762

D
Douwe Maan 已提交
763 764 765
  def update_file(user, path, content, **options)
    previous_path = options.delete(:previous_path)
    action = previous_path && previous_path != path ? :move : :update
766

D
Douwe Maan 已提交
767 768
    options[:user] = user
    options[:actions] = [{ action: action, file_path: path, previous_path: previous_path, content: content }]
769

D
Douwe Maan 已提交
770
    multi_action(**options)
771 772
  end

D
Douwe Maan 已提交
773 774 775
  def delete_file(user, path, **options)
    options[:user] = user
    options[:actions] = [{ action: :delete, file_path: path }]
776

D
Douwe Maan 已提交
777
    multi_action(**options)
778 779
  end

780 781
  def with_cache_hooks
    result = yield
782

783
    return unless result
784

785 786
    after_create if result.repo_created?
    after_create_branch if result.branch_created?
787

788 789 790 791 792 793 794 795 796
    result.newrev
  end

  def with_branch(user, *args)
    with_cache_hooks do
      Gitlab::Git::OperationService.new(user, raw_repository).with_branch(*args) do |start_commit|
        yield start_commit
      end
    end
797 798
  end

799
  # rubocop:disable Metrics/ParameterLists
L
Lin Jen-Shin 已提交
800
  def multi_action(
801
    user:, branch_name:, message:, actions:,
802
    author_email: nil, author_name: nil,
803
    start_branch_name: nil, start_project: project)
804

805 806
    with_branch(
      user,
807
      branch_name,
808
      start_branch_name: start_branch_name,
809
      start_repository: start_project.repository.raw_repository) do |start_commit|
810

811
      index = Gitlab::Git::Index.new(raw_repository)
812

813
      if start_commit
814
        index.read_tree(start_commit.rugged_commit.tree)
815 816 817
        parents = [start_commit.sha]
      else
        parents = []
M
Marc Siegfriedt 已提交
818 819
      end

820
      actions.each do |options|
821
        index.public_send(options.delete(:action), options) # rubocop:disable GitlabSecurity/PublicSend
M
Marc Siegfriedt 已提交
822 823 824
      end

      options = {
825
        tree: index.write_tree,
M
Marc Siegfriedt 已提交
826 827 828 829 830
        message: message,
        parents: parents
      }
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))

831
      create_commit(options)
M
Marc Siegfriedt 已提交
832 833
    end
  end
834
  # rubocop:enable Metrics/ParameterLists
M
Marc Siegfriedt 已提交
835

836 837
  def get_committer_and_author(user, email: nil, name: nil)
    committer = user_to_committer(user)
D
Douwe Maan 已提交
838
    author = Gitlab::Git.committer_hash(email: email, name: name) || committer
839

840
    {
841 842
      author: author,
      committer: committer
843 844 845 846 847 848 849 850 851 852 853 854 855 856
    }
  end

  def can_be_merged?(source_sha, target_branch)
    our_commit = rugged.branches[target_branch].target
    their_commit = rugged.lookup(source_sha)

    if our_commit && their_commit
      !rugged.merge_commits(our_commit, their_commit).conflicts?
    else
      false
    end
  end

857 858 859 860 861 862
  def merge(user, source_sha, merge_request, message)
    with_cache_hooks do
      raw_repository.merge(user, source_sha, merge_request.target_branch, message) do |commit_id|
        merge_request.update(in_progress_merge_commit_sha: commit_id)
        nil # Return value does not matter.
      end
863
    end
864 865
  end

866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884
  def ff_merge(user, source, target_branch, merge_request: nil)
    our_commit = rugged.branches[target_branch].target
    their_commit =
      if source.is_a?(Gitlab::Git::Commit)
        source.raw_commit
      else
        rugged.lookup(source)
      end

    raise 'Invalid merge target' if our_commit.nil?
    raise 'Invalid merge source' if their_commit.nil?

    with_branch(user, target_branch) do |start_commit|
      merge_request&.update(in_progress_merge_commit_sha: their_commit.oid)

      their_commit.oid
    end
  end

885
  def revert(
886
    user, commit, branch_name, message,
887
    start_branch_name: nil, start_project: project)
888

889 890 891 892 893 894 895 896 897
    with_cache_hooks do
      raw_repository.revert(
        user: user,
        commit: commit.raw,
        branch_name: branch_name,
        message: message,
        start_branch_name: start_branch_name,
        start_repository: start_project.repository.raw_repository
      )
898
    end
899 900
  end

901
  def cherry_pick(
902
    user, commit, branch_name, message,
903
    start_branch_name: nil, start_project: project)
P
P.S.V.R 已提交
904

905 906 907 908 909 910 911 912 913
    with_cache_hooks do
      raw_repository.cherry_pick(
        user: user,
        commit: commit.raw,
        branch_name: branch_name,
        message: message,
        start_branch_name: start_branch_name,
        start_repository: start_project.repository.raw_repository
      )
P
P.S.V.R 已提交
914 915 916
    end
  end

917
  def resolve_conflicts(user, branch_name, params)
918
    with_branch(user, branch_name) do
919 920
      committer = user_to_committer(user)

921
      create_commit(params.merge(author: committer, committer: committer))
922 923 924
    end
  end

F
Florent (HP) 已提交
925 926 927 928 929
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
930
      same_head = branch_commit.id == root_ref_commit.id
931
      !same_head && ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
932 933 934 935 936
    else
      nil
    end
  end

S
Stan Hu 已提交
937
  def merge_base(first_commit_id, second_commit_id)
938 939
    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 已提交
940
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
941 942
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
943 944
  end

945
  def ancestor?(ancestor_id, descendant_id)
946
    return false if ancestor_id.nil? || descendant_id.nil?
947

948 949
    Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
      if is_enabled
950
        raw_repository.ancestor?(ancestor_id, descendant_id)
951
      else
J
Jacob Vosmaer 已提交
952
        rugged_is_ancestor?(ancestor_id, descendant_id)
953 954
      end
    end
955 956
  end

V
Valery Sizov 已提交
957 958 959
  def empty_repo?
    !exists? || !has_visible_content?
  end
960
  cache_method :empty_repo?, memoize_only: true
V
Valery Sizov 已提交
961 962 963

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

965
    offset = 2
966 967 968
    args = %W(grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})

    run_git(args).first.scrub.split(/^--$/)
969 970
  end

V
Valery Sizov 已提交
971 972
  def search_files_by_name(query, ref)
    return [] if empty_repo? || query.blank?
973

974 975 976
    args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)})

    run_git(args).first.lines.map(&:strip)
977 978
  end

979 980 981 982 983 984 985 986 987 988 989 990 991 992
  def add_remote(name, url)
    raw_repository.remote_add(name, url)
  rescue Rugged::ConfigError
    raw_repository.remote_update(name, url: url)
  end

  def remove_remote(name)
    raw_repository.remote_delete(name)
    true
  rescue Rugged::ConfigError
    false
  end

  def fetch_remote(remote, forced: false, no_tags: false)
993
    gitlab_shell.fetch_remote(raw_repository, remote, forced: forced, no_tags: no_tags)
994 995
  end

996 997 998
  def fetch_source_branch(source_repository, source_branch, local_ref)
    raw_repository.fetch_source_branch(source_repository.raw_repository, source_branch, local_ref)
  end
999

1000 1001
  def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:)
    raw_repository.compare_source_branch(target_branch_name, source_repository.raw_repository, source_branch_name, straight: straight)
1002
  end
1003

1004
  def create_ref(ref, ref_path)
1005
    raw_repository.write_ref(ref_path, ref)
1006 1007
  end

1008 1009 1010 1011 1012
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

1013 1014 1015 1016
  def gitattribute(path, name)
    raw_repository.attributes(path)[name]
  end

1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
  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

1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
  # 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.
1038
  def cache_method_output(key, fallback: nil, memoize_only: false, &block)
1039
    ivar = cache_instance_variable_name(key)
1040

1041 1042 1043 1044
    if instance_variable_defined?(ivar)
      instance_variable_get(ivar)
    else
      begin
1045 1046 1047 1048 1049 1050 1051
        value =
          if memoize_only
            yield
          else
            cache.fetch(key, &block)
          end
        instance_variable_set(ivar, value)
1052 1053 1054 1055
      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
1056 1057 1058
      end
    end
  end
1059

1060 1061 1062
  def cache_instance_variable_name(key)
    :"@#{key.to_s.tr('?!', '')}"
  end
1063

1064 1065
  def file_on_head(type)
    if head = tree(:head)
D
Douwe Maan 已提交
1066 1067
      head.blobs.find do |blob|
        Gitlab::FileDetector.type_of(blob.path) == type
1068 1069 1070 1071
      end
    end
  end

D
Douwe Maan 已提交
1072 1073 1074 1075
  def route_map_for(sha)
    blob_data_at(sha, '.gitlab/route-map.yml')
  end

1076 1077
  def gitlab_ci_yml_for(sha, path = '.gitlab-ci.yml')
    blob_data_at(sha, path)
D
Douwe Maan 已提交
1078 1079
  end

1080 1081
  private

D
Douwe Maan 已提交
1082 1083
  def blob_data_at(sha, path)
    blob = blob_at(sha, path)
1084
    return unless blob
1085

1086
    blob.load_all_data!
1087
    blob.data
1088
  end
1089

1090
  def cache
1091 1092
    # TODO: should we use UUIDs here? We could move repositories without clearing this cache
    @cache ||= RepositoryCache.new(full_path, @project.id)
1093
  end
1094 1095

  def tags_sorted_by_committed_date
1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107
    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
1108
  end
D
Douwe Maan 已提交
1109 1110

  def keep_around_ref_name(sha)
1111
    "refs/#{REF_KEEP_AROUND}/#{sha}"
D
Douwe Maan 已提交
1112
  end
Y
Yorick Peterse 已提交
1113 1114

  def repository_event(event, tags = {})
1115
    Gitlab::Metrics.add_event(event, { path: full_path }.merge(tags))
Y
Yorick Peterse 已提交
1116
  end
1117

1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128
  def last_commit_for_path_by_gitaly(sha, path)
    c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path)
    commit(c)
  end

  def last_commit_for_path_by_rugged(sha, path)
    sha = last_commit_id_for_path_by_shelling_out(sha, path)
    commit(sha)
  end

  def last_commit_id_for_path_by_shelling_out(sha, path)
1129
    args = %W(rev-list --max-count=1 #{sha} -- #{path})
A
Andrew Newdigate 已提交
1130
    raw_repository.run_git_with_timeout(args, Gitlab::Git::Popen::FAST_GIT_PROCESS_TIMEOUT).first.strip
1131 1132
  end

1133 1134 1135
  def repository_storage_path
    @project.repository_storage_path
  end
1136

1137
  def initialize_raw_repository
1138
    Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, false))
1139
  end
1140

1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160
  def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
    ref ||= root_ref

    args = %W(
      log #{ref} --pretty=%H --skip #{offset}
      --max-count #{limit} --grep=#{query} --regexp-ignore-case
    )
    args = args.concat(%W(-- #{path})) if path.present?

    git_log_results = run_git(args).first.lines

    git_log_results.map { |c| commit(c.chomp) }.compact
  end

  def find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
    raw_repository
      .gitaly_commit_client
      .commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset)
      .map { |c| commit(c) }
  end
1161
end