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

3
class Repository
4 5
  class CommitError < StandardError; end

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

10 11
  include Gitlab::ShellAdapter

12
  attr_accessor :path_with_namespace, :project
13

14
  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
  def find_branch(name)
124
    raw_repository.branches.find { |branch| branch.name == name }
125 126 127
  end

  def find_tag(name)
128
    tags.find { |tag| tag.name == name }
129 130
  end

131 132 133 134 135 136 137 138 139 140
  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
      rugged.branches.create(branch_name, target)
    end
141

142
    after_create_branch
143
    find_branch(branch_name)
144 145
  end

146 147 148 149 150 151
  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
152

153 154
    options = { message: message, tagger: user_to_committer(user) } if message

155 156
    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
      rugged.tags.create(tag_name, target, options)
157
    end
158

159
    find_tag(tag_name)
160 161
  end

162
  def rm_branch(user, branch_name)
163
    before_remove_branch
164

165
    branch = find_branch(branch_name)
166
    oldrev = branch.try(:target).try(:id)
167 168 169 170 171 172
    newrev = Gitlab::Git::BLANK_SHA
    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name

    GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
      rugged.branches.delete(branch_name)
    end
173

174
    after_remove_branch
175
    true
176 177
  end

178
  def rm_tag(tag_name)
Y
Yorick Peterse 已提交
179
    before_remove_tag
180

R
Robert Schilling 已提交
181 182 183 184 185 186
    begin
      rugged.tags.delete(tag_name)
      true
    rescue Rugged::ReferenceError
      false
    end
187 188
  end

189 190 191 192
  def ref_names
    branch_names + tag_names
  end

193
  def branch_names
P
Paco Guzman 已提交
194
    @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) }
195 196
  end

197 198 199 200
  def branch_exists?(branch_name)
    branch_names.include?(branch_name)
  end

201 202 203 204
  def ref_exists?(ref)
    rugged.references.exist?(ref)
  end

D
Douwe Maan 已提交
205 206 207 208
  # 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.
209 210 211 212 213
  def keep_around(sha)
    return unless sha && commit(sha)

    return if kept_around?(sha)

214 215 216 217 218
    # 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}"
219 220 221
    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}"
222
    end
223 224 225
  end

  def kept_around?(sha)
226 227 228 229 230
    begin
      ref_exists?(keep_around_ref_name(sha))
    rescue Rugged::ReferenceError
      false
    end
231 232
  end

233
  def tag_names
234
    cache.fetch(:tag_names) { raw_repository.tag_names }
235 236
  end

237
  def commit_count
238
    cache.fetch(:commit_count) do
239
      begin
240
        raw_repository.commit_count(self.root_ref)
241 242 243
      rescue
        0
      end
244
    end
245 246
  end

Y
Yorick Peterse 已提交
247
  def branch_count
248
    @branch_count ||= cache.fetch(:branch_count) { branches.size }
Y
Yorick Peterse 已提交
249 250 251 252 253 254
  end

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

255 256 257
  # Return repo size in megabytes
  # Cached in redis
  def size
258
    cache.fetch(:size) { raw_repository.size }
259
  end
260

261
  def diverging_commit_counts(branch)
262
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
J
Jeff Stubler 已提交
263
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
264 265
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
266
      number_commits_behind = raw_repository.
267
        count_commits_between(branch.target.sha, root_ref_hash)
268 269

      number_commits_ahead = raw_repository.
270
        count_commits_between(root_ref_hash, branch.target.sha)
271

272 273 274
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
275

276
  # Keys for data that can be affected for any commit push.
277
  def cache_keys
278
    %i(size commit_count
279
       readme version contribution_guide changelog
A
Alfredo Sumaran 已提交
280
       license_blob license_key gitignore)
281
  end
282

283 284 285 286 287
  # Keys for data on branch/tag operations.
  def cache_keys_for_branches_and_tags
    %i(branch_names tag_names branch_count tag_count)
  end

288
  def build_cache
289
    (cache_keys + cache_keys_for_branches_and_tags).each do |key|
290 291 292 293 294 295
      unless cache.exist?(key)
        send(key)
      end
    end
  end

D
Douwe Maan 已提交
296 297 298 299 300 301 302
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
P
Paco Guzman 已提交
303
    @branch_names = nil
304
    @local_branches = nil
D
Douwe Maan 已提交
305 306
  end

307
  def expire_cache(branch_name = nil, revision = nil)
308
    cache_keys.each do |key|
309 310
      cache.expire(key)
    end
311

312
    expire_branch_cache(branch_name)
313
    expire_avatar_cache(branch_name, revision)
314 315 316 317

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

