repository.rb 29.0 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
  def initialize(path_with_namespace, project)
15
    @path_with_namespace = path_with_namespace
16
    @project = project
17
  end
18

19 20
  def raw_repository
    return nil unless path_with_namespace
21

22
    @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
23 24
  end

25 26 27 28
  def update_autocrlf_option
    raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
  end

29
  # Return absolute path to repository
30
  def path_to_repo
31
    @path_to_repo ||= File.expand_path(
32
      File.join(@project.repository_storage_path, path_with_namespace + ".git")
33
    )
34 35
  end

36
  def exists?
37
    return @exists unless @exists.nil?
38

39 40 41 42 43 44 45
    @exists = cache.fetch(:exists?) do
      begin
        raw_repository && raw_repository.rugged ? true : false
      rescue Gitlab::Git::Repository::NoRepository
        false
      end
    end
46 47 48
  end

  def empty?
49 50 51
    return @empty unless @empty.nil?

    @empty = cache.fetch(:empty?) { raw_repository.empty? }
52 53
  end

54 55 56 57 58 59 60 61 62 63
  #
  # 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?
64 65 66
    return @has_visible_content unless @has_visible_content.nil?

    @has_visible_content = cache.fetch(:has_visible_content?) do
67
      branch_count > 0
68
    end
69 70
  end

L
Lin Jen-Shin 已提交
71
  def commit(ref = 'HEAD')
72
    return nil unless exists?
73 74 75 76 77 78
    commit =
      if ref.is_a?(Gitlab::Git::Commit)
        ref
      else
        Gitlab::Git::Commit.find(raw_repository, ref)
      end
79
    commit = ::Commit.new(commit, @project) if commit
80
    commit
81
  rescue Rugged::OdbError
82
    nil
83 84
  end

85
  def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil)
86
    options = {
87 88 89 90 91
      repo: raw_repository,
      ref: ref,
      path: path,
      limit: limit,
      offset: offset,
92 93
      after: after,
      before: before,
94 95
      # --follow doesn't play well with --skip. See:
      # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
96 97
      follow: false,
      skip_merges: skip_merges
98 99 100
    }

    commits = Gitlab::Git::Commit.where(options)
101
    commits = Commit.decorate(commits, @project) if commits.present?
102 103 104
    commits
  end

105 106
  def commits_between(from, to)
    commits = Gitlab::Git::Commit.between(raw_repository, from, to)
107
    commits = Commit.decorate(commits, @project) if commits.present?
108 109 110
    commits
  end

111 112 113
  def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
    ref ||= root_ref

114
    # Limited to 1000 commits for now, could be parameterized?
115 116
    args = %W(#{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} --max-count #{limit} --grep=#{query})
    args = args.concat(%W(-- #{path})) if path.present?
117

118 119
    git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
    commits = git_log_results.map { |c| commit(c) }
120
    commits
121 122
  end

123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
  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)
138 139 140
  end

  def find_tag(name)
141
    tags.find { |tag| tag.name == name }
142 143
  end

144 145 146 147 148 149 150 151
  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
152
      update_ref!(ref, target, oldrev)
153
    end
154

155
    after_create_branch
156
    find_branch(branch_name)
157 158
  end

159 160 161 162 163 164
  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
165

166 167
    options = { message: message, tagger: user_to_committer(user) } if message

168 169
    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
      rugged.tags.create(tag_name, target, options)
170
    end
171

172
    find_tag(tag_name)
173 174
  end

175
  def rm_branch(user, branch_name)
176
    before_remove_branch
177

178
    branch = find_branch(branch_name)
179
    oldrev = branch.try(:target).try(:id)
180 181 182 183
    newrev = Gitlab::Git::BLANK_SHA
    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name

    GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
184
      update_ref!(ref, newrev, oldrev)
185
    end
186

187
    after_remove_branch
188
    true
189 190
  end

191
  def rm_tag(tag_name)
Y
Yorick Peterse 已提交
192
    before_remove_tag
193

R
Robert Schilling 已提交
194 195 196 197 198 199
    begin
      rugged.tags.delete(tag_name)
      true
    rescue Rugged::ReferenceError
      false
    end
200 201
  end

202 203 204 205
  def ref_names
    branch_names + tag_names
  end

206
  def branch_names
P
Paco Guzman 已提交
207
    @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) }
208 209
  end

