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

3
class Repository
4 5
  class CommitError < StandardError; end

6 7 8 9
  # Files to use as a project avatar in case no avatar was uploaded via the web
  # UI.
  AVATAR_FILES = %w{logo.png logo.jpg logo.gif}

10 11
  include Gitlab::ShellAdapter

12
  attr_accessor :path_with_namespace, :project
13

14 15 16 17 18 19 20 21 22 23 24 25 26 27
  def self.storages
    Gitlab.config.repositories.storages
  end

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

    repo_path
  end

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

33 34
  def raw_repository
    return nil unless path_with_namespace
35

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

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

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

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

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

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

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

68 69 70 71 72 73 74 75 76 77
  #
  # Git repository can contains some hidden refs like:
  #   /refs/notes/*
  #   /refs/git-as-svn/*
  #   /refs/pulls/*
  # This refs by default not visible in project page and not cloned to client side.
  #
  # This method return true if repository contains some content visible in project page.
  #
  def has_visible_content?
78 79 80
    return @has_visible_content unless @has_visible_content.nil?

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

L
Lin Jen-Shin 已提交
85
  def commit(ref = 'HEAD')
86
    return nil unless exists?
87

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

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

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

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

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

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

132 133
    ref ||= root_ref

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

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

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

    raw_repo.find_branch(name)
159 160 161
  end

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

165
  def add_branch(user, branch_name, target)
166 167 168 169 170 171
    oldrev = Gitlab::Git::BLANK_SHA
    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
    target = commit(target).try(:id)

    return false unless target

172
    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
173
      update_ref!(ref, target, oldrev)
174
    end
175

176
    after_create_branch
177
    find_branch(branch_name)
178 179
  end

180 181 182 183 184 185
  def add_tag(user, tag_name, target, message = nil)
    oldrev = Gitlab::Git::BLANK_SHA
    ref    = Gitlab::Git::TAG_REF_PREFIX + tag_name
    target = commit(target).try(:id)

    return false unless target
186

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

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

193
    find_tag(tag_name)
194 195
  end

196
  def rm_branch(user, branch_name)
197
    before_remove_branch
198

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

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

208
    after_remove_branch
209
    true
210 211
  end

212
  def rm_tag(tag_name)
Y
Yorick Peterse 已提交
213
    before_remove_tag
214

R
Robert Schilling 已提交
215 216 217 218 219 220
    begin
      rugged.tags.delete(tag_name)
      true
    rescue Rugged::ReferenceError
      false
    end
221 222
  end

223 224 225 226
  def ref_names
    branch_names + tag_names
  end

227
  def branch_names
P
Paco Guzman 已提交
228
    @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) }
229 230
  end

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

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

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

    return if status.zero?

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

D
Douwe Maan 已提交
256 257 258 259
  # Makes sure a commit is kept around when Git garbage collection runs.
  # Git GC will delete commits from the repository that are no longer in any
  # branches or tags, but we want to keep some of these commits around, for
  # example if they have comments or CI builds.
260 261 262 263 264
  def keep_around(sha)
    return unless sha && commit(sha)

    return if kept_around?(sha)

265 266 267 268 269
    # This will still fail if the file is corrupted (e.g. 0 bytes)
    begin
      rugged.references.create(keep_around_ref_name(sha), sha, force: true)
    rescue Rugged::ReferenceError => ex
      Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
270 271 272
    rescue Rugged::OSError => ex
      raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
      Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
273
    end
274 275 276
  end

  def kept_around?(sha)
277
    ref_exists?(keep_around_ref_name(sha))
278 279
  end

280
  def tag_names
281
    cache.fetch(:tag_names) { raw_repository.tag_names }
282 283
  end

284
  def commit_count
285
    cache.fetch(:commit_count) do
286
      begin
287
        raw_repository.commit_count(self.root_ref)
288 289 290
      rescue
        0
      end
291
    end
292 293
  end

Y
Yorick Peterse 已提交
294
  def branch_count
295
    @branch_count ||= cache.fetch(:branch_count) { branches.size }
Y
Yorick Peterse 已提交
296 297 298 299 300 301
  end

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

302 303 304
  # Return repo size in megabytes
  # Cached in redis
  def size
305
    cache.fetch(:size) { raw_repository.size }
306
  end
307

308
  def diverging_commit_counts(branch)
309
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
J
Jeff Stubler 已提交
310
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
311 312
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
313
      number_commits_behind = raw_repository.
314
        count_commits_between(branch.dereferenced_target.sha, root_ref_hash)
315 316

      number_commits_ahead = raw_repository.
