repository.rb 31.7 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
    branch_count > 0
89 90
  end

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

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

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

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

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

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

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

138 139
    ref ||= root_ref

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

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

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

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

171 172 173 174 175 176 177 178
  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
179
      update_ref!(ref, target, oldrev)
180
    end
181

182
    after_create_branch
183
    find_branch(branch_name)
184 185
  end

186 187 188 189 190 191
  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
192

193 194
    options = { message: message, tagger: user_to_committer(user) } if message

195 196 197
    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do |service|
      raw_tag = rugged.tags.create(tag_name, target, options)
      service.newrev = raw_tag.target_id
198
    end
199

200
    find_tag(tag_name)
201 202
  end

203
  def rm_branch(user, branch_name)
204
    before_remove_branch
205

206
    branch = find_branch(branch_name)
207
    oldrev = branch.try(:dereferenced_target).try(:id)
208 209 210 211
    newrev = Gitlab::Git::BLANK_SHA
    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name

    GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
212
      update_ref!(ref, newrev, oldrev)
213
    end
214

215
    after_remove_branch
216
    true
217 218
  end

219
  def rm_tag(tag_name)
Y
Yorick Peterse 已提交
220
    before_remove_tag
221

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

230 231 232 233
  def ref_names
    branch_names + tag_names
  end

234 235 236 237
  def branch_exists?(branch_name)
    branch_names.include?(branch_name)
  end

238 239
  def ref_exists?(ref)
    rugged.references.exist?(ref)
240 241
  rescue Rugged::ReferenceError
    false
242 243
  end

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

    return if status.zero?

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

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

    return if kept_around?(sha)

268 269 270 271 272
    # 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}"
273 274 275
    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}"
276
    end
277 278 279
  end

  def kept_around?(sha)
280
    ref_exists?(keep_around_ref_name(sha))
281 282
  end

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

      number_commits_ahead = raw_repository.
292
        count_commits_between(root_ref_hash, branch.dereferenced_target.sha)
293

294 295 296
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
297

298 299 300
  def expire_tags_cache
    expire_method_caches(%i(tag_names tag_count))
    @tags = nil
301
  end
302

303 304 305
  def expire_branches_cache
    expire_method_caches(%i(branch_names branch_count))
    @local_branches = nil
306 307
  end

308 309
  def expire_statistics_caches
    expire_method_caches(%i(size commit_count))
310 311
  end

312 313
  def expire_all_method_caches
    expire_method_caches(CACHED_METHODS)
D
Douwe Maan 已提交
314 315
  end

316 317 318 319 320 321 322 323 324
  # 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 已提交
325 326
  end

327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
  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
342
    end
343

344
    expire_method_caches(to_refresh)
345

346
    to_refresh.each { |method| send(method) }
347
  end
348

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

364
  def expire_root_ref_cache
365
    expire_method_caches(%i(root_ref))
366 367
  end

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

372
    expire_method_caches(%i(empty?))
373 374
  end

375 376 377 378
  def lookup_cache
    @lookup_cache ||= {}
  end

379
  def expire_exists_cache
380
    expire_method_caches(%i(exists?))
381 382
  end

383 384 385 386 387 388 389
  # 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
390
    expire_statistics_caches
391 392
  end

393 394 395
  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
396 397
    expire_root_ref_cache
    expire_emptiness_caches
Y
Yorick Peterse 已提交
398 399

    repository_event(:create_repository)
400 401
  end

402 403
  # Runs code just before a repository is deleted.
  def before_delete
404
    expire_exists_cache
405 406
    expire_all_method_caches
    expire_branch_cache if exists?
407
    expire_content_cache
Y
Yorick Peterse 已提交
408 409

    repository_event(:remove_repository)
410 411 412 413 414 415 416
  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 已提交
417 418

    repository_event(:change_default_branch)
419 420
  end

Y
Yorick Peterse 已提交
421 422
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
423 424
    expire_statistics_caches
    expire_emptiness_caches
425
    expire_tags_cache
Y
Yorick Peterse 已提交
426 427

    repository_event(:push_tag)
Y
Yorick Peterse 已提交
428 429 430 431 432
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
433
    expire_statistics_caches
Y
Yorick Peterse 已提交
434 435

    repository_event(:remove_tag)
436 437
  end

438
  def before_import
439
    expire_content_cache
440 441
  end

442 443 444 445 446
  # Runs code after the HEAD of a repository is changed.
  def after_change_head
    expire_method_caches(METHOD_CACHES_FOR_FILE_TYPES.keys)
  end

447 448
  # Runs code after a repository has been forked/imported.
  def after_import
449
    expire_content_cache
450 451
    expire_tags_cache
    expire_branches_cache
452 453 454
  end

  # Runs code after a new commit has been pushed.
455 456 457
  def after_push_commit(branch_name)
    expire_statistics_caches
    expire_branch_cache(branch_name)
Y
Yorick Peterse 已提交
458 459

    repository_event(:push_commit, branch: branch_name)
460 461 462 463
  end

  # Runs code after a new branch has been created.
  def after_create_branch
464
    expire_branches_cache
Y
Yorick Peterse 已提交
465 466

    repository_event(:push_branch)
467 468
  end

469 470 471
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
Y
Yorick Peterse 已提交
472 473

    repository_event(:remove_branch)
474 475
  end

476 477
  # Runs code after an existing branch has been removed.
  def after_remove_branch
478
    expire_branches_cache
479 480
  end

481
  def method_missing(m, *args, &block)
