repository.rb 23.8 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(user, tag_name, ref, message = nil)
150
    before_push_tag
151

152 153 154 155 156 157 158 159
    options = { message: message, tagger: user_to_committer(user) } if message

    tag = rugged.tags.create(tag_name, ref, options)
    if tag.annotated?
      Gitlab::Git::Tag.new(tag_name, ref, tag.annotation.message)
    else
      Gitlab::Git::Tag.new(tag_name, ref)
    end
160 161
  end

162
  def rm_branch(user, branch_name)
163
    before_remove_branch
164

165 166 167 168 169 170 171 172
    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
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
  def branch_names
190
    cache.fetch(:branch_names) { branches.map(&:name) }
191 192 193
  end

  def tag_names
194
    cache.fetch(:tag_names) { raw_repository.tag_names }
195 196
  end

197
  def commit_count
198
    cache.fetch(:commit_count) do
199
      begin
200
        raw_repository.commit_count(self.root_ref)
201 202 203
      rescue
        0
      end
204
    end
205 206
  end

Y
Yorick Peterse 已提交
207
  def branch_count
208
    @branch_count ||= cache.fetch(:branch_count) { branches.size }
Y
Yorick Peterse 已提交
209 210 211 212 213 214
  end

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

215 216 217
  # Return repo size in megabytes
  # Cached in redis
  def size
218
    cache.fetch(:size) { raw_repository.size }
219
  end
220

221
  def diverging_commit_counts(branch)
222
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
J
Jeff Stubler 已提交
223
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
224 225
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
226 227 228 229 230
      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)
231

232 233 234
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
235

236
  def cache_keys
237
    %i(size branch_names tag_names commit_count
238 239
       readme version contribution_guide changelog
       license_blob license_key)
240
  end
241

242 243 244 245 246 247 248 249
  def build_cache
    cache_keys.each do |key|
      unless cache.exist?(key)
        send(key)
      end
    end
  end

D
Douwe Maan 已提交
250 251 252 253 254 255 256
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
257
    @local_branches = nil
D
Douwe Maan 已提交
258 259
  end

260
  def expire_cache(branch_name = nil, revision = nil)
261
    cache_keys.each do |key|
262 263
      cache.expire(key)
    end
264

265
    expire_branch_cache(branch_name)
266
    expire_avatar_cache(branch_name, revision)
267 268 269 270

    # This ensures this particular cache is flushed after the first commit to a
    # new repository.
    expire_emptiness_caches if empty?
271 272
    expire_branch_count_cache
    expire_tag_count_cache
273
  end
274

275 276 277 278 279 280 281 282 283 284 285 286
  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}")
287
    end
D
Dmitriy Zaporozhets 已提交
288 289
  end

290 291 292 293 294
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

295 296 297 298 299 300 301 302
  # 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

303 304 305 306 307
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

Y
Yorick Peterse 已提交
308 309 310 311 312 313 314 315 316 317
  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

318 319 320 321
  def lookup_cache
    @lookup_cache ||= {}
  end

322 323 324 325
  def expire_branch_names
    cache.expire(:branch_names)
  end

326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
  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

343 344 345 346 347 348 349 350
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
351 352
    expire_root_ref_cache
    expire_emptiness_caches
353 354
  end

355 356
  # Runs code just before a repository is deleted.
  def before_delete
357 358
    expire_exists_cache

359 360 361 362
    expire_cache if exists?

    expire_root_ref_cache
    expire_emptiness_caches
363
    expire_exists_cache
364 365 366 367 368 369 370 371 372
  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 已提交
373 374
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
375
    expire_cache
376
    expire_tags_cache
Y
Yorick Peterse 已提交
377 378 379 380 381 382 383
    expire_tag_count_cache
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
384 385
  end

386 387 388 389 390
  def before_import
    expire_emptiness_caches
    expire_exists_cache
  end

391 392 393
  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
394
    expire_exists_cache
395 396 397
  end

  # Runs code after a new commit has been pushed.
398 399
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
400 401 402 403
  end

  # Runs code after a new branch has been created.
  def after_create_branch
404
    expire_branches_cache
405
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
406
    expire_branch_count_cache