317
        count_commits_between(root_ref_hash, branch.dereferenced_target.sha)
318

319 320 321
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
322

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

330 331 332 333 334
  # Keys for data on branch/tag operations.
  def cache_keys_for_branches_and_tags
    %i(branch_names tag_names branch_count tag_count)
  end

335
  def build_cache
336
    (cache_keys + cache_keys_for_branches_and_tags).each do |key|
337 338 339 340 341 342
      unless cache.exist?(key)
        send(key)
      end
    end
  end

D
Douwe Maan 已提交
343 344 345 346 347 348 349
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
P
Paco Guzman 已提交
350
    @branch_names = nil
351
    @local_branches = nil
D
Douwe Maan 已提交
352 353
  end

354
  def expire_cache(branch_name = nil, revision = nil)
355
    cache_keys.each do |key|
356 357
      cache.expire(key)
    end
358

359
    expire_branch_cache(branch_name)
360
    expire_avatar_cache(branch_name, revision)
361 362 363 364

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

367 368 369 370 371 372 373 374 375 376 377 378
  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}")
379
    end
D
Dmitriy Zaporozhets 已提交
380 381
  end

382 383 384 385 386
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

387 388 389 390 391 392 393 394
  # Expires the cache(s) used to determine if a repository is empty or not.
  def expire_emptiness_caches
    cache.expire(:empty?)
    @empty = nil

    expire_has_visible_content_cache
  end

395 396 397 398 399
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

Y
Yorick Peterse 已提交
400 401 402 403 404 405 406 407 408 409
  def expire_branch_count_cache
    cache.expire(:branch_count)
    @branch_count = nil
  end

  def expire_tag_count_cache
    cache.expire(:tag_count)
    @tag_count = nil
  end

410 411 412 413
  def lookup_cache
    @lookup_cache ||= {}
  end

414 415 416 417 418 419 420 421
  def expire_avatar_cache(branch_name = nil, revision = nil)
    # Avatars are pulled from the default branch, thus if somebody pushes to a
    # different branch there's no need to expire anything.
    return if branch_name && branch_name != root_ref

    # We don't want to flush the cache if the commit didn't actually make any
    # changes to any of the possible avatar files.
    if revision && commit = self.commit(revision)
422
      return unless commit.raw_diffs(deltas_only: true).
423 424 425 426 427 428 429 430
        any? { |diff| AVATAR_FILES.include?(diff.new_path) }
    end

    cache.expire(:avatar)

    @avatar = nil
  end

431 432 433 434 435
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

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

447 448 449
  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
450 451
    expire_root_ref_cache
    expire_emptiness_caches
Y
Yorick Peterse 已提交
452 453

    repository_event(:create_repository)
454 455
  end

456 457
  # Runs code just before a repository is deleted.
  def before_delete
458 459
    expire_exists_cache

460 461
    expire_cache if exists?

462
    expire_content_cache
Y
Yorick Peterse 已提交
463 464

    repository_event(:remove_repository)
465 466 467 468 469 470 471
  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 已提交
472 473

    repository_event(:change_default_branch)
474 475
  end

Y
Yorick Peterse 已提交
476 477
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
478
    expire_cache
479
    expire_tags_cache
Y
Yorick Peterse 已提交
480
    expire_tag_count_cache
Y
Yorick Peterse 已提交
481 482

    repository_event(:push_tag)
Y
Yorick Peterse 已提交
483 484 485 486 487 488
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
Y
Yorick Peterse 已提交
489 490

    repository_event(:remove_tag)
491 492
  end

493
  def before_import
494
    expire_content_cache
495 496
  end

497 498
  # Runs code after a repository has been forked/imported.
  def after_import
499 500
    expire_content_cache
    build_cache
501 502 503
  end

  # Runs code after a new commit has been pushed.
504 505
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
Y
Yorick Peterse 已提交
506 507

    repository_event(:push_commit, branch: branch_name)
508 509 510 511
  end

  # Runs code after a new branch has been created.
  def after_create_branch
512
    expire_branches_cache
513
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
514
    expire_branch_count_cache
Y
Yorick Peterse 已提交
515 516

    repository_event(:push_branch)
517 518
  end

519 520 521
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
Y
Yorick Peterse 已提交
522 523

    repository_event(:remove_branch)
524 525
  end

526 527 528
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
529
    expire_branch_count_cache
530
    expire_branches_cache
531 532
  end

533
  def method_missing(m, *args, &block)
534 535 536 537 538 539
    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
540 541
  end

542 543
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
544
  end