482 483 484 485 486 487
    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
488 489
  end

490 491
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
492
  end
D
Dmitriy Zaporozhets 已提交
493 494

  def blob_at(sha, path)
495
    unless Gitlab::Git.blank_ref?(sha)
496
      Blob.decorate(Gitlab::Git::Blob.find(self, sha, path))
497
    end
D
Dmitriy Zaporozhets 已提交
498
  end
499

500 501 502 503
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

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

562
  def readme
563 564 565
    if head = tree(:head)
      head.readme
    end
566
  end
567
  cache_method :readme
568

569
  def version
570
    file_on_head(:version)
571
  end
572
  cache_method :version
573

574
  def contribution_guide
575
    file_on_head(:contributing)
576
  end
577
  cache_method :contribution_guide
578 579

  def changelog
580
    file_on_head(:changelog)
581
  end
582
  cache_method :changelog
583

584
  def license_blob
585
    file_on_head(:license)
586
  end
587
  cache_method :license_blob
Z
Zeger-Jan van de Weg 已提交
588

589
  def license_key
590
    return unless exists?
591

592
    Licensee.license(path).try(:key)
593
  end
594
  cache_method :license_key
595

596
  def gitignore
597
    file_on_head(:gitignore)
598
  end
599
  cache_method :gitignore
600

601
  def koding_yml
602
    file_on_head(:koding)
603
  end
604
  cache_method :koding_yml
605

606
  def gitlab_ci_yml
607
    file_on_head(:gitlab_ci)
608
  end
609
  cache_method :gitlab_ci_yml
610

611
  def head_commit
612 613 614 615
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
616 617 618
    if head_commit
      @head_tree ||= Tree.new(self, head_commit.sha, nil)
    end
619 620
  end

621
  def tree(sha = :head, path = nil, recursive: false)
622
    if sha == :head
623 624
      return unless head_commit

625 626 627 628 629
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
630 631
    end

632
    Tree.new(self, sha, path, recursive: recursive)
633
  end
D
Dmitriy Zaporozhets 已提交
634 635

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

D
Dmitriy Zaporozhets 已提交
638 639 640 641 642
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
643
  end
D
Dmitriy Zaporozhets 已提交
644 645 646 647 648 649 650 651

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
652
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
653 654 655 656 657 658 659
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
660 661

  def last_commit_for_path(sha, path)
H
Hiroyuki Sato 已提交
662
    sha = last_commit_id_for_path(sha, path)
663
    commit(sha)
664
  end
665

H
Hiroyuki Sato 已提交
666 667
  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 已提交
668

H
Hiroyuki Sato 已提交
669
    cache.fetch(key) do
H
Hiroyuki Sato 已提交
670 671
      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 已提交
672 673 674
    end
  end

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

P
P.S.V.R 已提交
682
    highest_branch_id = branch_ids.max || 0
683

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

    "#{name}-#{highest_branch_id + 1}"
687 688
  end

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

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

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

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

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

732
        contributor.commits += 1
733 734
      end

735 736
      contributor
    end
737
  end
D
Dmitriy Zaporozhets 已提交
738

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

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

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

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

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

772
  def local_branches
773
    @local_branches ||= raw_repository.local_branches
774 775
  end

776 777
  alias_method :branches, :local_branches

778 779 780 781
  def tags
    @tags ||= raw_repository.tags
  end

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

792 793
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))

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

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

813
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
814

815 816
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
817 818
  end

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

834
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
835

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

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

858
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
859

860 861
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
862 863
  end

M
Marc Siegfriedt 已提交
864 865 866 867 868 869 870
  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
871
        last_commit = branch.dereferenced_target
M
Marc Siegfriedt 已提交
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 908 909
        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

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

914
    {
915 916
      author: author,
      committer: committer
917 918 919
    }
  end

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

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

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

    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?

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

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

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

961
    return false unless revert_tree_id
962

963
    update_branch_with_hooks(user, base_branch) do
964
      committer = user_to_committer(user)
965
      source_sha = Rugged::Commit.create(rugged,
966
        message: commit.revert_message(user),
967 968
        author: committer,
        committer: committer,
969
        tree: revert_tree_id,
970
        parents: [rugged.lookup(source_sha)])
971
    end
972 973
  end

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

    return false unless cherry_pick_tree_id

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

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

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

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

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

    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

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

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

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

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

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

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

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

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

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

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

1083 1084 1085 1086
  def create_ref(ref, ref_path)
    fetch_ref(path_to_repo, ref, ref_path)
  end

1087
  def update_branch_with_hooks(current_user, branch)
1088 1089
    update_autocrlf_option

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

1094 1095
    # Make commit
    newrev = yield(ref)
1096

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

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

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

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

    newrev
1118 1119
  end

1120 1121 1122 1123 1124
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

1125 1126 1127 1128
  def gitattribute(path, name)
    raw_repository.attributes(path)[name]
  end

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

1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164
  # 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
1165

1166 1167 1168 1169 1170 1171 1172 1173
  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
1174 1175 1176 1177
      end
    end
  end

1178 1179
  private

1180 1181 1182 1183 1184 1185
  def refs_directory_exists?
    return false unless path_with_namespace

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

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

1190
  def tags_sorted_by_committed_date
1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202
    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
1203
  end
D
Douwe Maan 已提交
1204 1205 1206 1207

  def keep_around_ref_name(sha)
    "refs/keep-around/#{sha}"
  end
Y
Yorick Peterse 已提交
1208 1209 1210 1211

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