repository.rb 26.3 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
    @path_to_repo ||= File.expand_path(
42
      File.join(@project.repository_storage_path, path_with_namespace + ".git")
43
    )
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

L
Lin Jen-Shin 已提交
81
  def commit(ref = 'HEAD')
82
    return nil unless exists?
L
Lin Jen-Shin 已提交
83
    commit = Gitlab::Git::Commit.find(raw_repository, ref)
84
    commit = ::Commit.new(commit, @project) if commit
85
    commit
86
  rescue Rugged::OdbError
87
    nil
88 89
  end

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

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

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

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

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

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

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

  def find_tag(name)
133
    tags.find { |tag| tag.name == name }
134 135
  end

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

147
    after_create_branch
148
    find_branch(branch_name)
149 150
  end

151 152 153 154 155 156
  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
157

158 159
    options = { message: message, tagger: user_to_committer(user) } if message

160 161
    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
      rugged.tags.create(tag_name, target, options)
162
    end
163

164
    find_tag(tag_name)
165 166
  end

167
  def rm_branch(user, branch_name)
168
    before_remove_branch
169

170 171 172 173 174 175 176 177
    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
178

179
    after_remove_branch
180
    true
181 182
  end

183
  def rm_tag(tag_name)
Y
Yorick Peterse 已提交
184
    before_remove_tag
185

R
Robert Schilling 已提交
186 187 188 189 190 191
    begin
      rugged.tags.delete(tag_name)
      true
    rescue Rugged::ReferenceError
      false
    end
192 193
  end

194 195 196 197
  def ref_names
    branch_names + tag_names
  end

198
  def branch_names
P
Paco Guzman 已提交
199
    @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) }
200 201
  end

202 203 204 205
  def branch_exists?(branch_name)
    branch_names.include?(branch_name)
  end

206 207 208 209
  def ref_exists?(ref)
    rugged.references.exist?(ref)
  end

D
Douwe Maan 已提交
210 211 212 213
  # 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.
214 215 216 217 218 219 220 221 222 223 224 225
  def keep_around(sha)
    return unless sha && commit(sha)

    return if kept_around?(sha)

    rugged.references.create(keep_around_ref_name(sha), sha)
  end

  def kept_around?(sha)
    ref_exists?(keep_around_ref_name(sha))
  end

226
  def tag_names
227
    cache.fetch(:tag_names) { raw_repository.tag_names }
228 229
  end

230
  def commit_count
231
    cache.fetch(:commit_count) do
232
      begin
233
        raw_repository.commit_count(self.root_ref)
234 235 236
      rescue
        0
      end
237
    end
238 239
  end

Y
Yorick Peterse 已提交
240
  def branch_count
241
    @branch_count ||= cache.fetch(:branch_count) { branches.size }
Y
Yorick Peterse 已提交
242 243 244 245 246 247
  end

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

248 249 250
  # Return repo size in megabytes
  # Cached in redis
  def size
251
    cache.fetch(:size) { raw_repository.size }
252
  end
253

254
  def diverging_commit_counts(branch)
255
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
J
Jeff Stubler 已提交
256
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
257 258
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
259 260 261 262 263
      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)
264

265 266 267
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
268

269
  # Keys for data that can be affected for any commit push.
270
  def cache_keys
271
    %i(size commit_count
272
       readme version contribution_guide changelog
A
Alfredo Sumaran 已提交
273
       license_blob license_key gitignore)
274
  end
275

276 277 278 279 280
  # Keys for data on branch/tag operations.
  def cache_keys_for_branches_and_tags
    %i(branch_names tag_names branch_count tag_count)
  end

281
  def build_cache
282
    (cache_keys + cache_keys_for_branches_and_tags).each do |key|
283 284 285 286 287 288
      unless cache.exist?(key)
        send(key)
      end
    end
  end

D
Douwe Maan 已提交
289 290 291 292 293 294 295
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
P
Paco Guzman 已提交
296
    @branch_names = nil
297
    @local_branches = nil
D
Douwe Maan 已提交
298 299
  end

300
  def expire_cache(branch_name = nil, revision = nil)
301
    cache_keys.each do |key|
302 303
      cache.expire(key)
    end
304

305
    expire_branch_cache(branch_name)
306
    expire_avatar_cache(branch_name, revision)
307 308 309 310

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

313 314 315 316 317 318 319 320 321 322 323 324
  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}")
325
    end
D
Dmitriy Zaporozhets 已提交
326 327
  end

328 329 330 331 332
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

333 334 335 336 337 338 339 340
  # 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

341 342 343 344 345
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

