repository.rb 22.4 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 15 16 17 18 19 20 21
  def self.clean_old_archives
    repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path

    return unless File.directory?(repository_downloads_path)

    Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
  end

22
  def initialize(path_with_namespace, project)
23
    @path_with_namespace = path_with_namespace
24
    @project = project
25
  end
26

27 28
  def raw_repository
    return nil unless path_with_namespace
29

30
    @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
31 32
  end

33 34 35 36
  def update_autocrlf_option
    raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
  end

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

44
  def exists?
45
    return @exists unless @exists.nil?
46

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

  def empty?
57 58 59
    return @empty unless @empty.nil?

    @empty = cache.fetch(:empty?) { raw_repository.empty? }
60 61
  end

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

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

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

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

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

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

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

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

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

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

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

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

143
    after_create_branch
144
    find_branch(branch_name)
145 146
  end

147
  def add_tag(tag_name, ref, message = nil)
148
    before_push_tag
149

150
    gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
151 152
  end

153
  def rm_branch(user, branch_name)
154
    before_remove_branch
155

156 157 158 159 160 161 162 163
    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
164

165
    after_remove_branch
166
    true
167 168
  end

169
  def rm_tag(tag_name)
Y
Yorick Peterse 已提交
170
    before_remove_tag
171

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

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

  def tag_names
185
    cache.fetch(:tag_names) { raw_repository.tag_names }
186 187
  end

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

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

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

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

212
  def diverging_commit_counts(branch)
213
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
J
Jeff Stubler 已提交
214
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
215 216
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
217 218 219 220 221
      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)
222

223 224 225
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
226

227
  def cache_keys
228
    %i(size branch_names tag_names commit_count
229 230
       readme version contribution_guide changelog license)
  end
231

232 233 234 235 236 237 238 239
  def build_cache
    cache_keys.each do |key|
      unless cache.exist?(key)
        send(key)
      end
    end
  end

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

  def expire_branches_cache
    cache.expire(:branch_names)
247
    @local_branches = nil
D
Douwe Maan 已提交
248 249
  end

250
  def expire_cache(branch_name = nil, revision = nil)
251
    cache_keys.each do |key|
252 253
      cache.expire(key)
    end
254

255
    expire_branch_cache(branch_name)
256
    expire_avatar_cache(branch_name, revision)
257 258 259 260

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

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

280 281 282 283 284
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

285 286 287 288 289 290 291 292
  # 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

293 294 295 296 297
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

Y
Yorick Peterse 已提交
298 299 300 301 302 303 304 305 306 307
  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

308 309 310 311
  def lookup_cache
    @lookup_cache ||= {}
  end

312 313 314 315
  def expire_branch_names
    cache.expire(:branch_names)
  end

316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
  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

333 334 335 336 337 338 339 340
  def expire_exists_cache
    cache.expire(:exists?)
    @exists = nil
  end

  # Runs code after a repository has been created.
  def after_create
    expire_exists_cache
341 342
    expire_root_ref_cache
    expire_emptiness_caches
343 344
  end

345 346
  # Runs code just before a repository is deleted.
  def before_delete
347 348
    expire_exists_cache

349 350 351 352
    expire_cache if exists?

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

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
374 375
  end

376 377 378 379 380
  def before_import
    expire_emptiness_caches
    expire_exists_cache
  end

381 382 383
  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
384
    expire_exists_cache
385 386 387
  end

  # Runs code after a new commit has been pushed.
388 389
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
390 391 392 393
  end

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

399 400 401 402 403
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
  end

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

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

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

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

430 431 432 433
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

434
  def readme
435
    cache.fetch(:readme) { tree(:head).readme }
436
  end
437

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

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

  def changelog
    cache.fetch(:changelog) do
      tree(:head).blobs.find do |file|
457
        file.name =~ /\A(changelog|history)/i
458 459
      end
    end
460 461
  end

462 463
  def license
    cache.fetch(:license) do
Z
Zeger-Jan van de Weg 已提交
464
      licenses =  tree(:head).blobs.find_all do |file|