320 321 322 323 324 325 326 327 328 329 330 331
  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}")
332
    end
D
Dmitriy Zaporozhets 已提交
333 334
  end

335 336 337 338 339
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

340 341 342 343 344 345 346 347
  # 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

348 349 350 351 352
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

Y
Yorick Peterse 已提交
353 354 355 356 357 358 359 360 361 362
  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

363 364 365 366
  def lookup_cache
    @lookup_cache ||= {}
  end

367 368 369 370 371 372 373 374
  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)
375
      return unless commit.raw_diffs(deltas_only: true).
376 377 378 379 380 381 382 383
        any? { |diff| AVATAR_FILES.include?(diff.new_path) }
    end

    cache.expire(:avatar)

    @avatar = nil
  end

384 385 386 387 388 389 390 391
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
392 393
    expire_root_ref_cache
    expire_emptiness_caches
394 395
  end

396 397
  # Runs code just before a repository is deleted.
  def before_delete
398 399
    expire_exists_cache

400 401
    expire_cache if exists?

402 403 404 405 406
    # 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
407 408
    expire_root_ref_cache
    expire_emptiness_caches
409
    expire_exists_cache
410 411 412 413 414 415 416 417 418
  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
  end

Y
Yorick Peterse 已提交
419 420
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
421
    expire_cache
422
    expire_tags_cache
Y
Yorick Peterse 已提交
423 424 425 426 427 428 429
    expire_tag_count_cache
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
430 431
  end

432 433 434 435 436
  def before_import
    expire_emptiness_caches
    expire_exists_cache
  end

437 438 439
  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
440
    expire_exists_cache
441 442 443
  end

  # Runs code after a new commit has been pushed.
444 445
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
446 447 448 449
  end

  # Runs code after a new branch has been created.
  def after_create_branch
450
    expire_branches_cache
451
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
452
    expire_branch_count_cache
453 454
  end

455 456 457 458 459
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
  end

460 461 462
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
463
    expire_branch_count_cache
464
    expire_branches_cache
465 466
  end

467
  def method_missing(m, *args, &block)
468 469 470 471 472 473
    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
474 475
  end

476 477
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
478
  end
D
Dmitriy Zaporozhets 已提交
479 480

  def blob_at(sha, path)
481
    unless Gitlab::Git.blank_ref?(sha)
482
      Blob.decorate(Gitlab::Git::Blob.find(self, sha, path))
483
    end
D
Dmitriy Zaporozhets 已提交
484
  end
485

486 487 488 489
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

490
  def readme
491
    cache.fetch(:readme) { tree(:head).readme }
492
  end
493

494
  def version
495
    cache.fetch(:version) do
496
      tree(:head).blobs.find do |file|
497
        file.name.casecmp('version').zero?
498 499 500 501
      end
    end
  end

502
  def contribution_guide
503 504 505 506 507 508
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
509 510 511

  def changelog
    cache.fetch(:changelog) do
512
      file_on_head(/\A(changelog|history|changes|news)/i)
513
    end
514 515
  end

516
  def license_blob
517
    return nil unless head_exists?
518

519
    cache.fetch(:license_blob) do
520
      file_on_head(/\A(licen[sc]e|copying)(\..+|\z)/i)
521 522
    end
  end
Z
Zeger-Jan van de Weg 已提交
523

524
  def license_key
525
    return nil unless head_exists?
526 527

    cache.fetch(:license_key) do
528
      Licensee.license(path).try(:key)
529
    end
530 531
  end

532 533 534 535 536 537 538 539
  def gitignore
    return nil if !exists? || empty?

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

540
  def gitlab_ci_yml
541
    return nil unless head_exists?
542 543 544 545

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

552
  def head_commit
553 554 555 556 557
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
558 559 560 561
  end

  def tree(sha = :head, path = nil)
    if sha == :head
562 563 564 565 566
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
567 568 569 570
    end

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

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

D
Dmitriy Zaporozhets 已提交
575 576 577 578 579
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
580
  end
D
Dmitriy Zaporozhets 已提交
581 582 583 584 585 586 587 588

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
589
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
590 591 592 593 594 595 596
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
597 598

  def last_commit_for_path(sha, path)
599
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
600 601
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
602
  end
603

604
  def next_branch(name, opts = {})