407 408
  end

409 410 411 412 413
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
  end

414 415 416
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
417
    expire_branch_count_cache
418
    expire_branches_cache
419 420
  end

421
  def method_missing(m, *args, &block)
422 423 424 425 426 427
    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
428 429
  end

430 431
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
432
  end
D
Dmitriy Zaporozhets 已提交
433 434

  def blob_at(sha, path)
435 436 437
    unless Gitlab::Git.blank_ref?(sha)
      Gitlab::Git::Blob.find(self, sha, path)
    end
D
Dmitriy Zaporozhets 已提交
438
  end
439

440 441 442 443
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

444
  def readme
445
    cache.fetch(:readme) { tree(:head).readme }
446
  end
447

448
  def version
449
    cache.fetch(:version) do
450 451 452 453 454 455
      tree(:head).blobs.find do |file|
        file.name.downcase == 'version'
      end
    end
  end

456
  def contribution_guide
457 458 459 460 461 462
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
463 464 465 466

  def changelog
    cache.fetch(:changelog) do
      tree(:head).blobs.find do |file|
467
        file.name =~ /\A(changelog|history|changes|news)/i
468 469
      end
    end
470 471
  end

472 473
  def license_blob
    return nil if !exists? || empty?
474

475
    cache.fetch(:license_blob) do
476 477
      tree(:head).blobs.find do |file|
        file.name =~ /\A(licen[sc]e|copying)(\..+|\z)/i
478
      end
479 480
    end
  end
Z
Zeger-Jan van de Weg 已提交
481

482 483 484 485
  def license_key
    return nil if !exists? || empty?

    cache.fetch(:license_key) do
486
      Licensee.license(path).try(:key)
487
    end
488 489
  end

490
  def gitlab_ci_yml
491
    return nil if !exists? || empty?
492 493 494 495

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

502
  def head_commit
503 504 505 506 507
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
508 509 510 511
  end

  def tree(sha = :head, path = nil)
    if sha == :head
512 513 514 515 516
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
517 518 519 520
    end

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

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

D
Dmitriy Zaporozhets 已提交
525 526 527 528 529
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
530
  end
D
Dmitriy Zaporozhets 已提交
531 532 533 534 535 536 537 538

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
539
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
540 541 542 543 544 545 546
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
547 548

  def last_commit_for_path(sha, path)
549
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
550 551
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
552
  end
553

P
P.S.V.R 已提交
554 555 556 557
  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/)
558 559 560
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
561
    highest_branch_id = branch_ids.max || 0
562

P
P.S.V.R 已提交
563 564 565
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
566 567
  end

568
  # Remove archives older than 2 hours
569 570 571 572 573 574 575 576 577 578 579 580 581 582
  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
583 584

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

D
Dmitriy Zaporozhets 已提交
587
    commits.group_by(&:author_email).map do |email, commits|
588 589
      contributor = Gitlab::Contributor.new
      contributor.email = email
590

D
Dmitriy Zaporozhets 已提交
591
      commits.each do |commit|
592
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
593
          contributor.name = commit.author_name
594 595
        end

596
        contributor.commits += 1
597 598
      end

599 600
      contributor
    end
601
  end
D
Dmitriy Zaporozhets 已提交
602 603

  def blob_for_diff(commit, diff)
604
    blob_at(commit.id, diff.file_path)
D
Dmitriy Zaporozhets 已提交
605 606 607 608 609 610 611
  end

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

613 614
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
615 616 617 618 619 620 621 622 623 624 625 626 627 628
    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 已提交
629

630 631 632
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
633

634 635
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
636
  end
637

638 639 640 641
  def local_branches
    @local_branches ||= rugged.branches.each(:local).map do |branch|
      Gitlab::Git::Branch.new(branch.name, branch.target)
    end
642 643
  end

644 645
  alias_method :branches, :local_branches

646 647 648 649 650
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
651
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
652 653
  end

S
Stan Hu 已提交
654
  def commit_dir(user, path, message, branch)
655
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
656 657 658 659 660 661 662 663 664 665 666 667 668
      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
669

S
Stan Hu 已提交
670 671 672
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
673 674 675 676 677 678 679
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
      }
