repository.rb 21.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 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 46
    return false unless raw_repository

47 48 49 50
    raw_repository.rugged
    true
  rescue Gitlab::Git::Repository::NoRepository
    false
51 52 53
  end

  def empty?
54 55 56
    return @empty unless @empty.nil?

    @empty = cache.fetch(:empty?) { raw_repository.empty? }
57 58
  end

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

    @has_visible_content = cache.fetch(:has_visible_content?) do
      raw_repository.branch_count > 0
    end
74 75
  end

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

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

    commits = Gitlab::Git::Commit.where(options)
99
    commits = Commit.decorate(commits, @project) if commits.present?
100 101 102
    commits
  end

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

109 110 111
  def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
    ref ||= root_ref

112
    # Limited to 1000 commits for now, could be parameterized?
113 114
    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?
115

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

121
  def find_branch(name)
122
    raw_repository.branches.find { |branch| branch.name == name }
123 124 125
  end

  def find_tag(name)
126
    raw_repository.tags.find { |tag| tag.name == name }
127 128
  end

129 130 131 132 133 134 135 136 137 138
  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
139

140
    after_create_branch
141
    find_branch(branch_name)
142 143
  end

144
  def add_tag(tag_name, ref, message = nil)
145
    before_push_tag
146

147
    gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
148 149
  end

150
  def rm_branch(user, branch_name)
151
    before_remove_branch
152

153 154 155 156 157 158 159 160
    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
161

162
    after_remove_branch
163
    true
164 165
  end

166
  def rm_tag(tag_name)
Y
Yorick Peterse 已提交
167
    before_remove_tag
168

169 170 171
    gitlab_shell.rm_tag(path_with_namespace, tag_name)
  end

172
  def branch_names
173
    cache.fetch(:branch_names) { raw_repository.branch_names }
174 175 176
  end

  def tag_names
177
    cache.fetch(:tag_names) { raw_repository.tag_names }
178 179
  end

180
  def commit_count
181
    cache.fetch(:commit_count) do
182
      begin
183
        raw_repository.commit_count(self.root_ref)
184 185 186
      rescue
        0
      end
187
    end
188 189
  end

Y
Yorick Peterse 已提交
190 191 192 193 194 195 196 197
  def branch_count
    @branch_count ||= cache.fetch(:branch_count) { raw_repository.branch_count }
  end

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

198 199 200
  # Return repo size in megabytes
  # Cached in redis
  def size
201
    cache.fetch(:size) { raw_repository.size }
202
  end
203

204
  def diverging_commit_counts(branch)
205
    root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
J
Jeff Stubler 已提交
206
    cache.fetch(:"diverging_commit_counts_#{branch.name}") do
207 208
      # Rugged seems to throw a `ReferenceError` when given branch_names rather
      # than SHA-1 hashes
209 210 211 212 213
      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)
214

215 216 217
      { behind: number_commits_behind, ahead: number_commits_ahead }
    end
  end
218

219
  def cache_keys
220
    %i(size branch_names tag_names commit_count
221 222
       readme version contribution_guide changelog license)
  end
223

224 225 226 227 228 229 230 231
  def build_cache
    cache_keys.each do |key|
      unless cache.exist?(key)
        send(key)
      end
    end
  end

D
Douwe Maan 已提交
232 233 234 235 236 237 238 239 240 241
  def expire_tags_cache
    cache.expire(:tag_names)
    @tags = nil
  end

  def expire_branches_cache
    cache.expire(:branch_names)
    @branches = nil
  end

242
  def expire_cache(branch_name = nil, revision = nil)
243
    cache_keys.each do |key|
244 245
      cache.expire(key)
    end
246

247
    expire_branch_cache(branch_name)
248
    expire_avatar_cache(branch_name, revision)
249 250 251 252

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

255 256 257 258 259 260 261 262 263 264 265 266
  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}")
267
    end
D
Dmitriy Zaporozhets 已提交
268 269
  end