210 211 212 213
  def branch_exists?(branch_name)
    branch_names.include?(branch_name)
  end

214 215 216 217
  def ref_exists?(ref)
    rugged.references.exist?(ref)
  end

218 219 220 221 222 223
  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.
    command = %w[git update-ref --stdin -z]
224
    _, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin|
225 226 227 228 229
      stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00")
    end

    return if status.zero?

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

D
Douwe Maan 已提交
233 234 235 236
  # 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.
237 238 239 240 241
  def keep_around(sha)
    return unless sha && commit(sha)

    return if kept_around?(sha)

242 243 244 245 246
    # 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}"
247 248 249
    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}"
250
    end
251 252 253
  end

  def kept_around?(sha)
254 255 256 257 258
    begin
      ref_exists?(keep_around_ref_name(sha))
    rescue Rugged::ReferenceError
      false
    end
259 260
  end

261
  def tag_names
262
    cache.fetch(:tag_names) { raw_repository.tag_names }
263 264
  end

265
  def commit_count
266
    cache.fetch(:commit_count) do
267
      begin
268
        raw_repository.commit_count(self.root_ref)
269 270 271
      rescue
        0
      end
272
    end
273 274
  end

Y
Yorick Peterse 已提交
275
  def branch_count
276
    @branch_count ||= cache.fetch(:branch_count) { branches.size }
Y
Yorick Peterse 已提交
277 278 279 280 281 282
  end

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

283 284 285
  # Return repo size in megabytes
  # Cached in redis
  def size
286
    cache.fetch(:size) { raw_repository.size }
287
  end
288

289
  def diverging_commit_counts(branch)
290
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
J
Jeff Stubler 已提交
291
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
292 293
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
294
      number_commits_behind = raw_repository.
295
        count_commits_between(branch.target.sha, root_ref_hash)
296 297

      number_commits_ahead = raw_repository.
298
        count_commits_between(root_ref_hash, branch.target.sha)
299

300 301 302
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
303

304
  # Keys for data that can be affected for any commit push.
305
  def cache_keys
306
    %i(size commit_count
307
       readme version contribution_guide changelog
308
       license_blob license_key gitignore koding_yml)
309
  end
310

311 312 313 314 315
  # Keys for data on branch/tag operations.
  def cache_keys_for_branches_and_tags
    %i(branch_names tag_names branch_count tag_count)
  end

316
  def build_cache
317
    (cache_keys + cache_keys_for_branches_and_tags).each do |key|
318 319 320 321 322 323
      unless cache.exist?(key)
        send(key)
      end
    end
  end

D
Douwe Maan 已提交
324 325 326 327 328 329 330
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
P
Paco Guzman 已提交
331
    @branch_names = nil
332
    @local_branches = nil
D
Douwe Maan 已提交
333 334
  end

335
  def expire_cache(branch_name = nil, revision = nil)
336
    cache_keys.each do |key|
337 338
      cache.expire(key)
    end
339

340
    expire_branch_cache(branch_name)
341
    expire_avatar_cache(branch_name, revision)
342 343 344 345

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

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

363 364 365 366 367
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

368 369 370 371 372 373 374 375
  # 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

376 377 378 379 380
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

Y
Yorick Peterse 已提交
381 382 383 384 385 386 387 388 389 390
  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

391 392 393 394
  def lookup_cache
    @lookup_cache ||= {}
  end

395 396 397 398 399 400 401 402
  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)
403
      return unless commit.raw_diffs(deltas_only: true).
404 405 406 407 408 409 410 411
        any? { |diff| AVATAR_FILES.include?(diff.new_path) }
    end

    cache.expire(:avatar)

    @avatar = nil
  end

412 413 414 415 416 417 418 419
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
420 421
    expire_root_ref_cache
    expire_emptiness_caches
Y
Yorick Peterse 已提交
422 423

    repository_event(:create_repository)
424 425
  end

426 427
  # Runs code just before a repository is deleted.
  def before_delete
428 429
    expire_exists_cache

430 431
    expire_cache if exists?

432 433 434 435 436
    # expire cache that don't depend on repository data (when expiring)
    expire_tags_cache
    expire_tag_count_cache
    expire_branches_cache
    expire_branch_count_cache
437 438
    expire_root_ref_cache
    expire_emptiness_caches
439
    expire_exists_cache
Y
Yorick Peterse 已提交
440 441

    repository_event(:remove_repository)