D
Dmitriy Zaporozhets 已提交
545 546

  def blob_at(sha, path)
547
    unless Gitlab::Git.blank_ref?(sha)
548
      Blob.decorate(Gitlab::Git::Blob.find(self, sha, path))
549
    end
D
Dmitriy Zaporozhets 已提交
550
  end
551

552 553 554 555
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

556
  def readme
557
    cache.fetch(:readme) { tree(:head).readme }
558
  end
559

560
  def version
561
    cache.fetch(:version) do
562
      tree(:head).blobs.find do |file|
563
        file.name.casecmp('version').zero?
564 565 566 567
      end
    end
  end

568
  def contribution_guide
569 570 571 572 573 574
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
575 576 577

  def changelog
    cache.fetch(:changelog) do
578
      file_on_head(/\A(changelog|history|changes|news)/i)
579
    end
580 581
  end

582
  def license_blob
583
    return nil unless head_exists?
584

585
    cache.fetch(:license_blob) do
586
      file_on_head(/\A(licen[sc]e|copying)(\..+|\z)/i)
587 588
    end
  end
Z
Zeger-Jan van de Weg 已提交
589

590
  def license_key
591
    return nil unless head_exists?
592 593

    cache.fetch(:license_key) do
594
      Licensee.license(path).try(:key)
595
    end
596 597
  end

598 599 600 601 602 603 604 605
  def gitignore
    return nil if !exists? || empty?

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

606 607 608 609 610 611 612 613
  def koding_yml
    return nil unless head_exists?

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

614
  def gitlab_ci_yml
615
    return nil unless head_exists?
616 617 618 619

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

626
  def head_commit
627 628 629 630 631
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
632 633 634 635
  end

  def tree(sha = :head, path = nil)
    if sha == :head
636 637 638 639 640
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
641 642 643 644
    end

    Tree.new(self, sha, path)
  end
D
Dmitriy Zaporozhets 已提交
645 646

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

D
Dmitriy Zaporozhets 已提交
649 650 651 652 653
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
654
  end
D
Dmitriy Zaporozhets 已提交
655 656 657 658 659 660 661 662

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
663
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
664 665 666 667 668 669 670
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
671 672

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

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

P
P.S.V.R 已提交
685
    highest_branch_id = branch_ids.max || 0
686

P
P.S.V.R 已提交
687 688 689
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
690 691
  end

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

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

723
  def contributors
724
    commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
725

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

D
Dmitriy Zaporozhets 已提交
730
      commits.each do |commit|
731
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
732
          contributor.name = commit.author_name
733 734
        end

735
        contributor.commits += 1
736 737
      end

738 739
      contributor
    end
740
  end
D
Dmitriy Zaporozhets 已提交
741

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

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

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

767 768 769
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
770

771 772
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
773
  end
774

775
  def local_branches
776
    @local_branches ||= raw_repository.local_branches
777 778
  end

779 780
  alias_method :branches, :local_branches

781 782 783 784 785
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
786
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
787 788
  end

L
Lin Jen-Shin 已提交
789 790
  def commit_dir(
    user, path, message, branch,
791 792 793 794 795
    author_email: nil, author_name: nil, source_branch: nil)
    update_branch_with_hooks(
      user,
      branch,
      source_branch: source_branch) do |ref|
796 797 798 799 800 801
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        }
S
Stan Hu 已提交
802 803
      }

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

S
Stan Hu 已提交
806 807 808
      raw_repository.mkdir(path, options)
    end
  end
809

L
Lin Jen-Shin 已提交
810 811 812
  # rubocop:disable Metrics/ParameterLists
  def commit_file(
    user, path, content, message, branch, update,
813 814 815 816 817
    author_email: nil, author_name: nil, source_branch: nil)
    update_branch_with_hooks(
      user,
      branch,
      source_branch: source_branch) do |ref|
818 819 820 821 822 823 824 825 826 827 828
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          content: content,
          path: path,
          update: update
        }
829
      }
830

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

833 834
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
835
  end
L
Lin Jen-Shin 已提交
836
  # rubocop:enable Metrics/ParameterLists
837

L
Lin Jen-Shin 已提交
838 839 840
  # rubocop:disable Metrics/ParameterLists
  def update_file(
    user, path, content,
841 842 843 844 845 846
    branch:, previous_path:, message:,
    author_email: nil, author_name: nil, source_branch: nil)
    update_branch_with_hooks(
      user,
      branch,
      source_branch: source_branch) do |ref|
847 848 849 850 851 852 853 854 855 856 857
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          content: content,
          path: path,
          update: true
        }