270 271 272 273 274
  def expire_root_ref_cache
    cache.expire(:root_ref)
    @root_ref = nil
  end

275 276 277 278 279 280 281 282
  # 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

283 284 285 286 287
  def expire_has_visible_content_cache
    cache.expire(:has_visible_content?)
    @has_visible_content = nil
  end

Y
Yorick Peterse 已提交
288 289 290 291 292 293 294 295 296 297
  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

298 299 300 301
  def lookup_cache
    @lookup_cache ||= {}
  end

302 303 304 305
  def expire_branch_names
    cache.expire(:branch_names)
  end

306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
  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

323 324
  # Runs code just before a repository is deleted.
  def before_delete
325 326 327 328
    expire_cache if exists?

    expire_root_ref_cache
    expire_emptiness_caches
329 330 331 332 333 334 335 336 337
  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 已提交
338 339
  # Runs code before pushing (= creating or removing) a tag.
  def before_push_tag
340
    expire_cache
341
    expire_tags_cache
Y
Yorick Peterse 已提交
342 343 344 345 346 347 348
    expire_tag_count_cache
  end

  # Runs code before removing a tag.
  def before_remove_tag
    expire_tags_cache
    expire_tag_count_cache
349 350 351 352 353 354 355 356
  end

  # Runs code after a repository has been forked/imported.
  def after_import
    expire_emptiness_caches
  end

  # Runs code after a new commit has been pushed.
357 358
  def after_push_commit(branch_name, revision)
    expire_cache(branch_name, revision)
359 360 361 362
  end

  # Runs code after a new branch has been created.
  def after_create_branch
363
    expire_branches_cache
364
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
365
    expire_branch_count_cache
366 367
  end

368 369 370 371 372
  # Runs code before removing an existing branch.
  def before_remove_branch
    expire_branches_cache
  end

373 374 375
  # Runs code after an existing branch has been removed.
  def after_remove_branch
    expire_has_visible_content_cache
Y
Yorick Peterse 已提交
376
    expire_branch_count_cache
377
    expire_branches_cache
378 379
  end

380
  def method_missing(m, *args, &block)
381 382 383 384 385 386
    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
387 388
  end

389 390
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
391
  end
D
Dmitriy Zaporozhets 已提交
392 393

  def blob_at(sha, path)
394 395 396
    unless Gitlab::Git.blank_ref?(sha)
      Gitlab::Git::Blob.find(self, sha, path)
    end
D
Dmitriy Zaporozhets 已提交
397
  end
398

399 400 401 402
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

403
  def readme
404
    cache.fetch(:readme) { tree(:head).readme }
405
  end
406

407
  def version
408
    cache.fetch(:version) do
409 410 411 412 413 414
      tree(:head).blobs.find do |file|
        file.name.downcase == 'version'
      end
    end
  end

415
  def contribution_guide
416 417 418 419 420 421
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
422 423 424 425

  def changelog
    cache.fetch(:changelog) do
      tree(:head).blobs.find do |file|
426
        file.name =~ /\A(changelog|history)/i
427 428
      end
    end
429 430
  end

431 432
  def license
    cache.fetch(:license) do
Z
Zeger-Jan van de Weg 已提交
433
      licenses =  tree(:head).blobs.find_all do |file|
434
                    file.name =~ /\A(copying|license|licence)/i
Z
Zeger-Jan van de Weg 已提交
435 436
                  end

437 438 439 440 441 442 443 444 445 446 447 448 449
      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 已提交
450

451
      license
452
    end
453 454
  end

455
  def head_commit
456 457 458 459 460
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
461 462 463 464
  end

  def tree(sha = :head, path = nil)
    if sha == :head
465 466 467 468 469
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
470 471 472 473
    end

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

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

D
Dmitriy Zaporozhets 已提交
478 479 480 481 482
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
D
Dmitriy Zaporozhets 已提交
483
  end
