repository.rb 23.6 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

J
Jacob Vosmaer 已提交
14
  def self.clean_old_archives
15 16
    Gitlab::Metrics.measure(:clean_old_archives) do
      repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
J
Jacob Vosmaer 已提交
17

18
      return unless File.directory?(repository_downloads_path)
J
Jacob Vosmaer 已提交
19

20 21
      Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
    end
J
Jacob Vosmaer 已提交
22 23
  end

24
  def initialize(path_with_namespace, project)
25
    @path_with_namespace = path_with_namespace
26
    @project = project
27
  end
28

29 30
  def raw_repository
    return nil unless path_with_namespace
31

32
    @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
33 34
  end

35 36 37 38
  def update_autocrlf_option
    raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
  end

39
  # Return absolute path to repository
40
  def path_to_repo
41 42 43
    @path_to_repo ||= File.expand_path(
      File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git")
    )
44 45
  end

46
  def exists?
47
    return @exists unless @exists.nil?
48

49 50 51 52 53 54 55
    @exists = cache.fetch(:exists?) do
      begin
        raw_repository && raw_repository.rugged ? true : false
      rescue Gitlab::Git::Repository::NoRepository
        false
      end
    end
56 57 58
  end

  def empty?
59 60 61
    return @empty unless @empty.nil?

    @empty = cache.fetch(:empty?) { raw_repository.empty? }
62 63
  end

64 65 66 67 68 69 70 71 72 73
  #
  # 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?
74 75 76
    return @has_visible_content unless @has_visible_content.nil?

    @has_visible_content = cache.fetch(:has_visible_content?) do
77
      branch_count > 0
78
    end
79 80
  end

81
  def commit(id = 'HEAD')
82
    return nil unless exists?
83
    commit = Gitlab::Git::Commit.find(raw_repository, id)
84
    commit = Commit.new(commit, @project) if commit
85
    commit
86
  rescue Rugged::OdbError
87
    nil
88 89
  end

D
Dmitriy Zaporozhets 已提交
90
  def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
91
    options = {
92 93 94 95 96
      repo: raw_repository,
      ref: ref,
      path: path,
      limit: limit,
      offset: offset,
97 98
      # --follow doesn't play well with --skip. See:
      # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
99 100
      follow: false,
      skip_merges: skip_merges
101 102 103
    }

    commits = Gitlab::Git::Commit.where(options)
104
    commits = Commit.decorate(commits, @project) if commits.present?
105 106 107
    commits
  end

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

114 115 116
  def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
    ref ||= root_ref

117
    # Limited to 1000 commits for now, could be parameterized?
118 119
    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?
120

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

126
  def find_branch(name)
127
    raw_repository.branches.find { |branch| branch.name == name }
128 129 130
  end

  def find_tag(name)
131
    raw_repository.tags.find { |tag| tag.name == name }
132 133
  end

134 135 136 137 138 139 140 141 142 143
  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
144

145
    after_create_branch
146
    find_branch(branch_name)
147 148
  end

149
  def add_tag(tag_name, ref, message = nil)
150
    before_push_tag
151

152
    gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
153 154
  end

155
  def rm_branch(user, branch_name)
156
    before_remove_branch
157

158 159 160 161 162 163 164 165
    branch = find_branch(branch_name)
    oldrev = branch.try(:target)
    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
166

167
    after_remove_branch
168
    true
169 170
  end

171
  def rm_tag(tag_name)
Y
Yorick Peterse 已提交
172
    before_remove_tag
173

R
Robert Schilling 已提交
174 175 176 177 178 179
    begin
      rugged.tags.delete(tag_name)
      true
    rescue Rugged::ReferenceError
      false
    end
180 181
  end

182
  def branch_names
183
    cache.fetch(:branch_names) { branches.map(&:name) }
184 185 186
  end

  def tag_names
187
    cache.fetch(:tag_names) { raw_repository.tag_names }
188 189
  end

190
  def commit_count
191
    cache.fetch(:commit_count) do
192
      begin
193
        raw_repository.commit_count(self.root_ref)
194 195 196
      rescue
        0
      end
197
    end
198 199
  end

Y
Yorick Peterse 已提交
200
  def branch_count
201
    @branch_count ||= cache.fetch(:branch_count) { branches.size }
Y
Yorick Peterse 已提交
202 203 204 205 206 207
  end

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

208 209 210
  # Return repo size in megabytes
  # Cached in redis
  def size
211
    cache.fetch(:size) { raw_repository.size }
212
  end
213

214
  def diverging_commit_counts(branch)