Y
Yorick Peterse 已提交
346 347 348 349 350 351 352 353 354 355
  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

356 357 358 359
  def lookup_cache
    @lookup_cache ||= {}
  end

360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
  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

377 378 379 380 381 382 383 384
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
385 386
    expire_root_ref_cache
    expire_emptiness_caches
387 388
  end

389 390
  # Runs code just before a repository is deleted.
  def before_delete
391 392
    expire_exists_cache

393 394 395 396
    expire_cache if exists?

    expire_root_ref_cache
    expire_emptiness_caches
397
    expire_exists_cache
398 399 400 401 402 403 404 405 406
  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 已提交
407 408
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
409
    expire_cache
410
    expire_tags_cache
Y
Yorick Peterse 已提交
411 412 413 414 415 416 417
    expire_tag_count_cache
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
418 419
  end

420 421 422 423 424
  def before_import
    expire_emptiness_caches
    expire_exists_cache
  end

425 426 427
  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
428
    expire_exists_cache
429 430 431
  end

  # Runs code after a new commit has been pushed.
432 433
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
434 435 436 437
  end

  # Runs code after a new branch has been created.
  def after_create_branch
438
    expire_branches_cache
439
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
440
    expire_branch_count_cache
441 442
  end

443 444 445 446 447
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
  end

448 449 450
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
451
    expire_branch_count_cache
452
    expire_branches_cache
453 454
  end

455
  def method_missing(m, *args, &block)
456 457 458 459 460 461
    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
462 463
  end

464 465
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
466
  end
D
Dmitriy Zaporozhets 已提交
467 468

  def blob_at(sha, path)
469
    unless Gitlab::Git.blank_ref?(sha)
470
      Blob.decorate(Gitlab::Git::Blob.find(self, sha, path))
471
    end
D
Dmitriy Zaporozhets 已提交
472
  end
473

474 475 476 477
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

478
  def readme
479
    cache.fetch(:readme) { tree(:head).readme }
480
  end
481

482
  def version
483
    cache.fetch(:version) do
484
      tree(:head).blobs.find do |file|
485
        file.name.casecmp('version').zero?
486 487 488 489
      end
    end
  end

490
  def contribution_guide
491 492 493 494 495 496
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
497 498 499

  def changelog
    cache.fetch(:changelog) do
500
      file_on_head(/\A(changelog|history|changes|news)/i)
501
    end
502 503
  end

504
  def license_blob
505
    return nil unless head_exists?
506

507
    cache.fetch(:license_blob) do
508
      file_on_head(/\A(licen[sc]e|copying)(\..+|\z)/i)
509 510
    end
  end
Z
Zeger-Jan van de Weg 已提交
511

512
  def license_key
513
    return nil unless head_exists?
514 515

    cache.fetch(:license_key) do
516
      Licensee.license(path).try(:key)
517
    end
518 519
  end

520 521 522 523 524 525 526 527
  def gitignore
    return nil if !exists? || empty?

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

528
  def gitlab_ci_yml
529
    return nil unless head_exists?
530 531 532 533

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

540
  def head_commit
541 542 543 544 545
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
546 547 548 549
  end

  def tree(sha = :head, path = nil)
    if sha == :head
550 551 552 553 554
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
555 556 557 558
    end

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

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

D
Dmitriy Zaporozhets 已提交
563 564 565 566 567
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
568
  end
D
Dmitriy Zaporozhets 已提交
569 570 571 572 573 574 575 576

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
577
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
578 579 580 581 582 583 584
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
585 586

  def last_commit_for_path(sha, path)
587
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
588 589
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
590
  end
591

P
P.S.V.R 已提交
592 593 594 595
  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/)
596 597 598
      result[1].to_i if result
    end.compact

P
P.S.V.R 已提交
599
    highest_branch_id = branch_ids.max || 0
600

P
P.S.V.R 已提交
601 602 603
    return name if opts[:mild] && 0 == highest_branch_id

    "#{name}-#{highest_branch_id + 1}"
604 605
  end

606
  # Remove archives older than 2 hours
607 608 609 610 611 612 613 614 615 616 617 618 619 620
  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
621

622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
  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

637
  def contributors
638
    commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
639

D
Dmitriy Zaporozhets 已提交
640
    commits.group_by(&:author_email).map do |email, commits|
641 642
      contributor = Gitlab::Contributor.new
      contributor.email = email
643

D
Dmitriy Zaporozhets 已提交
644
      commits.each do |commit|
645
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
646
          contributor.name = commit.author_name
647 648
        end

649
        contributor.commits += 1
650 651
      end