442 443 444 445 446 447 448
  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 已提交
449 450

    repository_event(:change_default_branch)
451 452
  end

Y
Yorick Peterse 已提交
453 454
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
455
    expire_cache
456
    expire_tags_cache
Y
Yorick Peterse 已提交
457
    expire_tag_count_cache
Y
Yorick Peterse 已提交
458 459

    repository_event(:push_tag)
Y
Yorick Peterse 已提交
460 461 462 463 464 465
  end

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

    repository_event(:remove_tag)
468 469
  end

470 471 472 473 474
  def before_import
    expire_emptiness_caches
    expire_exists_cache
  end

475 476 477
  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
478
    expire_exists_cache
479 480 481
  end

  # Runs code after a new commit has been pushed.
482 483
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
Y
Yorick Peterse 已提交
484 485

    repository_event(:push_commit, branch: branch_name)
486 487 488 489
  end

  # Runs code after a new branch has been created.
  def after_create_branch
490
    expire_branches_cache
491
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
492
    expire_branch_count_cache
Y
Yorick Peterse 已提交
493 494

    repository_event(:push_branch)
495 496
  end

497 498 499
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
Y
Yorick Peterse 已提交
500 501

    repository_event(:remove_branch)
502 503
  end

504 505 506
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
507
    expire_branch_count_cache
508
    expire_branches_cache
509 510
  end

511
  def method_missing(m, *args, &block)
512 513 514 515 516 517
    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
518 519
  end

520 521
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
522
  end
D
Dmitriy Zaporozhets 已提交
523 524

  def blob_at(sha, path)
525
    unless Gitlab::Git.blank_ref?(sha)
526
      Blob.decorate(Gitlab::Git::Blob.find(self, sha, path))
527
    end
D
Dmitriy Zaporozhets 已提交
528
  end
529

530 531 532 533
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

534
  def readme
535
    cache.fetch(:readme) { tree(:head).readme }
536
  end
537

538
  def version
539
    cache.fetch(:version) do
540
      tree(:head).blobs.find do |file|
541
        file.name.casecmp('version').zero?
542 543 544 545
      end
    end
  end

546
  def contribution_guide
547 548 549 550 551 552
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
553 554 555

  def changelog
    cache.fetch(:changelog) do
556
      file_on_head(/\A(changelog|history|changes|news)/i)
557
    end
558 559
  end

560
  def license_blob
561
    return nil unless head_exists?
562

563
    cache.fetch(:license_blob) do
564
      file_on_head(/\A(licen[sc]e|copying)(\..+|\z)/i)
565 566
    end
  end
Z
Zeger-Jan van de Weg 已提交
567

568
  def license_key
569
    return nil unless head_exists?
570 571

    cache.fetch(:license_key) do
572
      Licensee.license(path).try(:key)
573
    end
574 575
  end

576 577 578 579 580 581 582 583
  def gitignore
    return nil if !exists? || empty?

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

584 585 586 587 588 589 590 591
  def koding_yml
    return nil unless head_exists?

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

592
  def gitlab_ci_yml
593
    return nil unless head_exists?
594 595 596 597

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

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

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

  def tree(sha = :head, path = nil)
    if sha == :head
614 615 616 617 618
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
619 620 621 622
    end

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

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

D
Dmitriy Zaporozhets 已提交
627 628 629 630 631
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
632
  end
D
Dmitriy Zaporozhets 已提交
633 634 635 636 637 638 639 640

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
641
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
642 643 644 645 646 647 648
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
649 650

  def last_commit_for_path(sha, path)
651
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
652 653
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
654
  end
655

656
  def next_branch(name, opts = {})