P
P.S.V.R 已提交
605 606 607
    branch_ids = self.branch_names.map do |n|
      next 1 if n == name
      result = n.match(/\A#{name}-([0-9]+)\z/)
608 609 610
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
611
    highest_branch_id = branch_ids.max || 0
612

P
P.S.V.R 已提交
613 614 615
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
616 617
  end

618
  # Remove archives older than 2 hours
619 620
  def branches_sorted_by(value)
    case value
621 622
    when 'name'
      branches.sort_by(&:name)
623
    when 'updated_desc'
624 625 626
      branches.sort do |a, b|
        commit(b.target).committed_date <=> commit(a.target).committed_date
      end
627
    when 'updated_asc'
628 629 630 631 632 633 634
      branches.sort do |a, b|
        commit(a.target).committed_date <=> commit(b.target).committed_date
      end
    else
      branches
    end
  end
635

636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
  def tags_sorted_by(value)
    case value
    when 'name'
      # Would be better to use `sort_by` but `version_sorter` only exposes
      # `sort` and `rsort`
      VersionSorter.rsort(tag_names).map { |tag_name| find_tag(tag_name) }
    when 'updated_desc'
      tags_sorted_by_committed_date.reverse
    when 'updated_asc'
      tags_sorted_by_committed_date
    else
      tags
    end
  end

651
  def contributors
652
    commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
653

D
Dmitriy Zaporozhets 已提交
654
    commits.group_by(&:author_email).map do |email, commits|
655 656
      contributor = Gitlab::Contributor.new
      contributor.email = email
657

D
Dmitriy Zaporozhets 已提交
658
      commits.each do |commit|
659
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
660
          contributor.name = commit.author_name
661 662
        end

663
        contributor.commits += 1
664 665
      end

666 667
      contributor
    end
668
  end
D
Dmitriy Zaporozhets 已提交
669

670 671
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
672 673 674 675 676 677 678 679 680 681 682 683 684 685
    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 已提交
686

687 688 689
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
690

691 692
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
693
  end
694

695
  def local_branches
696
    @local_branches ||= raw_repository.local_branches
697 698
  end

699 700
  alias_method :branches, :local_branches

701 702 703 704 705
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
706
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
707 708
  end

S
Stan Hu 已提交
709
  def commit_dir(user, path, message, branch)
710
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
711 712 713 714 715 716 717 718
      committer = user_to_committer(user)
      options = {}
      options[:committer] = committer
      options[:author] = committer

      options[:commit] = {
        message: message,
        branch: ref,
719
        update_ref: false,
S
Stan Hu 已提交
720 721 722 723 724
      }

      raw_repository.mkdir(path, options)
    end
  end
725

S
Stan Hu 已提交
726 727 728
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
729 730 731 732 733 734
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
735
        update_ref: false,
736
      }
737

738 739
      options[:file] = {
        content: content,
S
Stan Hu 已提交
740 741
        path: path,
        update: update
742
      }
743

744 745
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
746 747
  end

748
  def update_file(user, path, content, branch:, previous_path:, message:)
749 750
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
751 752 753 754 755
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
756 757
        branch: ref,
        update_ref: false
758 759
      }

760
      options[:file] = {
761 762
        content: content,
        path: path,
763
        update: true
764 765
      }

766 767
      if previous_path
        options[:file][:previous_path] = previous_path
768
        Gitlab::Git::Blob.rename(raw_repository, options)
T
tiagonbotelho 已提交
769
      else
770
        Gitlab::Git::Blob.commit(raw_repository, options)
T
tiagonbotelho 已提交
771
      end
772 773 774
    end
  end

775
  def remove_file(user, path, message, branch)
776
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
777
      committer = user_to_committer(user)
778 779 780 781 782
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
783 784
        branch: ref,
        update_ref: false,
785
      }
786

787 788 789
      options[:file] = {
        path: path
      }
790

791 792
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
793 794
  end

S
Stan Hu 已提交
795
  def user_to_committer(user)
796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
    {
      email: user.email,
      name: user.name,
      time: Time.now
    }
  end

  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

814 815 816
  def merge(user, merge_request, options = {})
    our_commit = rugged.branches[merge_request.target_branch].target
    their_commit = rugged.lookup(merge_request.diff_head_sha)
817 818 819 820 821 822 823

    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?

824
    commit_with_hooks(user, merge_request.target_branch) do
825 826 827 828
      actual_options = options.merge(
        parents: [our_commit, their_commit],
        tree: merge_index.write_tree(rugged),
      )
829

830 831 832
      commit_id = Rugged::Commit.create(rugged, actual_options)
      merge_request.update(in_progress_merge_commit_sha: commit_id)
      commit_id
833
    end
834 835
  end

836
  def revert(user, commit, base_branch, revert_tree_id = nil)
837
    source_sha = find_branch(base_branch).target.sha
838
    revert_tree_id ||= check_revert_content(commit, base_branch)
839

840
    return false unless revert_tree_id
841

842
    commit_with_hooks(user, base_branch) do
