repository.rb 31.4 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 992
    source_sha = raw_ensure_branch(base_branch, source_commit: commit).
      first.dereferenced_target.sha
993
    revert_tree_id ||= check_revert_content(commit, base_branch)
994

995
    return false unless revert_tree_id
996

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

P
P.S.V.R 已提交
1011
  def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
1012 1013
    source_sha = raw_ensure_branch(base_branch, source_commit: commit).
      first.dereferenced_target.sha
P
P.S.V.R 已提交
1014 1015 1016 1017
    cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)

    return false unless cherry_pick_tree_id

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

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

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

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

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

    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

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

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

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

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

1096 1097 1098 1099
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end

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

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

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

1115 1116 1117 1118
  def create_ref(ref, ref_path)
    fetch_ref(path_to_repo, ref, ref_path)
  end

1119
  def update_branch_with_hooks(current_user, branch, source_branch: nil)
1120 1121
    update_autocrlf_option

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

1127 1128
    # Make commit
    newrev = yield(ref)
1129

1130 1131 1132
    unless newrev
      raise CommitError.new('Failed to create commit')
    end
1133

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

1140
    GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
1141
      update_ref!(ref, newrev, oldrev)
1142

1143 1144 1145
      # If repo was empty expire cache
      after_create if was_empty
      after_create_branch if was_empty || new_branch_added
1146
    end
1147 1148

    newrev
1149 1150
  end

1151 1152 1153 1154 1155
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

1156 1157 1158 1159
  def gitattribute(path, name)
    raw_repository.attributes(path)[name]
  end

1160 1161 1162 1163 1164 1165 1166 1167 1168 1169
  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

1170
  def avatar
1171 1172
    return nil unless exists?

1173 1174
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
1175
        blob_at_branch(root_ref, file)
1176 1177 1178 1179
      end
    end
  end

1180 1181
  private

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

  def head_exists?
    exists? && !empty? && !rugged.head_unborn?
  end
1189 1190 1191 1192

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

  def tags_sorted_by_committed_date
1195
    tags.sort_by { |tag| tag.dereferenced_target.committed_date }
1196
  end
D
Douwe Maan 已提交
1197 1198 1199 1200

  def keep_around_ref_name(sha)
    "refs/keep-around/#{sha}"
  end
Y
Yorick Peterse 已提交
1201 1202 1203 1204

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

1206
  def raw_ensure_branch(branch_name, source_commit: nil, source_branch: nil)
1207 1208 1209 1210
    old_branch = find_branch(branch_name)

    if old_branch
      [old_branch, false]
1211
    elsif source_commit || source_branch
1212 1213
      oldrev = Gitlab::Git::BLANK_SHA
      ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
1214
      target = (source_commit || commit(source_branch)).try(:sha)
1215 1216 1217

      unless target
        raise CommitError.new(
1218
          "Cannot find branch #{branch_name} nor #{source_commit.try(:sha) || source_branch}")
1219 1220 1221 1222 1223 1224
      end

      update_ref!(ref, target, oldrev)

      [find_branch(branch_name), true]
    else
1225
      [nil, true] # Empty branch
1226 1227
    end
  end
1228
end