P
P.S.V.R 已提交
657 658 659
    branch_ids = self.branch_names.map do |n|
      next 1 if n == name
      result = n.match(/\A#{name}-([0-9]+)\z/)
660 661 662
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
663
    highest_branch_id = branch_ids.max || 0
664

P
P.S.V.R 已提交
665 666 667
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
668 669
  end

670
  # Remove archives older than 2 hours
671 672
  def branches_sorted_by(value)
    case value
673 674
    when 'name'
      branches.sort_by(&:name)
675
    when 'updated_desc'
676 677 678
      branches.sort do |a, b|
        commit(b.target).committed_date <=> commit(a.target).committed_date
      end
679
    when 'updated_asc'
680 681 682 683 684 685 686
      branches.sort do |a, b|
        commit(a.target).committed_date <=> commit(b.target).committed_date
      end
    else
      branches
    end
  end
687

688 689 690
  def tags_sorted_by(value)
    case value
    when 'name'
691
      VersionSorter.rsort(tags) { |tag| tag.name }
692 693 694 695 696 697 698 699 700
    when 'updated_desc'
      tags_sorted_by_committed_date.reverse
    when 'updated_asc'
      tags_sorted_by_committed_date
    else
      tags
    end
  end

701
  def contributors
702
    commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
703

D
Dmitriy Zaporozhets 已提交
704
    commits.group_by(&:author_email).map do |email, commits|
705 706
      contributor = Gitlab::Contributor.new
      contributor.email = email
707

D
Dmitriy Zaporozhets 已提交
708
      commits.each do |commit|
709
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
710
          contributor.name = commit.author_name
711 712
        end

713
        contributor.commits += 1
714 715
      end

716 717
      contributor
    end
718
  end
D
Dmitriy Zaporozhets 已提交
719

720 721
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
722 723 724 725 726 727 728 729 730 731 732 733 734 735
    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 已提交
736

737 738 739
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
740

741 742
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
743
  end
744

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

749 750
  alias_method :branches, :local_branches

751 752 753 754 755
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
756
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
757 758
  end

759
  def commit_dir(user, path, message, branch, author_email: nil, author_name: nil)
760
    update_branch_with_hooks(user, branch) do |ref|
761 762 763 764 765 766
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        }
S
Stan Hu 已提交
767 768
      }

769 770
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))

S
Stan Hu 已提交
771 772 773
      raw_repository.mkdir(path, options)
    end
  end
774

775
  def commit_file(user, path, content, message, branch, update, author_email: nil, author_name: nil)
776
    update_branch_with_hooks(user, branch) do |ref|
777 778 779 780 781 782 783 784 785 786 787
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          content: content,
          path: path,
          update: update
        }
788
      }
789

790
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
791

792 793
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
794 795
  end

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

811
      options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
812

813
      if previous_path && previous_path != path
814
        options[:file][:previous_path] = previous_path
815
        Gitlab::Git::Blob.rename(raw_repository, options)
T
tiagonbotelho 已提交
816
      else
817
        Gitlab::Git::Blob.commit(raw_repository, options)
T
tiagonbotelho 已提交
818
      end
819 820 821
    end
  end

822
  def remove_file(user, path, message, branch, author_email: nil, author_name: nil)
823
    update_branch_with_hooks(user, branch) do |ref|
824 825 826 827 828 829 830 831 832
      options = {
        commit: {
          branch: ref,
          message: message,
          update_ref: false
        },
        file: {
          path: path
        }
833
      }
834

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

837 838
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
839 840
  end

M
Marc Siegfriedt 已提交
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
  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
        last_commit = branch.target
        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

887 888
  def get_committer_and_author(user, email: nil, name: nil)
    committer = user_to_committer(user)
D
Dan Dunckel 已提交
889
    author = Gitlab::Git::committer_hash(email: email, name: name) || committer
890

891
    {
892 893
      author: author,
      committer: committer
894 895 896
    }
  end

897 898 899 900
  def user_to_committer(user)
    Gitlab::Git::committer_hash(email: user.email, name: user.name)
  end

901 902 903 904 905 906 907 908 909 910 911
  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

912 913 914
  def merge(user, merge_request, options = {})
    our_commit = rugged.branches[merge_request.target_branch].target
    their_commit = rugged.lookup(merge_request.diff_head_sha)
915 916 917 918 919 920 921

    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?

922
    update_branch_with_hooks(user, merge_request.target_branch) do
923 924 925 926
      actual_options = options.merge(
        parents: [our_commit, their_commit],
        tree: merge_index.write_tree(rugged),
      )
927

928 929 930
      commit_id = Rugged::Commit.create(rugged, actual_options)
      merge_request.update(in_progress_merge_commit_sha: commit_id)
      commit_id
931
    end
932 933
  end

934
  def revert(user, commit, base_branch, revert_tree_id = nil)
935
    source_sha = find_branch(base_branch).target.sha
936
    revert_tree_id ||= check_revert_content(commit, base_branch)
937

938
    return false unless revert_tree_id
939

940
    update_branch_with_hooks(user, base_branch) do