215
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
J
Jeff Stubler 已提交
216
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
217 218
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
219 220 221 222 223
      number_commits_behind = raw_repository.
        count_commits_between(branch.target, root_ref_hash)

      number_commits_ahead = raw_repository.
        count_commits_between(root_ref_hash, branch.target)
224

225 226 227
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
228

229
  def cache_keys
230
    %i(size branch_names tag_names commit_count
231 232
       readme version contribution_guide changelog
       license_blob license_key)
233
  end
234

235 236 237 238 239 240 241 242
  def build_cache
    cache_keys.each do |key|
      unless cache.exist?(key)
        send(key)
      end
    end
  end

D
Douwe Maan 已提交
243 244 245 246 247 248 249
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
250
    @local_branches = nil
D
Douwe Maan 已提交
251 252
  end

253
  def expire_cache(branch_name = nil, revision = nil)
254
    cache_keys.each do |key|
255 256
      cache.expire(key)
    end
257

258
    expire_branch_cache(branch_name)
259
    expire_avatar_cache(branch_name, revision)
260 261 262 263

    # This ensures this particular cache is flushed after the first commit to a
    # new repository.
    expire_emptiness_caches if empty?
264 265
    expire_branch_count_cache
    expire_tag_count_cache
266
  end
267

268 269 270 271 272 273 274 275 276 277 278 279
  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}")
280
    end
D
Dmitriy Zaporozhets 已提交
281 282
  end

283 284 285 286 287
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

288 289 290 291 292 293 294 295
  # 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

296 297 298 299 300
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

Y
Yorick Peterse 已提交
301 302 303 304 305 306 307 308 309 310
  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

311 312 313 314
  def lookup_cache
    @lookup_cache ||= {}
  end

315 316 317 318
  def expire_branch_names
    cache.expire(:branch_names)
  end

319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
  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)
      return unless commit.diffs.
        any? { |diff| AVATAR_FILES.include?(diff.new_path) }
    end

    cache.expire(:avatar)

    @avatar = nil
  end

336 337 338 339 340 341 342 343
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
344 345
    expire_root_ref_cache
    expire_emptiness_caches
346 347
  end

348 349
  # Runs code just before a repository is deleted.
  def before_delete
350 351
    expire_exists_cache

352 353 354 355
    expire_cache if exists?

    expire_root_ref_cache
    expire_emptiness_caches
356
    expire_exists_cache
357 358 359 360 361 362 363 364 365
  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 已提交
366 367
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
368
    expire_cache
369
    expire_tags_cache
Y
Yorick Peterse 已提交
370 371 372 373 374 375 376
    expire_tag_count_cache
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
377 378
  end

379 380 381 382 383
  def before_import
    expire_emptiness_caches
    expire_exists_cache
  end

384 385 386
  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
387
    expire_exists_cache
388 389 390
  end

  # Runs code after a new commit has been pushed.
391 392
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
393 394 395 396
  end

  # Runs code after a new branch has been created.
  def after_create_branch
397
    expire_branches_cache
398
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
399
    expire_branch_count_cache
400 401
  end

402 403 404 405 406
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
  end

407 408 409
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
410
    expire_branch_count_cache
411
    expire_branches_cache
412 413
  end

414
  def method_missing(m, *args, &block)
415 416 417 418 419 420
    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
421 422
  end

423 424
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
425
  end
D
Dmitriy Zaporozhets 已提交
426 427

  def blob_at(sha, path)
428 429 430
    unless Gitlab::Git.blank_ref?(sha)
      Gitlab::Git::Blob.find(self, sha, path)
    end
D
Dmitriy Zaporozhets 已提交
431
  end
432

433 434 435 436
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

437
  def readme
438
    cache.fetch(:readme) { tree(:head).readme }
439
  end
440

441
  def version
442
    cache.fetch(:version) do
443 444 445 446 447 448
      tree(:head).blobs.find do |file|
        file.name.downcase == 'version'
      end
    end
  end

449
  def contribution_guide
450 451 452 453 454 455
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
456 457 458 459

  def changelog
    cache.fetch(:changelog) do
      tree(:head).blobs.find do |file|
460
        file.name =~ /\A(changelog|history|changes|news)/i
461 462
      end
    end
463 464
  end

465 466
  def license_blob
    return nil if !exists? || empty?
467

468
    cache.fetch(:license_blob) do
469 470
      tree(:head).blobs.find do |file|
        file.name =~ /\A(licen[sc]e|copying)(\..+|\z)/i
471
      end
472 473
    end
  end
Z
Zeger-Jan van de Weg 已提交
474