858 859
      }

860
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
861

862
      if previous_path && previous_path != path
863
        options[:file][:previous_path] = previous_path
864
        Gitlab::Git::Blob.rename(raw_repository, options)
T
tiagonbotelho 已提交
865
      else
866
        Gitlab::Git::Blob.commit(raw_repository, options)
T
tiagonbotelho 已提交
867
      end
868 869
    end
  end
L
Lin Jen-Shin 已提交
870
  # rubocop:enable Metrics/ParameterLists
871

L
Lin Jen-Shin 已提交
872 873
  def remove_file(
    user, path, message, branch,
874 875 876 877 878
    author_email: nil, author_name: nil, source_branch: nil)
    update_branch_with_hooks(
      user,
      branch,
      source_branch: source_branch) do |ref|
879 880 881 882 883 884 885 886 887
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          path: path
        }
888
      }
889

890
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
891

892 893
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
894 895
  end

L
Lin Jen-Shin 已提交
896 897
  def multi_action(
    user:, branch:, message:, actions:,
898 899 900 901 902
    author_email: nil, author_name: nil, source_branch: nil)
    update_branch_with_hooks(
      user,
      branch,
      source_branch: source_branch) do |ref|
M
Marc Siegfriedt 已提交
903 904
      index = rugged.index

905 906 907
      last_commit = find_branch(ref).dereferenced_target
      index.read_tree(last_commit.raw_commit.tree)
      parents = [last_commit.sha]
M
Marc Siegfriedt 已提交
908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942

      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

943 944
  def get_committer_and_author(user, email: nil, name: nil)
    committer = user_to_committer(user)
D
Dan Dunckel 已提交
945
    author = Gitlab::Git::committer_hash(email: email, name: name) || committer
946

947
    {
948 949
      author: author,
      committer: committer
950 951 952
    }
  end

953 954 955 956
  def user_to_committer(user)
    Gitlab::Git::committer_hash(email: user.email, name: user.name)
  end

957 958 959 960 961 962 963 964 965 966 967
  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

968 969 970
  def merge(user, merge_request, options = {})
    our_commit = rugged.branches[merge_request.target_branch].target
    their_commit = rugged.lookup(merge_request.diff_head_sha)
971 972 973 974 975 976 977

    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?

978
    update_branch_with_hooks(user, merge_request.target_branch) do
979 980 981 982
      actual_options = options.merge(
        parents: [our_commit, their_commit],
        tree: merge_index.write_tree(rugged),
      )
983

984 985 986
      commit_id = Rugged::Commit.create(rugged, actual_options)
      merge_request.update(in_progress_merge_commit_sha: commit_id)
      commit_id
987
    end
988 989
  end

990
  def revert(user, commit, base_branch, revert_tree_id = nil)
991
    source_sha = find_branch(base_branch).dereferenced_target.sha
992
    revert_tree_id ||= check_revert_content(commit, base_branch)
993

994
    return false unless revert_tree_id
995

996 997 998 999
    update_branch_with_hooks(
      user,
      base_branch,
      source_branch: revert_tree_id) do
1000
      committer = user_to_committer(user)
L
Lin Jen-Shin 已提交
1001
      Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
1002
        message: commit.revert_message,
1003 1004
        author: committer,
        committer: committer,
1005
        tree: revert_tree_id,
1006
        parents: [rugged.lookup(source_sha)])
1007
    end
1008 1009
  end

P
P.S.V.R 已提交
1010
  def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
1011
    source_sha = find_branch(base_branch).dereferenced_target.sha
P
P.S.V.R 已提交
1012 1013 1014 1015
    cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)

    return false unless cherry_pick_tree_id

1016 1017 1018 1019
    update_branch_with_hooks(
      user,
      base_branch,
      source_branch: cherry_pick_tree_id) do
P
P.S.V.R 已提交
1020
      committer = user_to_committer(user)
L
Lin Jen-Shin 已提交
1021
      Rugged::Commit.create(rugged,
P
P.S.V.R 已提交
1022 1023 1024 1025 1026 1027 1028 1029
        message: commit.message,
        author: {
          email: commit.author_email,
          name: commit.author_name,
          time: commit.authored_date
        },
        committer: committer,
        tree: cherry_pick_tree_id,
1030
        parents: [rugged.lookup(source_sha)])
P
P.S.V.R 已提交
1031 1032 1033
    end
  end

1034
  def resolve_conflicts(user, branch, params)
1035
    update_branch_with_hooks(user, branch) do
1036 1037 1038 1039 1040 1041
      committer = user_to_committer(user)

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