D
Dmitriy Zaporozhets 已提交
484 485 486 487 488 489 490 491

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
D
Dmitriy Zaporozhets 已提交
492
    if submodules(ref).any?
D
Dmitriy Zaporozhets 已提交
493 494 495 496 497 498 499
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
500 501

  def last_commit_for_path(sha, path)
502
    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
503 504
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
505
  end
506

507 508 509 510 511 512 513 514 515 516 517
  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

518
  # Remove archives older than 2 hours
519 520 521 522 523 524 525 526 527 528 529 530 531 532
  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
533 534

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

D
Dmitriy Zaporozhets 已提交
537
    commits.group_by(&:author_email).map do |email, commits|
538 539
      contributor = Gitlab::Contributor.new
      contributor.email = email
540

D
Dmitriy Zaporozhets 已提交
541
      commits.each do |commit|
542
        if contributor.name.blank?
D
Dmitriy Zaporozhets 已提交
543
          contributor.name = commit.author_name
544 545
        end

546
        contributor.commits += 1
547 548
      end

549 550
      contributor
    end
551
  end
D
Dmitriy Zaporozhets 已提交
552 553

  def blob_for_diff(commit, diff)
554
    blob_at(commit.id, diff.file_path)
D
Dmitriy Zaporozhets 已提交
555 556 557 558 559 560 561
  end

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

563 564
  def refs_contains_sha(ref_type, sha)
    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
565 566 567 568 569 570 571 572 573 574 575 576 577 578
    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 已提交
579

580 581 582
  def branch_names_contains(sha)
    refs_contains_sha('branch', sha)
  end
H
Hannes Rosenögger 已提交
583

584 585
  def tag_names_contains(sha)
    refs_contains_sha('tag', sha)
H
Hannes Rosenögger 已提交
586
  end
587

588 589 590 591 592 593 594 595 596
  def branches
    @branches ||= raw_repository.branches
  end

  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
597
    @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
598 599
  end

S
Stan Hu 已提交
600
  def commit_dir(user, path, message, branch)
601
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
602 603 604 605 606 607 608 609 610 611 612 613 614
      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
615

S
Stan Hu 已提交
616 617 618
  def commit_file(user, path, content, message, branch, update)
    commit_with_hooks(user, branch) do |ref|
      committer = user_to_committer(user)
619 620 621 622 623 624 625
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref,
      }
626

627 628
      options[:file] = {
        content: content,
S
Stan Hu 已提交
629 630
        path: path,
        update: update
631
      }
632

633 634
      Gitlab::Git::Blob.commit(raw_repository, options)
    end
635 636
  end

637
  def remove_file(user, path, message, branch)
638
    commit_with_hooks(user, branch) do |ref|
S
Stan Hu 已提交
639
      committer = user_to_committer(user)
640 641 642 643 644 645 646
      options = {}
      options[:committer] = committer
      options[:author] = committer
      options[:commit] = {
        message: message,
        branch: ref
      }
647

648 649 650
      options[:file] = {
        path: path
      }
651

652 653
      Gitlab::Git::Blob.remove(raw_repository, options)
    end
654 655
  end

S
Stan Hu 已提交
656
  def user_to_committer(user)
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
    {
      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

675
  def merge(user, source_sha, target_branch, options = {})
676 677 678 679 680 681 682 683 684
    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?

685 686 687 688 689 690
    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
      )
691

692
      Rugged::Commit.create(rugged, actual_options)
693
    end
694 695
  end

696 697
  def revert(user, commit, base_branch, revert_tree_id = nil)
    source_sha = find_branch(base_branch).target
698
    revert_tree_id ||= check_revert_content(commit, base_branch)
699

700
    return false unless revert_tree_id
701

702
    commit_with_hooks(user, base_branch) do |ref|
703
      committer = user_to_committer(user)