475 476 477 478
  def license_key
    return nil if !exists? || empty?

    cache.fetch(:license_key) do
479
      Licensee.license(path).try(:key)
480
    end
481 482
  end

483
  def gitlab_ci_yml
484
    return nil if !exists? || empty?
485 486 487 488

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

495
  def head_commit
496 497 498 499 500
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
501 502 503 504
  end

  def tree(sha = :head, path = nil)
    if sha == :head
505 506 507 508 509
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
510 511 512 513
    end

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

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

D
Dmitriy Zaporozhets 已提交
518 519 520 521 522
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
523
  end
D
Dmitriy Zaporozhets 已提交
524 525 526 527 528 529 530 531

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
532
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
533 534 535 536 537 538 539
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
540 541

  def last_commit_for_path(sha, path)
542
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
543 544
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
545
  end
546

P
P.S.V.R 已提交
547 548 549 550
  def next_branch(name, opts={})
    branch_ids = self.branch_names.map do |n|
      next 1 if n == name
      result = n.match(/\A#{name}-([0-9]+)\z/)
551 552 553
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
554
    highest_branch_id = branch_ids.max || 0
555

P
P.S.V.R 已提交
556 557 558
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
559 560
  end

561
  # Remove archives older than 2 hours
562 563 564 565 566 567 568 569 570 571 572 573 574 575
  def branches_sorted_by(value)
    case value
    when 'recently_updated'
      branches.sort do |a, b|
        commit(b.target).committed_date <=> commit(a.target).committed_date
      end
    when 'last_updated'
      branches.sort do |a, b|
        commit(a.target).committed_date <=> commit(b.target).committed_date
      end
    else
      branches
    end
  end
576 577

  def contributors
D
Dmitriy Zaporozhets 已提交
578
    commits = self.commits(nil, nil, 2000, 0, true)
579

D
Dmitriy Zaporozhets 已提交
580
    commits.group_by(&:author_email).map do |email, commits|
581 582
      contributor = Gitlab::Contributor.new
      contributor.email = email
583

D
Dmitriy Zaporozhets 已提交
584
      commits.each do |commit|
585
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
586
          contributor.name = commit.author_name
587 588
        end

589
        contributor.commits += 1
590 591
      end

592 593
      contributor
    end
594
  end
D
Dmitriy Zaporozhets 已提交
595 596

  def blob_for_diff(commit, diff)
597
    blob_at(commit.id, diff.file_path)
D
Dmitriy Zaporozhets 已提交
598 599 600 601 602 603 604
  end

  def prev_blob_for_diff(commit, diff)
    if commit.parent_id
      blob_at(commit.parent_id, diff.old_path)
    end
  end
605

606 607
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
608 609 610 611 612 613 614 615 616 617 618 619 620 621
    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 已提交
622

623 624 625
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
626

627 628
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
629
  end
630

631 632 633 634
  def local_branches
    @local_branches ||= rugged.branches.each(:local).map do |branch|
      Gitlab::Git::Branch.new(branch.name, branch.target)
    end
635 636
  end

637 638
  alias_method :branches, :local_branches

639 640 641 642 643
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
644
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
645 646
  end

S
Stan Hu 已提交
647
  def commit_dir(user, path, message, branch)
648
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
649 650 651 652 653 654 655 656 657 658 659 660 661
      committer = user_to_committer(user)
      options = {}
      options[:committer] = committer
      options[:author] = committer

      options[:commit] = {
        message: message,
        branch: ref,
      }

      raw_repository.mkdir(path, options)
    end
  end
662

S
Stan Hu 已提交
663 664 665
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
666 667 668 669 670 671 672
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
      }
673

674 675
      options[:file] = {
        content: content,
S
Stan Hu 已提交
676 677
        path: path,
        update: update
678
      }
679

680 681
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
682 683
  end

684
  def remove_file(user, path, message, branch)
685
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
686
      committer = user_to_committer(user)
687 688 689 690 691 692 693
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref
      }
694

695 696 697
      options[:file] = {
        path: path
      }
698

699 700
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
701 702
  end