941
      committer = user_to_committer(user)
942
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
943
        message: commit.revert_message,
944 945
        author: committer,
        committer: committer,
946
        tree: revert_tree_id,
947
        parents: [rugged.lookup(source_sha)])
948
    end
949 950
  end

P
P.S.V.R 已提交
951
  def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
952
    source_sha = find_branch(base_branch).target.sha
P
P.S.V.R 已提交
953 954 955 956
    cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)

    return false unless cherry_pick_tree_id

957
    update_branch_with_hooks(user, base_branch) do
P
P.S.V.R 已提交
958 959 960 961 962 963 964 965 966 967
      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,
968
        parents: [rugged.lookup(source_sha)])
P
P.S.V.R 已提交
969 970 971
    end
  end

972
  def resolve_conflicts(user, branch, params)
973
    update_branch_with_hooks(user, branch) do
974 975 976 977 978 979
      committer = user_to_committer(user)

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

980
  def check_revert_content(commit, base_branch)
981
    source_sha = find_branch(base_branch).target.sha
982
    args       = [commit.id, source_sha]
983
    args << { mainline: 1 } if commit.merge_commit?
984 985 986 987 988 989 990 991 992 993

    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 已提交
994
  def check_cherry_pick_content(commit, base_branch)
995
    source_sha = find_branch(base_branch).target.sha
P
P.S.V.R 已提交
996
    args       = [commit.id, source_sha]
997
    args << 1 if commit.merge_commit?
P
P.S.V.R 已提交
998 999 1000 1001 1002 1003 1004 1005 1006 1007

    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

1008 1009
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
1010 1011
  end

F
Florent (HP) 已提交
1012 1013 1014 1015 1016
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
1017
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
1018 1019 1020 1021 1022
    else
      nil
    end
  end

S
Stan Hu 已提交
1023
  def merge_base(first_commit_id, second_commit_id)
1024 1025
    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 已提交
1026
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
1027 1028
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
1029 1030
  end

1031 1032 1033 1034
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end

1035 1036
  def search_files(query, ref)
    offset = 2
1037
    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})
1038 1039 1040
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

1041
  def fetch_ref(source_path, source_ref, target_ref)
1042
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
1043 1044 1045
    Gitlab::Popen.popen(args, path_to_repo)
  end

1046 1047 1048 1049
  def create_ref(ref, ref_path)
    fetch_ref(path_to_repo, ref, ref_path)
  end

1050
  def update_branch_with_hooks(current_user, branch)
1051 1052
    update_autocrlf_option

1053
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
1054
    target_branch = find_branch(branch)
1055
    was_empty = empty?
1056

1057 1058
    # Make commit
    newrev = yield(ref)
1059

1060 1061 1062
    unless newrev
      raise CommitError.new('Failed to create commit')
    end
1063

1064 1065 1066 1067 1068
    if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil?
      oldrev = Gitlab::Git::BLANK_SHA
    else
      oldrev = rugged.merge_base(newrev, target_branch.target.sha)
    end
1069

1070
    GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
1071
      update_ref!(ref, newrev, oldrev)
1072

1073
      if was_empty || !target_branch
1074 1075 1076
        # If repo was empty expire cache
        after_create if was_empty
        after_create_branch
1077 1078
      end
    end
1079 1080

    newrev
1081 1082
  end

1083 1084 1085 1086 1087
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

1088 1089 1090 1091
  def gitattribute(path, name)
    raw_repository.attributes(path)[name]
  end

1092 1093 1094 1095 1096 1097 1098 1099 1100 1101
  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

1102
  def avatar
1103 1104
    return nil unless exists?

1105 1106
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
1107
        blob_at_branch(root_ref, file)
1108 1109 1110 1111
      end
    end
  end

1112 1113
  private

1114
  def cache
1115
    @cache ||= RepositoryCache.new(path_with_namespace, @project.id)
1116
  end
1117 1118 1119 1120

  def head_exists?
    exists? && !empty? && !rugged.head_unborn?
  end
1121 1122 1123 1124

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

  def tags_sorted_by_committed_date
1127
    tags.sort_by { |tag| tag.target.committed_date }
1128
  end
D
Douwe Maan 已提交
1129 1130 1131 1132

  def keep_around_ref_name(sha)
    "refs/keep-around/#{sha}"
  end
Y
Yorick Peterse 已提交
1133 1134 1135 1136

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