843
      committer = user_to_committer(user)
844
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
845
        message: commit.revert_message,
846 847
        author: committer,
        committer: committer,
848
        tree: revert_tree_id,
849
        parents: [rugged.lookup(source_sha)])
850
    end
851 852
  end

P
P.S.V.R 已提交
853
  def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
854
    source_sha = find_branch(base_branch).target.sha
P
P.S.V.R 已提交
855 856 857 858
    cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)

    return false unless cherry_pick_tree_id

859
    commit_with_hooks(user, base_branch) do
P
P.S.V.R 已提交
860 861 862 863 864 865 866 867 868 869
      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,
870
        parents: [rugged.lookup(source_sha)])
P
P.S.V.R 已提交
871 872 873
    end
  end

874
  def check_revert_content(commit, base_branch)
875
    source_sha = find_branch(base_branch).target.sha
876
    args       = [commit.id, source_sha]
877
    args << { mainline: 1 } if commit.merge_commit?
878 879 880 881 882 883 884 885 886 887

    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 已提交
888
  def check_cherry_pick_content(commit, base_branch)
889
    source_sha = find_branch(base_branch).target.sha
P
P.S.V.R 已提交
890
    args       = [commit.id, source_sha]
891
    args << 1 if commit.merge_commit?
P
P.S.V.R 已提交
892 893 894 895 896 897 898 899 900 901

    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

902 903
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
904 905
  end

F
Florent (HP) 已提交
906 907 908 909 910
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
911
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
912 913 914 915 916
    else
      nil
    end
  end

S
Stan Hu 已提交
917
  def merge_base(first_commit_id, second_commit_id)
918 919
    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 已提交
920
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
921 922
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
923 924
  end

925 926 927 928
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end

929 930
  def search_files(query, ref)
    offset = 2
931
    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})
932 933 934
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

D
Dmitriy Zaporozhets 已提交
935
  def parse_search_result(result)
936 937
    ref = nil
    filename = nil
938
    basename = nil
939 940
    startline = 0

941
    result.each_line.each_with_index do |line, index|
942 943 944
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
945
        extname = Regexp.escape(File.extname(filename))
946
        basename = filename.sub(/#{extname}$/, '')
947 948 949 950
        break
      end
    end

951
    data = ""
952

953 954 955
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
956 957 958

    OpenStruct.new(
      filename: filename,
959
      basename: basename,
960 961 962 963 964 965
      ref: ref,
      startline: startline,
      data: data
    )
  end

966
  def fetch_ref(source_path, source_ref, target_ref)
967
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
968 969 970
    Gitlab::Popen.popen(args, path_to_repo)
  end

971
  def commit_with_hooks(current_user, branch)
972 973
    update_autocrlf_option

974 975
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
976
    target_branch = find_branch(branch)
977
    was_empty = empty?
978

979
    if !was_empty && target_branch
980
      oldrev = target_branch.target.id
981 982
    end

983 984
    # Make commit
    newrev = yield(ref)
985

986 987 988
    unless newrev
      raise CommitError.new('Failed to create commit')
    end
989

990 991 992 993
    GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
      if was_empty || !target_branch
        # Create branch
        rugged.references.create(ref, newrev)
994 995 996 997

        # If repo was empty expire cache
        after_create if was_empty
        after_create_branch
998 999
      else
        # Update head
1000
        current_head = find_branch(branch).target.id
1001

1002 1003 1004
        # Make sure target branch was not changed during pre-receive hook
        if current_head == oldrev
          rugged.references.update(ref, newrev)
1005
        else
1006
          raise CommitError.new('Commit was rejected because branch received new push')
1007 1008 1009
        end
      end
    end
1010 1011

    newrev
1012 1013
  end

1014 1015 1016 1017 1018
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

1019 1020 1021 1022
  def gitattribute(path, name)
    raw_repository.attributes(path)[name]
  end

1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
  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

1033
  def avatar
1034 1035
    return nil unless exists?

1036 1037 1038 1039 1040 1041 1042
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
        blob_at_branch('master', file)
      end
    end
  end

1043 1044
  private

1045
  def cache
1046
    @cache ||= RepositoryCache.new(path_with_namespace, @project.id)
1047
  end
1048 1049 1050 1051

  def head_exists?
    exists? && !empty? && !rugged.head_unborn?
  end
1052 1053 1054 1055

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

  def tags_sorted_by_committed_date
1058
    tags.sort_by { |tag| tag.target.committed_date }
1059
  end
D
Douwe Maan 已提交
1060 1061 1062 1063

  def keep_around_ref_name(sha)
    "refs/keep-around/#{sha}"
  end
1064
end