S
Stan Hu 已提交
703
  def user_to_committer(user)
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
    {
      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

722
  def merge(user, source_sha, target_branch, options = {})
723 724 725 726 727 728 729 730 731
    our_commit = rugged.branches[target_branch].target
    their_commit = rugged.lookup(source_sha)

    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?

732 733 734 735 736 737
    commit_with_hooks(user, target_branch) do |ref|
      actual_options = options.merge(
        parents: [our_commit, their_commit],
        tree: merge_index.write_tree(rugged),
        update_ref: ref
      )
738

739
      Rugged::Commit.create(rugged, actual_options)
740
    end
741 742
  end

743 744
  def revert(user, commit, base_branch, revert_tree_id = nil)
    source_sha = find_branch(base_branch).target
745
    revert_tree_id ||= check_revert_content(commit, base_branch)
746

747
    return false unless revert_tree_id
748

749
    commit_with_hooks(user, base_branch) do |ref|
750
      committer = user_to_committer(user)
751
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
752
        message: commit.revert_message,
753 754
        author: committer,
        committer: committer,
755
        tree: revert_tree_id,
756
        parents: [rugged.lookup(source_sha)],
R
Rubén Dávila 已提交
757
        update_ref: ref)
758
    end
759 760
  end

P
P.S.V.R 已提交
761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
  def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
    source_sha = find_branch(base_branch).target
    cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)

    return false unless cherry_pick_tree_id

    commit_with_hooks(user, base_branch) do |ref|
      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,
        parents: [rugged.lookup(source_sha)],
        update_ref: ref)
    end
  end

783 784 785 786 787 788 789 790 791 792 793 794 795 796
  def check_revert_content(commit, base_branch)
    source_sha = find_branch(base_branch).target
    args       = [commit.id, source_sha]
    args       << { mainline: 1 } if commit.merge_commit?

    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 已提交
797 798 799 800 801 802 803 804 805 806 807 808 809 810
  def check_cherry_pick_content(commit, base_branch)
    source_sha = find_branch(base_branch).target
    args       = [commit.id, source_sha]
    args       << 1 if commit.merge_commit?

    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

811 812
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
813 814
  end

F
Florent (HP) 已提交
815 816 817 818 819
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
820
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
821 822 823 824 825
    else
      nil
    end
  end

S
Stan Hu 已提交
826
  def merge_base(first_commit_id, second_commit_id)
827 828
    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 已提交
829
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
830 831
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
832 833
  end

834 835 836 837 838
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end


839 840
  def search_files(query, ref)
    offset = 2
841
    args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{Regexp.escape(query)} #{ref || root_ref})
842 843 844
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

D
Dmitriy Zaporozhets 已提交
845
  def parse_search_result(result)
846 847
    ref = nil
    filename = nil
848
    basename = nil
849 850
    startline = 0

851
    result.each_line.each_with_index do |line, index|
852 853 854
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
855 856
        extname = File.extname(filename)
        basename = filename.sub(/#{extname}$/, '')
857 858 859 860
        break
      end
    end

861
    data = ""
862

863 864 865
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
866 867 868

    OpenStruct.new(
      filename: filename,
869
      basename: basename,
870 871 872 873 874 875
      ref: ref,
      startline: startline,
      data: data
    )
  end

876
  def fetch_ref(source_path, source_ref, target_ref)
877
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
878 879 880
    Gitlab::Popen.popen(args, path_to_repo)
  end

881
  def with_tmp_ref(oldrev = nil)
882 883 884
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

885
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
886 887 888 889
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
890 891 892 893 894 895
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
896 897
    update_autocrlf_option

898 899
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
900
    target_branch = find_branch(branch)
901
    was_empty = empty?
902

903 904
    if !was_empty && target_branch
      oldrev = target_branch.target
905 906
    end

907 908 909 910 911 912 913
    with_tmp_ref(oldrev) do |tmp_ref|
      # Make commit in tmp ref
      newrev = yield(tmp_ref)

      unless newrev
        raise CommitError.new('Failed to create commit')
      end
914

915
      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
916
        if was_empty || !target_branch
917 918
          # Create branch
          rugged.references.create(ref, newrev)
919
        else
920 921 922 923 924 925 926 927 928
          # Update head
          current_head = find_branch(branch).target

          # Make sure target branch was not changed during pre-receive hook
          if current_head == oldrev
            rugged.references.update(ref, newrev)
          else
            raise CommitError.new('Commit was rejected because branch received new push')
          end
929 930
        end
      end
931 932

      newrev
933 934 935
    end
  end

936 937 938 939 940
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

941 942 943 944 945 946 947 948 949 950
  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

951
  def main_language
J
Jeroen Bobbeldijk 已提交
952
    return if empty? || rugged.head_unborn?
953 954

    Linguist::Repository.new(rugged, rugged.head.target_id).language
955 956
  end

957
  def avatar
958 959
    return nil unless exists?

960 961 962 963 964 965 966
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
        blob_at_branch('master', file)
      end
    end
  end

967 968
  private

969 970 971
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
972
end