repository.rb 30.1 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 166 167 168 169 170 171 172
  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
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
  end

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

643
    Tree.new(self, sha, path, recursive: recursive)
644
  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

789
  def commit_dir(user, path, message, branch, author_email: nil, author_name: nil)
790
    update_branch_with_hooks(user, branch) do |ref|
791 792 793 794 795 796
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        }
S
Stan Hu 已提交
797 798
      }

799 800
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))

S
Stan Hu 已提交
801 802 803
      raw_repository.mkdir(path, options)
    end
  end
804

805
  def commit_file(user, path, content, message, branch, update, author_email: nil, author_name: nil)
806
    update_branch_with_hooks(user, branch) do |ref|
807 808 809 810 811 812 813 814 815 816 817
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          content: content,
          path: path,
          update: update
        }
818
      }
819

820
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
821

822 823
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
824 825
  end

826
  def update_file(user, path, content, branch:, previous_path:, message:, author_email: nil, author_name: nil)
827
    update_branch_with_hooks(user, branch) do |ref|
828 829 830 831 832 833 834 835 836 837 838
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          content: content,
          path: path,
          update: true
        }
839 840
      }

841
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
842

843
      if previous_path && previous_path != path
844
        options[:file][:previous_path] = previous_path
845
        Gitlab::Git::Blob.rename(raw_repository, options)
T
tiagonbotelho 已提交
846
      else
847
        Gitlab::Git::Blob.commit(raw_repository, options)
T
tiagonbotelho 已提交
848
      end
849 850 851
    end
  end

852
  def remove_file(user, path, message, branch, author_email: nil, author_name: nil)
853
    update_branch_with_hooks(user, branch) do |ref|
854 855 856 857 858 859 860 861 862
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          path: path
        }
863
      }
864

865
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
866

867 868
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
869 870
  end

M
Marc Siegfriedt 已提交
871 872 873 874 875 876 877
  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
878
        last_commit = branch.dereferenced_target
M
Marc Siegfriedt 已提交
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 910 911 912 913 914 915 916
        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

917 918
  def get_committer_and_author(user, email: nil, name: nil)
    committer = user_to_committer(user)
D
Dan Dunckel 已提交
919
    author = Gitlab::Git::committer_hash(email: email, name: name) || committer
920

921
    {
922 923
      author: author,
      committer: committer
924 925 926
    }
  end

927 928 929 930
  def user_to_committer(user)
    Gitlab::Git::committer_hash(email: user.email, name: user.name)
  end

931 932 933 934 935 936 937 938 939 940 941
  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

942 943 944
  def merge(user, merge_request, options = {})
    our_commit = rugged.branches[merge_request.target_branch].target
    their_commit = rugged.lookup(merge_request.diff_head_sha)
945 946 947 948 949 950 951

    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?

952
    update_branch_with_hooks(user, merge_request.target_branch) do
953 954 955 956
      actual_options = options.merge(
        parents: [our_commit, their_commit],
        tree: merge_index.write_tree(rugged),
      )
957

958 959 960
      commit_id = Rugged::Commit.create(rugged, actual_options)
      merge_request.update(in_progress_merge_commit_sha: commit_id)
      commit_id
961
    end
962 963
  end

964
  def revert(user, commit, base_branch, revert_tree_id = nil)
965
    source_sha = find_branch(base_branch).dereferenced_target.sha
966
    revert_tree_id ||= check_revert_content(commit, base_branch)
967

968
    return false unless revert_tree_id
969

970
    update_branch_with_hooks(user, base_branch) do
971
      committer = user_to_committer(user)
972
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
973
        message: commit.revert_message,
974 975
        author: committer,
        committer: committer,
976
        tree: revert_tree_id,
977
        parents: [rugged.lookup(source_sha)])
978
    end
979 980
  end

P
P.S.V.R 已提交
981
  def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
982
    source_sha = find_branch(base_branch).dereferenced_target.sha
P
P.S.V.R 已提交
983 984 985 986
    cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)

    return false unless cherry_pick_tree_id

987
    update_branch_with_hooks(user, base_branch) do
P
P.S.V.R 已提交
988 989 990 991 992 993 994 995 996 997
      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,
998
        parents: [rugged.lookup(source_sha)])
P
P.S.V.R 已提交
999 1000 1001
    end
  end

1002
  def resolve_conflicts(user, branch, params)
1003
    update_branch_with_hooks(user, branch) do
1004 1005 1006 1007 1008 1009
      committer = user_to_committer(user)

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

1010
  def check_revert_content(commit, base_branch)
1011
    source_sha = find_branch(base_branch).dereferenced_target.sha
1012
    args       = [commit.id, source_sha]
1013
    args << { mainline: 1 } if commit.merge_commit?
1014 1015 1016 1017 1018 1019 1020 1021 1022 1023

    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 已提交
1024
  def check_cherry_pick_content(commit, base_branch)
1025
    source_sha = find_branch(base_branch).dereferenced_target.sha
P
P.S.V.R 已提交
1026
    args       = [commit.id, source_sha]
1027
    args << 1 if commit.merge_commit?
P
P.S.V.R 已提交
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037

    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

1038 1039
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
1040 1041
  end

F
Florent (HP) 已提交
1042 1043 1044 1045 1046
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
1047 1048
      same_head = branch_commit.id == root_ref_commit.id
      !same_head && is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
1049 1050 1051 1052 1053
    else
      nil
    end
  end

S
Stan Hu 已提交
1054
  def merge_base(first_commit_id, second_commit_id)
1055 1056
    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 已提交
1057
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
1058 1059
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
1060 1061
  end

1062 1063 1064 1065
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end

1066
  def search_files(query, ref)
V
Valery Sizov 已提交
1067 1068 1069 1070
    unless exists? && has_visible_content? && query.present?
      return []
    end

1071
    offset = 2
1072
    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})
1073 1074 1075
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

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

1081 1082 1083 1084
  def create_ref(ref, ref_path)
    fetch_ref(path_to_repo, ref, ref_path)
  end

1085
  def update_branch_with_hooks(current_user, branch)
1086 1087
    update_autocrlf_option

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

1092 1093
    # Make commit
    newrev = yield(ref)
1094

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

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

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

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

    newrev
1116 1117
  end

1118 1119 1120 1121 1122
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

1123 1124 1125 1126
  def gitattribute(path, name)
    raw_repository.attributes(path)[name]
  end

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

1137
  def avatar
1138 1139
    return nil unless exists?

1140 1141
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
1142
        blob_at_branch(root_ref, file)
1143 1144 1145 1146
      end
    end
  end

1147 1148
  private

1149
  def cache
1150
    @cache ||= RepositoryCache.new(path_with_namespace, @project.id)
1151
  end
1152 1153 1154 1155

  def head_exists?
    exists? && !empty? && !rugged.head_unborn?
  end
1156 1157 1158 1159

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

  def tags_sorted_by_committed_date
1162
    tags.sort_by { |tag| tag.dereferenced_target.committed_date }
1163
  end
D
Douwe Maan 已提交
1164 1165 1166 1167

  def keep_around_ref_name(sha)
    "refs/keep-around/#{sha}"
  end
Y
Yorick Peterse 已提交
1168 1169 1170 1171

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