465
                    file.name =~ /\A(copying|license|licence)/i
Z
Zeger-Jan van de Weg 已提交
466 467
                  end

468 469 470 471 472 473 474 475 476 477 478 479 480
      preferences = [
        /\Alicen[sc]e\z/i,        # LICENSE, LICENCE
        /\Alicen[sc]e\./i,        # LICENSE.md, LICENSE.txt
        /\Acopying\z/i,           # COPYING
        /\Acopying\.(?!lesser)/i, # COPYING.txt
        /Acopying.lesser/i        # COPYING.LESSER
      ]

      license = nil
      preferences.each do |r|
        license = licenses.find { |l| l.name =~ r }
        break if license
      end
Z
Zeger-Jan van de Weg 已提交
481

482
      license
483
    end
484 485
  end

486
  def gitlab_ci_yml
487
    return nil if !exists? || empty?
488 489 490 491

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

498
  def head_commit
499 500 501 502 503
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
504 505 506 507
  end

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

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

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

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

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

      if submodule
        submodule['url']
      end
    end
  end
543 544

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

550 551 552 553 554 555 556 557 558 559 560
  def next_patch_branch
    patch_branch_ids = self.branch_names.map do |n|
      result = n.match(/\Apatch-([0-9]+)\z/)
      result[1].to_i if result
    end.compact

    highest_patch_branch_id = patch_branch_ids.max || 0

    "patch-#{highest_patch_branch_id + 1}"
  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

761 762 763 764 765 766 767 768 769 770 771 772 773 774
  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

775 776
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
777 778
  end

F
Florent (HP) 已提交
779 780 781 782 783
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
784
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
785 786 787 788 789
    else
      nil
    end
  end

S
Stan Hu 已提交
790
  def merge_base(first_commit_id, second_commit_id)
791 792
    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 已提交
793
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
794 795
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
796 797
  end

798 799 800 801 802
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end


803 804
  def search_files(query, ref)
    offset = 2
805
    args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{Regexp.escape(query)} #{ref || root_ref})
806 807 808
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

D
Dmitriy Zaporozhets 已提交
809
  def parse_search_result(result)
810 811
    ref = nil
    filename = nil
812
    basename = nil
813 814
    startline = 0

815
    result.each_line.each_with_index do |line, index|
816 817 818
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
819 820
        extname = File.extname(filename)
        basename = filename.sub(/#{extname}$/, '')
821 822 823 824
        break
      end
    end

825
    data = ""
826

827 828 829
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
830 831 832

    OpenStruct.new(
      filename: filename,
833
      basename: basename,
834 835 836 837 838 839
      ref: ref,
      startline: startline,
      data: data
    )
  end

840
  def fetch_ref(source_path, source_ref, target_ref)
841
    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
842 843 844
    Gitlab::Popen.popen(args, path_to_repo)
  end

845
  def with_tmp_ref(oldrev = nil)
846 847 848
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

849
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
850 851 852 853
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
854 855 856 857 858 859
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
860 861
    update_autocrlf_option

862 863
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
864
    target_branch = find_branch(branch)
865
    was_empty = empty?
866

867 868
    if !was_empty && target_branch
      oldrev = target_branch.target
869 870
    end

871 872 873 874 875 876 877
    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
878

879
      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
880
        if was_empty || !target_branch
881 882
          # Create branch
          rugged.references.create(ref, newrev)
883
        else
884 885 886 887 888 889 890 891 892
          # 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
893 894
        end
      end
895 896

      newrev
897 898 899
    end
  end

900 901 902 903 904
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

905
  def main_language
J
Jeroen Bobbeldijk 已提交
906
    return if empty? || rugged.head_unborn?
907 908

    Linguist::Repository.new(rugged, rugged.head.target_id).language
909 910
  end

911
  def avatar
912 913
    return nil unless exists?

914 915 916 917 918 919 920
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
        blob_at_branch('master', file)
      end
    end
  end

921 922
  private

923 924 925
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
926
end