1042
  def check_revert_content(commit, base_branch)
1043
    source_sha = find_branch(base_branch).dereferenced_target.sha
1044
    args       = [commit.id, source_sha]
1045
    args << { mainline: 1 } if commit.merge_commit?
1046 1047 1048 1049 1050 1051 1052 1053 1054 1055

    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 已提交
1056
  def check_cherry_pick_content(commit, base_branch)
1057
    source_sha = find_branch(base_branch).dereferenced_target.sha
P
P.S.V.R 已提交
1058
    args       = [commit.id, source_sha]
1059
    args << 1 if commit.merge_commit?
P
P.S.V.R 已提交
1060 1061 1062 1063 1064 1065 1066 1067 1068 1069

    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

1070 1071
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
1072 1073
  end

F
Florent (HP) 已提交
1074 1075 1076 1077 1078
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
1079 1080
      same_head = branch_commit.id == root_ref_commit.id
      !same_head && is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
1081 1082 1083 1084 1085
    else
      nil
    end
  end

S
Stan Hu 已提交
1086
  def merge_base(first_commit_id, second_commit_id)
1087 1088
    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 已提交
1089
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
1090 1091
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
1092 1093
  end

1094 1095 1096 1097
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end

1098
  def search_files(query, ref)
V
Valery Sizov 已提交
1099 1100 1101 1102
    unless exists? && has_visible_content? && query.present?
      return []
    end

1103
    offset = 2
1104
    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})
1105 1106 1107
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

1108
  def fetch_ref(source_path, source_ref, target_ref)
1109
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
1110 1111 1112
    Gitlab::Popen.popen(args, path_to_repo)
  end

1113 1114 1115 1116
  def create_ref(ref, ref_path)
    fetch_ref(path_to_repo, ref, ref_path)
  end

1117
  def update_branch_with_hooks(current_user, branch, source_branch: nil)
1118 1119
    update_autocrlf_option

1120
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
1121
    target_branch, new_branch_added = raw_ensure_branch(branch, source_branch)
1122
    was_empty = empty?
1123

1124 1125
    # Make commit
    newrev = yield(ref)
1126

1127 1128 1129
    unless newrev
      raise CommitError.new('Failed to create commit')
    end
1130

1131
    if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil?
1132 1133
      oldrev = Gitlab::Git::BLANK_SHA
    else
1134
      oldrev = rugged.merge_base(newrev, target_branch.dereferenced_target.sha)
1135
    end
1136

1137
    GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
1138
      update_ref!(ref, newrev, oldrev)
1139

1140 1141 1142
      # If repo was empty expire cache
      after_create if was_empty
      after_create_branch if was_empty || new_branch_added
1143
    end
1144 1145

    newrev
1146 1147
  end

1148 1149 1150 1151 1152
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

1153 1154 1155 1156
  def gitattribute(path, name)
    raw_repository.attributes(path)[name]
  end

1157 1158 1159 1160 1161 1162 1163 1164 1165 1166
  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

1167
  def avatar
1168 1169
    return nil unless exists?

1170 1171
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
1172
        blob_at_branch(root_ref, file)
1173 1174 1175 1176
      end
    end
  end

1177 1178
  private

1179
  def cache
1180
    @cache ||= RepositoryCache.new(path_with_namespace, @project.id)
1181
  end
1182 1183 1184 1185

  def head_exists?
    exists? && !empty? && !rugged.head_unborn?
  end
1186 1187 1188 1189

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

  def tags_sorted_by_committed_date
1192
    tags.sort_by { |tag| tag.dereferenced_target.committed_date }
1193
  end
D
Douwe Maan 已提交
1194 1195 1196 1197

  def keep_around_ref_name(sha)
    "refs/keep-around/#{sha}"
  end
Y
Yorick Peterse 已提交
1198 1199 1200 1201

  def repository_event(event, tags = {})
    Gitlab::Metrics.add_event(event, { path: path_with_namespace }.merge(tags))
  end
1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221

  def raw_ensure_branch(branch_name, source_branch)
    old_branch = find_branch(branch_name)

    if old_branch
      [old_branch, false]
    elsif source_branch
      oldrev = Gitlab::Git::BLANK_SHA
      ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
      target = commit(source_branch).try(:id)

      unless target
        raise CommitError.new(
          "Cannot find branch #{branch_name} nor #{source_branch}")
      end

      update_ref!(ref, target, oldrev)

      [find_branch(branch_name), true]
    else
1222
      [nil, true] # Empty branch
1223 1224
    end
  end
1225
end