652 653
      contributor
    end
654
  end
D
Dmitriy Zaporozhets 已提交
655

656 657
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
658 659 660 661 662 663 664 665 666 667 668 669 670 671
    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 已提交
672

673 674 675
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
676

677 678
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
679
  end
680

681 682 683 684
  def local_branches
    @local_branches ||= rugged.branches.each(:local).map do |branch|
      Gitlab::Git::Branch.new(branch.name, branch.target)
    end
685 686
  end

687 688
  alias_method :branches, :local_branches

689 690 691 692 693
  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
694
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
695 696
  end

S
Stan Hu 已提交
697
  def commit_dir(user, path, message, branch)
698
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
699 700 701 702 703 704 705 706 707 708 709 710 711
      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
712

S
Stan Hu 已提交
713 714 715
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
716 717 718 719 720 721 722
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
      }
723

724 725
      options[:file] = {
        content: content,
S
Stan Hu 已提交
726 727
        path: path,
        update: update
728
      }
729

730 731
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
732 733
  end

734 735 736 737 738 739 740 741
  def update_file(user, path, previous_path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
742
<<<<<<< 3824e8e1c4315bb3d1b2c1389f442d3b5e94f945
T
tiagonbotelho 已提交
743
        branch: ref
744 745
      }

746 747 748 749 750 751 752 753 754 755 756 757 758 759
=======
        branch: ref,
      }

      if previous_path
        options[:file] = {
          path: previous_path
        }


        Gitlab::Git::Blob.remove(raw_repository, options)
      end

>>>>>>> creates the update_file method in repository.rb and applies changes accordingly
760 761 762 763 764 765
      options[:file] = {
        content: content,
        path: path,
        update: update
      }

766
<<<<<<< 3824e8e1c4315bb3d1b2c1389f442d3b5e94f945
T
tiagonbotelho 已提交
767 768 769 770 771 772 773
      if previous_path
        options[:file].merge!(previous_path: previous_path)

        Gitlab::Git::Blob.rename(raw_repository, options)
      else
        Gitlab::Git::Blob.commit(raw_repository, options)
      end
774 775 776
    end
  end

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

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

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

S
Stan Hu 已提交
796
  def user_to_committer(user)
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
    {
      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

815
  def merge(user, source_sha, target_branch, options = {})
816 817 818 819 820 821 822 823 824
    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?

825 826 827 828 829 830
    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
      )
831

832
      Rugged::Commit.create(rugged, actual_options)
833
    end
834 835
  end

836 837
  def revert(user, commit, base_branch, revert_tree_id = nil)
    source_sha = find_branch(base_branch).target
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 |ref|
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)],
R
Rubén Dávila 已提交
850
        update_ref: ref)
851
    end
852 853
  end

P
P.S.V.R 已提交
854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875
  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

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

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

    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

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

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

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

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

927 928 929 930
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end

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

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

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

953
    data = ""
954

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

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

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

973
  def with_tmp_ref(oldrev = nil)
974 975 976
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

977
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
978 979 980 981
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
982 983 984 985 986 987
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
988 989
    update_autocrlf_option

990 991
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
992
    target_branch = find_branch(branch)
993
    was_empty = empty?
994

995 996
    if !was_empty && target_branch
      oldrev = target_branch.target
997 998
    end

999 1000 1001 1002 1003 1004 1005
    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
1006

1007
      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
1008
        if was_empty || !target_branch
1009 1010
          # Create branch
          rugged.references.create(ref, newrev)
1011
        else
1012 1013 1014 1015 1016 1017 1018 1019 1020
          # 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
1021 1022
        end
      end
1023 1024

      newrev
1025 1026 1027
    end
  end

1028 1029 1030 1031 1032
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

1033 1034 1035 1036
  def gitattribute(path, name)
    raw_repository.attributes(path)[name]
  end

1037 1038 1039 1040 1041 1042 1043 1044 1045 1046
  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

1047
  def avatar
1048 1049
    return nil unless exists?

1050 1051 1052 1053 1054 1055 1056
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
        blob_at_branch('master', file)
      end
    end
  end

1057 1058
  private

1059 1060 1061
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
1062 1063 1064 1065

  def head_exists?
    exists? && !empty? && !rugged.head_unborn?
  end
1066 1067 1068 1069

  def file_on_head(regex)
    tree(:head).blobs.find { |file| file.name =~ regex }
  end
1070 1071 1072 1073

  def tags_sorted_by_committed_date
    tags.sort_by { |tag| commit(tag.target).committed_date }
  end
D
Douwe Maan 已提交
1074 1075 1076 1077

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