680

681 682
      options[:file] = {
        content: content,
S
Stan Hu 已提交
683 684
        path: path,
        update: update
685
      }
686

687 688
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
689 690
  end

691
  def remove_file(user, path, message, branch)
692
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
693
      committer = user_to_committer(user)
694 695 696 697 698 699 700
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref
      }
701

702 703 704
      options[:file] = {
        path: path
      }
705

706 707
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
708 709
  end

S
Stan Hu 已提交
710
  def user_to_committer(user)
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
    {
      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

729
  def merge(user, source_sha, target_branch, options = {})
730 731 732 733 734 735 736 737 738
    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?

739 740 741 742 743 744
    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
      )
745

746
      Rugged::Commit.create(rugged, actual_options)
747
    end
748 749
  end

750 751
  def revert(user, commit, base_branch, revert_tree_id = nil)
    source_sha = find_branch(base_branch).target
752
    revert_tree_id ||= check_revert_content(commit, base_branch)
753

754
    return false unless revert_tree_id
755

756
    commit_with_hooks(user, base_branch) do |ref|
757
      committer = user_to_committer(user)
758
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
759
        message: commit.revert_message,
760 761
        author: committer,
        committer: committer,
762
        tree: revert_tree_id,
763
        parents: [rugged.lookup(source_sha)],
R
Rubén Dávila 已提交
764
        update_ref: ref)
765
    end
766 767
  end

P
P.S.V.R 已提交
768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
  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

790 791 792 793 794 795 796 797 798 799 800 801 802 803
  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 已提交
804 805 806 807 808 809 810 811 812 813 814 815 816 817
  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

818 819
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
820 821
  end

F
Florent (HP) 已提交
822 823 824 825 826
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
827
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
828 829 830 831 832
    else
      nil
    end
  end

S
Stan Hu 已提交
833
  def merge_base(first_commit_id, second_commit_id)
834 835
    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 已提交
836
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
837 838
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
839 840
  end

841 842 843 844 845
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end


846 847
  def search_files(query, ref)
    offset = 2
848
    args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{Regexp.escape(query)} #{ref || root_ref})
849 850 851
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

D
Dmitriy Zaporozhets 已提交
852
  def parse_search_result(result)
853 854
    ref = nil
    filename = nil
855
    basename = nil
856 857
    startline = 0

858
    result.each_line.each_with_index do |line, index|
859 860 861
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
862 863
        extname = File.extname(filename)
        basename = filename.sub(/#{extname}$/, '')
864 865 866 867
        break
      end
    end

868
    data = ""
869

870 871 872
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
873 874 875

    OpenStruct.new(
      filename: filename,
876
      basename: basename,
877 878 879 880 881 882
      ref: ref,
      startline: startline,
      data: data
    )
  end

883
  def fetch_ref(source_path, source_ref, target_ref)
884
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
885 886 887
    Gitlab::Popen.popen(args, path_to_repo)
  end

888
  def with_tmp_ref(oldrev = nil)
889 890 891
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

892
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
893 894 895 896
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
897 898 899 900 901 902
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
903 904
    update_autocrlf_option

905 906
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
907
    target_branch = find_branch(branch)
908
    was_empty = empty?
909

910 911
    if !was_empty && target_branch
      oldrev = target_branch.target
912 913
    end

914 915 916 917 918 919 920
    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
921

922
      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
923
        if was_empty || !target_branch
924 925
          # Create branch
          rugged.references.create(ref, newrev)
926
        else
927 928 929 930 931 932 933 934 935
          # 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
936 937
        end
      end
938 939

      newrev
940 941 942
    end
  end

943 944 945 946 947
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

948 949 950 951 952 953 954 955 956 957
  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

958
  def main_language
J
Jeroen Bobbeldijk 已提交
959
    return if empty? || rugged.head_unborn?
960 961

    Linguist::Repository.new(rugged, rugged.head.target_id).language
962 963
  end

964
  def avatar
965 966
    return nil unless exists?

967 968 969 970 971 972 973
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
        blob_at_branch('master', file)
      end
    end
  end

974 975
  private

976 977 978
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
979
end