704
      source_sha = Rugged::Commit.create(rugged,
R
Rubén Dávila 已提交
705
        message: commit.revert_message,
706 707
        author: committer,
        committer: committer,
708
        tree: revert_tree_id,
709
        parents: [rugged.lookup(source_sha)],
R
Rubén Dávila 已提交
710
        update_ref: ref)
711
    end
712 713
  end

714 715 716 717 718 719 720 721 722 723 724 725 726 727
  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

728 729
  def diff_exists?(sha1, sha2)
    rugged.diff(sha1, sha2).size > 0
730 731
  end

F
Florent (HP) 已提交
732 733 734 735 736
  def merged_to_root_ref?(branch_name)
    branch_commit = commit(branch_name)
    root_ref_commit = commit(root_ref)

    if branch_commit
737
      is_ancestor?(branch_commit.id, root_ref_commit.id)
F
Florent (HP) 已提交
738 739 740 741 742
    else
      nil
    end
  end

S
Stan Hu 已提交
743
  def merge_base(first_commit_id, second_commit_id)
744 745
    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 已提交
746
    rugged.merge_base(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
747 748
  rescue Rugged::ReferenceError
    nil
S
Stan Hu 已提交
749 750
  end

751 752 753 754 755
  def is_ancestor?(ancestor_id, descendant_id)
    merge_base(ancestor_id, descendant_id) == ancestor_id
  end


756 757
  def search_files(query, ref)
    offset = 2
758
    args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
759 760 761
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

D
Dmitriy Zaporozhets 已提交
762
  def parse_search_result(result)
763 764
    ref = nil
    filename = nil
765
    basename = nil
766 767
    startline = 0

768
    result.each_line.each_with_index do |line, index|
769 770 771
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
772 773
        extname = File.extname(filename)
        basename = filename.sub(/#{extname}$/, '')
774 775 776 777
        break
      end
    end

778
    data = ""
779

780 781 782
    result.each_line do |line|
      data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end
783 784 785

    OpenStruct.new(
      filename: filename,
786
      basename: basename,
787 788 789 790 791 792
      ref: ref,
      startline: startline,
      data: data
    )
  end

793
  def fetch_ref(source_path, source_ref, target_ref)
794
    args = %W(#{Gitlab.config.git.bin_path} fetch -f #{source_path} #{source_ref}:#{target_ref})
795 796 797
    Gitlab::Popen.popen(args, path_to_repo)
  end

798
  def with_tmp_ref(oldrev = nil)
799 800 801
    random_string = SecureRandom.hex
    tmp_ref = "refs/tmp/#{random_string}/head"

802
    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
803 804 805 806
      rugged.references.create(tmp_ref, oldrev)
    end

    # Make commit in tmp ref
807 808 809 810 811 812
    yield(tmp_ref)
  ensure
    rugged.references.delete(tmp_ref) rescue nil
  end

  def commit_with_hooks(current_user, branch)
813 814
    update_autocrlf_option

815 816
    oldrev = Gitlab::Git::BLANK_SHA
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
817
    target_branch = find_branch(branch)
818
    was_empty = empty?
819

820 821
    if !was_empty && target_branch
      oldrev = target_branch.target
822 823
    end

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

832
      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
833
        if was_empty || !target_branch
834 835
          # Create branch
          rugged.references.create(ref, newrev)
836
        else
837 838 839 840 841 842 843 844 845
          # 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
846 847
        end
      end
848 849

      newrev
850 851 852
    end
  end

853 854 855 856 857
  def ls_files(ref)
    actual_ref = ref || root_ref
    raw_repository.ls_files(actual_ref)
  end

858 859 860 861 862 863
  def main_language
    unless empty?
      Linguist::Repository.new(rugged, rugged.head.target_id).language
    end
  end

864 865 866 867 868 869 870 871
  def avatar
    @avatar ||= cache.fetch(:avatar) do
      AVATAR_FILES.find do |file|
        blob_at_branch('master', file)
      end
    end
  end

872 873
  private

874 875 876
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
877
end