repository_spec.rb 73.1 KB
Newer Older
1 2
require 'spec_helper'

3
describe Repository do
4
  include RepoHelpers
D
Douwe Maan 已提交
5
  TestBlob = Struct.new(:path)
6

7
  let(:project) { create(:project, :repository) }
8
  let(:repository) { project.repository }
9
  let(:broken_repository) { create(:project, :broken_storage).repository }
10
  let(:user) { create(:user) }
11
  let(:git_user) { Gitlab::Git::User.from_gitlab(user) }
12

13
  let(:message) { 'Test message' }
14

R
Rubén Dávila 已提交
15
  let(:merge_commit) do
16
    merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project)
S
Sean McGivern 已提交
17 18 19 20

    merge_commit_id = repository.merge(user,
                                       merge_request.diff_head_sha,
                                       merge_request,
21
                                       message)
S
Sean McGivern 已提交
22

23
    repository.commit(merge_commit_id)
24
  end
25

26 27
  let(:author_email) { 'user@example.org' }
  let(:author_name) { 'John Doe' }
28

29 30
  def expect_to_raise_storage_error
    expect { yield }.to raise_error do |exception|
31
      storage_exceptions = [Gitlab::Git::Storage::Inaccessible, Gitlab::Git::CommandError, GRPC::Unavailable]
32 33 34
      known_exception = storage_exceptions.select { |e| exception.is_a?(e) }

      expect(known_exception).not_to be_nil
35 36 37
    end
  end

38
  describe '#branch_names_contains' do
39 40
    subject { repository.branch_names_contains(sample_commit.id) }

41 42 43
    it { is_expected.to include('master') }
    it { is_expected.not_to include('feature') }
    it { is_expected.not_to include('fix') }
44

45
    describe 'when storage is broken', :broken_storage  do
46 47 48 49 50 51
      it 'should raise a storage error' do
        expect_to_raise_storage_error do
          broken_repository.branch_names_contains(sample_commit.id)
        end
      end
    end
52
  end
53

54
  describe '#tag_names_contains' do
D
Dmitriy Zaporozhets 已提交
55
    subject { repository.tag_names_contains(sample_commit.id) }
56

D
Dmitriy Zaporozhets 已提交
57 58
    it { is_expected.to include('v1.1.0') }
    it { is_expected.not_to include('v1.0.0') }
59 60
  end

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
  describe 'tags_sorted_by' do
    context 'name' do
      subject { repository.tags_sorted_by('name').map(&:name) }

      it { is_expected.to eq(['v1.1.0', 'v1.0.0']) }
    end

    context 'updated' do
      let(:tag_a) { repository.find_tag('v1.0.0') }
      let(:tag_b) { repository.find_tag('v1.1.0') }

      context 'desc' do
        subject { repository.tags_sorted_by('updated_desc').map(&:name) }

        before do
          double_first = double(committed_date: Time.now)
          double_last = double(committed_date: Time.now - 1.second)

79 80
          allow(tag_a).to receive(:dereferenced_target).and_return(double_first)
          allow(tag_b).to receive(:dereferenced_target).and_return(double_last)
81
          allow(repository).to receive(:tags).and_return([tag_a, tag_b])
82 83 84 85 86 87 88 89 90 91 92 93
        end

        it { is_expected.to eq(['v1.0.0', 'v1.1.0']) }
      end

      context 'asc' do
        subject { repository.tags_sorted_by('updated_asc').map(&:name) }

        before do
          double_first = double(committed_date: Time.now - 1.second)
          double_last = double(committed_date: Time.now)

94 95
          allow(tag_a).to receive(:dereferenced_target).and_return(double_last)
          allow(tag_b).to receive(:dereferenced_target).and_return(double_first)
96
          allow(repository).to receive(:tags).and_return([tag_a, tag_b])
97 98 99 100
        end

        it { is_expected.to eq(['v1.1.0', 'v1.0.0']) }
      end
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124

      context 'annotated tag pointing to a blob' do
        let(:annotated_tag_name) { 'annotated-tag' }

        subject { repository.tags_sorted_by('updated_asc').map(&:name) }

        before do
          options = { message: 'test tag message\n',
                      tagger: { name: 'John Smith', email: 'john@gmail.com' } }
          repository.rugged.tags.create(annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', options)

          double_first = double(committed_date: Time.now - 1.second)
          double_last = double(committed_date: Time.now)

          allow(tag_a).to receive(:dereferenced_target).and_return(double_last)
          allow(tag_b).to receive(:dereferenced_target).and_return(double_first)
        end

        it { is_expected.to eq(['v1.1.0', 'v1.0.0', annotated_tag_name]) }

        after do
          repository.rugged.tags.delete(annotated_tag_name)
        end
      end
125 126 127
    end
  end

128
  describe '#ref_name_for_sha' do
129
    it 'returns the ref' do
130 131
      allow(repository.raw_repository).to receive(:ref_name_for_sha)
        .and_return('refs/environments/production/77')
132

133
      expect(repository.ref_name_for_sha('bla', '0' * 40)).to eq 'refs/environments/production/77'
134 135 136
    end
  end

137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
  describe '#ref_exists?' do
    context 'when ref exists' do
      it 'returns true' do
        expect(repository.ref_exists?('refs/heads/master')).to be true
      end
    end

    context 'when ref does not exist' do
      it 'returns false' do
        expect(repository.ref_exists?('refs/heads/non-existent')).to be false
      end
    end

    context 'when ref format is incorrect' do
      it 'returns false' do
        expect(repository.ref_exists?('refs/heads/invalid:master')).to be false
      end
    end
  end

157
  describe '#last_commit_for_path' do
158 159
    shared_examples 'getting last commit for path' do
      subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id }
160

161
      it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
162

163
      describe 'when storage is broken', :broken_storage  do
164 165 166 167 168 169
        it 'should raise a storage error' do
          expect_to_raise_storage_error do
            broken_repository.last_commit_id_for_path(sample_commit.id, '.gitignore')
          end
        end
      end
170 171 172 173 174
    end

    context 'when Gitaly feature last_commit_for_path is enabled' do
      it_behaves_like 'getting last commit for path'
    end
175

176
    context 'when Gitaly feature last_commit_for_path is disabled', :skip_gitaly_mock do
177 178
      it_behaves_like 'getting last commit for path'
    end
179
  end
180

H
Hiroyuki Sato 已提交
181
  describe '#last_commit_id_for_path' do
182 183
    shared_examples 'getting last commit ID for path' do
      subject { repository.last_commit_id_for_path(sample_commit.id, '.gitignore') }
H
Hiroyuki Sato 已提交
184

185 186 187 188 189 190 191 192 193 194 195
      it "returns last commit id for a given path" do
        is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8')
      end

      it "caches last commit id for a given path" do
        cache = repository.send(:cache)
        key = "last_commit_id_for_path:#{sample_commit.id}:#{Digest::SHA1.hexdigest('.gitignore')}"

        expect(cache).to receive(:fetch).with(key).and_return('c1acaa5')
        is_expected.to eq('c1acaa5')
      end
196

197
      describe 'when storage is broken', :broken_storage  do
198 199 200 201 202 203
        it 'should raise a storage error' do
          expect_to_raise_storage_error do
            broken_repository.last_commit_for_path(sample_commit.id, '.gitignore').id
          end
        end
      end
H
Hiroyuki Sato 已提交
204
    end
H
Hiroyuki Sato 已提交
205

206 207 208
    context 'when Gitaly feature last_commit_for_path is enabled' do
      it_behaves_like 'getting last commit ID for path'
    end
209

210
    context 'when Gitaly feature last_commit_for_path is disabled', :skip_gitaly_mock do
211
      it_behaves_like 'getting last commit ID for path'
H
Hiroyuki Sato 已提交
212 213 214
    end
  end

215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
  describe '#commits' do
    it 'sets follow when path is a single path' do
      expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: true)).and_call_original.twice

      repository.commits('master', path: 'README.md')
      repository.commits('master', path: ['README.md'])
    end

    it 'does not set follow when path is multiple paths' do
      expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original

      repository.commits('master', path: ['README.md', 'CHANGELOG'])
    end

    it 'does not set follow when there are no paths' do
      expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original

      repository.commits('master')
    end
  end

236
  describe '#find_commits_by_message' do
237 238 239 240 241 242 243 244 245 246 247 248 249 250
    shared_examples 'finding commits by message' do
      it 'returns commits with messages containing a given string' do
        commit_ids = repository.find_commits_by_message('submodule').map(&:id)

        expect(commit_ids).to include(
          '5937ac0a7beb003549fc5fd26fc247adbce4a52e',
          '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9',
          'cfe32cf61b73a0d5e9f13e774abde7ff789b1660'
        )
        expect(commit_ids).not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e')
      end

      it 'is case insensitive' do
        commit_ids = repository.find_commits_by_message('SUBMODULE').map(&:id)
251

252 253
        expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
      end
254 255
    end

256 257 258
    context 'when Gitaly commits_by_message feature is enabled' do
      it_behaves_like 'finding commits by message'
    end
259

260
    context 'when Gitaly commits_by_message feature is disabled', :skip_gitaly_mock do
261
      it_behaves_like 'finding commits by message'
262
    end
263

264
    describe 'when storage is broken', :broken_storage  do
265 266 267 268
      it 'should raise a storage error' do
        expect_to_raise_storage_error { broken_repository.find_commits_by_message('s') }
      end
    end
269 270
  end

271
  describe '#blob_at' do
272 273 274 275 276 277
    context 'blank sha' do
      subject { repository.blob_at(Gitlab::Git::BLANK_SHA, '.gitignore') }

      it { is_expected.to be_nil }
    end
  end
278

279
  describe '#merged_to_root_ref?' do
280
    context 'merged branch without ff' do
281
      subject { repository.merged_to_root_ref?('branch-merged') }
F
Florent (HP) 已提交
282 283 284

      it { is_expected.to be_truthy }
    end
285

286 287
    # If the HEAD was ff then it will be false
    context 'merged with ff' do
F
Florent (HP) 已提交
288 289 290 291
      subject { repository.merged_to_root_ref?('improve/awesome') }

      it { is_expected.to be_truthy }
    end
292

293 294 295 296 297
    context 'not merged branch' do
      subject { repository.merged_to_root_ref?('not-merged-branch') }

      it { is_expected.to be_falsey }
    end
298 299 300 301 302 303

    context 'default branch' do
      subject { repository.merged_to_root_ref?('master') }

      it { is_expected.to be_falsey }
    end
304 305
  end

306
  describe '#can_be_merged?' do
307 308 309 310 311 312 313 314 315 316 317
    context 'mergeable branches' do
      subject { repository.can_be_merged?('0b4bc9a49b562e85de7cc9e834518ea6828729b9', 'master') }

      it { is_expected.to be_truthy }
    end

    context 'non-mergeable branches' do
      subject { repository.can_be_merged?('bb5206fee213d983da88c47f9cf4cc6caf9c66dc', 'feature') }

      it { is_expected.to be_falsey }
    end
F
Florent (HP) 已提交
318 319 320 321 322 323 324 325 326 327 328 329

    context 'non merged branch' do
      subject { repository.merged_to_root_ref?('fix') }

      it { is_expected.to be_falsey }
    end

    context 'non existent branch' do
      subject { repository.merged_to_root_ref?('non_existent_branch') }

      it { is_expected.to be_nil }
    end
330 331
  end

332 333 334
  describe '#commit' do
    context 'when ref exists' do
      it 'returns commit object' do
D
Douwe Maan 已提交
335 336
        expect(repository.commit('master'))
          .to be_an_instance_of Commit
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
      end
    end

    context 'when ref does not exist' do
      it 'returns nil' do
        expect(repository.commit('non-existent-ref')).to be_nil
      end
    end

    context 'when ref is not valid' do
      context 'when preceding tree element exists' do
        it 'returns nil' do
          expect(repository.commit('master:ref')).to be_nil
        end
      end

      context 'when preceding tree element does not exist' do
        it 'returns nil' do
          expect(repository.commit('non-existent:ref')).to be_nil
        end
      end
    end
  end

D
Douwe Maan 已提交
361
  describe "#create_dir" do
362 363
    it "commits a change that creates a new directory" do
      expect do
364
        repository.create_dir(user, 'newdir',
365
          message: 'Create newdir', branch_name: 'master')
366 367 368 369 370 371
      end.to change { repository.commits('master').count }.by(1)

      newdir = repository.tree('master', 'newdir')
      expect(newdir.path).to eq('newdir')
    end

372
    context "when committing to another project" do
373
      let(:forked_project) { create(:project, :repository) }
374 375 376

      it "creates a fork and commit to the forked project" do
        expect do
377
          repository.create_dir(user, 'newdir',
378
            message: 'Create newdir', branch_name: 'patch',
L
Lin Jen-Shin 已提交
379
            start_branch_name: 'master', start_project: forked_project)
380 381 382 383 384 385 386 387 388 389
        end.to change { repository.commits('master').count }.by(0)

        expect(repository.branch_exists?('patch')).to be_truthy
        expect(forked_project.repository.branch_exists?('patch')).to be_falsy

        newdir = repository.tree('patch', 'newdir')
        expect(newdir.path).to eq('newdir')
      end
    end

390 391 392
    context "when an author is specified" do
      it "uses the given email/name to set the commit's author" do
        expect do
393
          repository.create_dir(user, 'newdir',
394 395 396
            message: 'Add newdir',
            branch_name: 'master',
            author_email: author_email, author_name: author_name)
397 398 399 400 401 402 403 404 405 406
        end.to change { repository.commits('master').count }.by(1)

        last_commit = repository.commit

        expect(last_commit.author_email).to eq(author_email)
        expect(last_commit.author_name).to eq(author_name)
      end
    end
  end

407 408
  describe "#create_file" do
    it 'commits new file successfully' do
409
      expect do
410 411 412
        repository.create_file(user, 'NEWCHANGELOG', 'Changelog!',
                               message: 'Create changelog',
                               branch_name: 'master')
413 414
      end.to change { repository.commits('master').count }.by(1)

415
      blob = repository.blob_at('master', 'NEWCHANGELOG')
416

417
      expect(blob.data).to eq('Changelog!')
418
    end
419

420
    it 'creates new file and dir when file_path has a forward slash' do
421
      expect do
422 423
        repository.create_file(user, 'new_dir/new_file.txt', 'File!',
                               message: 'Create new_file with new_dir',
424 425 426
                               branch_name: 'master')
      end.to change { repository.commits('master').count }.by(1)

427 428
      expect(repository.tree('master', 'new_dir').path).to eq('new_dir')
      expect(repository.blob_at('master', 'new_dir/new_file.txt').data).to eq('File!')
429 430
    end

431
    it 'respects the autocrlf setting' do
432
      repository.create_file(user, 'hello.txt', "Hello,\r\nWorld",
433
                             message: 'Add hello world',
434
                             branch_name: 'master')
435 436 437 438 439 440

      blob = repository.blob_at('master', 'hello.txt')

      expect(blob.data).to eq("Hello,\nWorld")
    end

441 442 443
    context "when an author is specified" do
      it "uses the given email/name to set the commit's author" do
        expect do
444
          repository.create_file(user, 'NEWREADME', 'README!',
445 446 447 448
                                 message: 'Add README',
                                 branch_name: 'master',
                                 author_email: author_email,
                                 author_name: author_name)
449 450 451 452 453 454 455 456
        end.to change { repository.commits('master').count }.by(1)

        last_commit = repository.commit

        expect(last_commit.author_email).to eq(author_email)
        expect(last_commit.author_name).to eq(author_name)
      end
    end
457 458
  end

459
  describe "#update_file" do
460 461 462 463 464 465 466 467 468 469 470 471
    it 'updates file successfully' do
      expect do
        repository.update_file(user, 'CHANGELOG', 'Changelog!',
                               message: 'Update changelog',
                               branch_name: 'master')
      end.to change { repository.commits('master').count }.by(1)

      blob = repository.blob_at('master', 'CHANGELOG')

      expect(blob.data).to eq('Changelog!')
    end

T
tiagonbotelho 已提交
472
    it 'updates filename successfully' do
473 474
      expect do
        repository.update_file(user, 'NEWLICENSE', 'Copyright!',
475
                                     branch_name: 'master',
T
tiagonbotelho 已提交
476
                                     previous_path: 'LICENSE',
477 478
                                     message: 'Changes filename')
      end.to change { repository.commits('master').count }.by(1)
T
tiagonbotelho 已提交
479 480 481 482 483 484

      files = repository.ls_files('master')

      expect(files).not_to include('LICENSE')
      expect(files).to include('NEWLICENSE')
    end
485 486 487 488

    context "when an author is specified" do
      it "uses the given email/name to set the commit's author" do
        expect do
489 490 491 492 493 494
          repository.update_file(user, 'README', 'Updated README!',
                                 branch_name: 'master',
                                 previous_path: 'README',
                                 message: 'Update README',
                                 author_email: author_email,
                                 author_name: author_name)
495 496 497 498 499 500 501 502 503 504
        end.to change { repository.commits('master').count }.by(1)

        last_commit = repository.commit

        expect(last_commit.author_email).to eq(author_email)
        expect(last_commit.author_name).to eq(author_name)
      end
    end
  end

D
Douwe Maan 已提交
505
  describe "#delete_file" do
506 507
    it 'removes file successfully' do
      expect do
508
        repository.delete_file(user, 'README',
509
          message: 'Remove README', branch_name: 'master')
510 511 512 513 514 515 516 517
      end.to change { repository.commits('master').count }.by(1)

      expect(repository.blob_at('master', 'README')).to be_nil
    end

    context "when an author is specified" do
      it "uses the given email/name to set the commit's author" do
        expect do
518
          repository.delete_file(user, 'README',
519 520
            message: 'Remove README', branch_name: 'master',
            author_email: author_email, author_name: author_name)
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
        end.to change { repository.commits('master').count }.by(1)

        last_commit = repository.commit

        expect(last_commit.author_email).to eq(author_email)
        expect(last_commit.author_name).to eq(author_name)
      end
    end
  end

  describe '#get_committer_and_author' do
    it 'returns the committer and author data' do
      options = repository.get_committer_and_author(user)
      expect(options[:committer][:email]).to eq(user.email)
      expect(options[:author][:email]).to eq(user.email)
    end

    context 'when the email/name are given' do
      it 'returns an object containing the email/name' do
        options = repository.get_committer_and_author(user, email: author_email, name: author_name)
        expect(options[:author][:email]).to eq(author_email)
        expect(options[:author][:name]).to eq(author_name)
      end
    end

    context 'when the email is given but the name is not' do
      it 'returns the committer as the author' do
        options = repository.get_committer_and_author(user, email: author_email)
        expect(options[:author][:email]).to eq(user.email)
        expect(options[:author][:name]).to eq(user.name)
      end
    end

    context 'when the name is given but the email is not' do
      it 'returns nil' do
        options = repository.get_committer_and_author(user, name: author_name)
        expect(options[:author][:email]).to eq(user.email)
        expect(options[:author][:name]).to eq(user.name)
      end
    end
T
tiagonbotelho 已提交
561 562
  end

V
Valery Sizov 已提交
563 564
  describe "search_files_by_content" do
    let(:results) { repository.search_files_by_content('feature', 'master') }
565 566 567 568
    subject { results }

    it { is_expected.to be_an Array }

569
    it 'regex-escapes the query string' do
V
Valery Sizov 已提交
570
      results = repository.search_files_by_content("test\\", 'master')
571 572 573 574

      expect(results.first).not_to start_with('fatal:')
    end

575
    it 'properly handles an unmatched parenthesis' do
V
Valery Sizov 已提交
576
      results = repository.search_files_by_content("test(", 'master')
577 578 579 580

      expect(results.first).not_to start_with('fatal:')
    end

V
Valery Sizov 已提交
581
    it 'properly handles when query is not present' do
V
Valery Sizov 已提交
582
      results = repository.search_files_by_content('', 'master')
V
Valery Sizov 已提交
583 584 585 586 587

      expect(results).to match_array([])
    end

    it 'properly handles query when repo is empty' do
588
      repository = create(:project, :empty_repo).repository
V
Valery Sizov 已提交
589
      results = repository.search_files_by_content('test', 'master')
V
Valery Sizov 已提交
590 591 592 593

      expect(results).to match_array([])
    end

594
    describe 'when storage is broken', :broken_storage  do
595 596 597 598 599 600 601
      it 'should raise a storage error' do
        expect_to_raise_storage_error do
          broken_repository.search_files_by_content('feature', 'master')
        end
      end
    end

602 603 604 605
    describe 'result' do
      subject { results.first }

      it { is_expected.to be_an String }
606
      it { expect(subject.lines[2]).to eq("master:CHANGELOG:190:  - Feature: Replace teams with group membership\n") }
607 608
    end
  end
Z
Zeger-Jan van de Weg 已提交
609

V
Valery Sizov 已提交
610 611 612 613 614 615 616 617 618 619 620 621 622 623
  describe "search_files_by_name" do
    let(:results) { repository.search_files_by_name('files', 'master') }

    it 'returns result' do
      expect(results.first).to eq('files/html/500.html')
    end

    it 'properly handles when query is not present' do
      results = repository.search_files_by_name('', 'master')

      expect(results).to match_array([])
    end

    it 'properly handles query when repo is empty' do
624
      repository = create(:project, :empty_repo).repository
V
Valery Sizov 已提交
625 626 627 628 629

      results = repository.search_files_by_name('test', 'master')

      expect(results).to match_array([])
    end
630

631
    describe 'when storage is broken', :broken_storage  do
632 633 634 635 636 637 638
      it 'should raise a storage error' do
        expect_to_raise_storage_error { broken_repository.search_files_by_name('files', 'master') }
      end
    end
  end

  describe '#fetch_ref' do
639
    let(:broken_repository) { create(:project, :broken_storage).repository }
640

641
    describe 'when storage is broken', :broken_storage  do
642
      it 'should raise a storage error' do
643 644 645
        expect_to_raise_storage_error do
          broken_repository.fetch_ref(broken_repository, source_ref: '1', target_ref: '2')
        end
646 647
      end
    end
V
Valery Sizov 已提交
648 649
  end

650
  describe '#create_ref' do
651
    it 'redirects the call to write_ref' do
652 653
      ref, ref_path = '1', '2'

654
      expect(repository.raw_repository).to receive(:write_ref).with(ref_path, ref)
655 656 657 658 659

      repository.create_ref(ref, ref_path)
    end
  end

660
  describe "#changelog", :use_clean_rails_memory_store_caching do
661 662 663
    it 'accepts changelog' do
      expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('changelog')])

D
Douwe Maan 已提交
664
      expect(repository.changelog.path).to eq('changelog')
665 666 667 668 669
    end

    it 'accepts news instead of changelog' do
      expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('news')])

D
Douwe Maan 已提交
670
      expect(repository.changelog.path).to eq('news')
671 672 673 674 675
    end

    it 'accepts history instead of changelog' do
      expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('history')])

D
Douwe Maan 已提交
676
      expect(repository.changelog.path).to eq('history')
677 678 679 680 681
    end

    it 'accepts changes instead of changelog' do
      expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('changes')])

D
Douwe Maan 已提交
682
      expect(repository.changelog.path).to eq('changes')
683 684 685 686 687
    end

    it 'is case-insensitive' do
      expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('CHANGELOG')])

D
Douwe Maan 已提交
688
      expect(repository.changelog.path).to eq('CHANGELOG')
689 690 691
    end
  end

692
  describe "#license_blob", :use_clean_rails_memory_store_caching do
693
    before do
694
      repository.delete_file(
695
        user, 'LICENSE', message: 'Remove LICENSE', branch_name: 'master')
696 697
    end

698
    it 'handles when HEAD points to non-existent ref' do
699
      repository.create_file(
700
        user, 'LICENSE', 'Copyright!',
701
        message: 'Add LICENSE', branch_name: 'master')
702

703 704
      allow(repository).to receive(:file_on_head)
        .and_raise(Rugged::ReferenceError)
705 706 707 708

      expect(repository.license_blob).to be_nil
    end

709
    it 'looks in the root_ref only' do
710
      repository.delete_file(user, 'LICENSE',
711
        message: 'Remove LICENSE', branch_name: 'markdown')
712
      repository.create_file(user, 'LICENSE',
713
        Licensee::License.new('mit').content,
714
        message: 'Add LICENSE', branch_name: 'markdown')
715 716 717 718

      expect(repository.license_blob).to be_nil
    end

719
    it 'detects license file with no recognizable open-source license content' do
720 721
      repository.create_file(user, 'LICENSE', 'Copyright!',
        message: 'Add LICENSE', branch_name: 'master')
722

D
Douwe Maan 已提交
723
      expect(repository.license_blob.path).to eq('LICENSE')
724 725
    end

726 727
    %w[LICENSE LICENCE LiCensE LICENSE.md LICENSE.foo COPYING COPYING.md].each do |filename|
      it "detects '#{filename}'" do
728
        repository.create_file(user, filename,
729
          Licensee::License.new('mit').content,
730
          message: "Add #{filename}", branch_name: 'master')
731

732 733
        expect(repository.license_blob.name).to eq(filename)
      end
734 735 736
    end
  end

737
  describe '#license_key', :use_clean_rails_memory_store_caching do
738
    before do
739
      repository.delete_file(user, 'LICENSE',
740
        message: 'Remove LICENSE', branch_name: 'master')
741
    end
Z
Zeger-Jan van de Weg 已提交
742

743
    it 'returns nil when no license is detected' do
744 745 746
      expect(repository.license_key).to be_nil
    end

747 748 749
    it 'returns nil when the repository does not exist' do
      expect(repository).to receive(:exists?).and_return(false)

750 751 752
      expect(repository.license_key).to be_nil
    end

D
Douwe Maan 已提交
753
    it 'returns nil when the content is not recognizable' do
754 755
      repository.create_file(user, 'LICENSE', 'Copyright!',
        message: 'Add LICENSE', branch_name: 'master')
756 757

      expect(repository.license_key).to be_nil
Z
Zeger-Jan van de Weg 已提交
758
    end
759

760
    it 'returns the license key' do
761
      repository.create_file(user, 'LICENSE',
762
        Licensee::License.new('mit').content,
763
        message: 'Add LICENSE', branch_name: 'master')
764

765
      expect(repository.license_key).to eq('mit')
766
    end
Z
Zeger-Jan van de Weg 已提交
767
  end
768

D
Douwe Maan 已提交
769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
  describe '#license' do
    before do
      repository.delete_file(user, 'LICENSE',
        message: 'Remove LICENSE', branch_name: 'master')
    end

    it 'returns nil when no license is detected' do
      expect(repository.license).to be_nil
    end

    it 'returns nil when the repository does not exist' do
      expect(repository).to receive(:exists?).and_return(false)

      expect(repository.license).to be_nil
    end

    it 'returns nil when the content is not recognizable' do
      repository.create_file(user, 'LICENSE', 'Copyright!',
        message: 'Add LICENSE', branch_name: 'master')

      expect(repository.license).to be_nil
    end

    it 'returns the license' do
      license = Licensee::License.new('mit')
      repository.create_file(user, 'LICENSE',
        license.content,
        message: 'Add LICENSE', branch_name: 'master')

      expect(repository.license).to eq(license)
    end
  end

802
  describe "#gitlab_ci_yml", :use_clean_rails_memory_store_caching do
803 804 805 806
    it 'returns valid file' do
      files = [TestBlob.new('file'), TestBlob.new('.gitlab-ci.yml'), TestBlob.new('copying')]
      expect(repository.tree).to receive(:blobs).and_return(files)

D
Douwe Maan 已提交
807
      expect(repository.gitlab_ci_yml.path).to eq('.gitlab-ci.yml')
808 809 810 811 812 813 814 815
    end

    it 'returns nil if not exists' do
      expect(repository.tree).to receive(:blobs).and_return([])
      expect(repository.gitlab_ci_yml).to be_nil
    end

    it 'returns nil for empty repository' do
816
      allow(repository).to receive(:file_on_head).and_raise(Rugged::ReferenceError)
817 818 819 820
      expect(repository.gitlab_ci_yml).to be_nil
    end
  end

821
  describe '#add_branch' do
822 823
    let(:branch_name) { 'new_feature' }
    let(:target) { 'master' }
824

825
    subject { repository.add_branch(user, branch_name, target) }
826

827 828 829 830 831
    context 'with Gitaly enabled' do
      it "calls Gitaly's OperationService" do
        expect_any_instance_of(Gitlab::GitalyClient::OperationService)
          .to receive(:user_create_branch).with(branch_name, user, target)
          .and_return(nil)
832

833 834
        subject
      end
835

836 837 838
      it 'creates_the_branch' do
        expect(subject.name).to eq(branch_name)
        expect(repository.find_branch(branch_name)).not_to be_nil
839
      end
840

841 842
      context 'with a non-existing target' do
        let(:target) { 'fake-target' }
843

844 845 846 847
        it "returns false and doesn't create the branch" do
          expect(subject).to be(false)
          expect(repository.find_branch(branch_name)).to be_nil
        end
848
      end
849 850
    end

851
    context 'with Gitaly disabled', :skip_gitaly_mock do
852 853 854 855
      context 'when pre hooks were successful' do
        it 'runs without errors' do
          hook = double(trigger: [true, nil])
          expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
856

857 858 859 860 861 862 863 864 865 866 867 868 869 870
          expect { subject }.not_to raise_error
        end

        it 'creates the branch' do
          allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])

          expect(subject.name).to eq(branch_name)
        end

        it 'calls the after_create_branch hook' do
          expect(repository).to receive(:after_create_branch)

          subject
        end
871 872
      end

873 874 875
      context 'when pre hooks failed' do
        it 'gets an error' do
          allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
876

877 878 879 880 881 882 883 884 885
          expect { subject }.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
        end

        it 'does not create the branch' do
          allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])

          expect { subject }.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
          expect(repository.find_branch(branch_name)).to be_nil
        end
886 887 888 889
      end
    end
  end

890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907
  describe '#find_branch' do
    it 'loads a branch with a fresh repo' do
      expect(Gitlab::Git::Repository).to receive(:new).twice.and_call_original

      2.times do
        expect(repository.find_branch('feature')).not_to be_nil
      end
    end

    it 'loads a branch with a cached repo' do
      expect(Gitlab::Git::Repository).to receive(:new).once.and_call_original

      2.times do
        expect(repository.find_branch('feature', fresh_repo: false)).not_to be_nil
      end
    end
  end

908
  describe '#update_branch_with_hooks' do
909
    let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
910
    let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev
911 912 913
    let(:updating_ref) { 'refs/heads/feature' }
    let(:target_project) { project }
    let(:target_repository) { target_project.repository }
914

915
    context 'when pre hooks were successful' do
916
      before do
917 918
        service = Gitlab::Git::HooksService.new
        expect(Gitlab::Git::HooksService).to receive(:new).and_return(service)
919
        expect(service).to receive(:execute)
920
          .with(git_user, target_repository.raw_repository, old_rev, new_rev, updating_ref)
921
          .and_yield(service).and_return(true)
922
      end
923

924
      it 'runs without errors' do
925
        expect do
926
          Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('feature') do
927 928
            new_rev
          end
929 930
        end.not_to raise_error
      end
931

932
      it 'ensures the autocrlf Git option is set to :input' do
933
        service = Gitlab::Git::OperationService.new(git_user, repository.raw_repository)
934 935

        expect(service).to receive(:update_autocrlf_option)
936

937
        service.with_branch('feature') { new_rev }
938
      end
939 940 941

      context "when the branch wasn't empty" do
        it 'updates the head' do
942
          expect(repository.find_branch('feature').dereferenced_target.id).to eq(old_rev)
943

944
          Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('feature') do
945 946 947
            new_rev
          end

948
          expect(repository.find_branch('feature').dereferenced_target.id).to eq(new_rev)
949 950
        end
      end
951 952 953 954 955 956 957 958

      context 'when target project does not have the commit' do
        let(:target_project) { create(:project, :empty_repo) }
        let(:old_rev) { Gitlab::Git::BLANK_SHA }
        let(:new_rev) { project.commit('feature').sha }
        let(:updating_ref) { 'refs/heads/master' }

        it 'fetch_ref and create the branch' do
959
          expect(target_project.repository.raw_repository).to receive(:fetch_ref)
960 961
            .and_call_original

962
          Gitlab::Git::OperationService.new(git_user, target_repository.raw_repository)
963 964
            .with_branch(
              'master',
965
              start_repository: project.repository.raw_repository,
966 967 968 969 970 971 972 973 974 975 976 977
              start_branch_name: 'feature') { new_rev }

          expect(target_repository.branch_names).to contain_exactly('master')
        end
      end

      context 'when target project already has the commit' do
        let(:target_project) { create(:project, :repository) }

        it 'does not fetch_ref and just pass the commit' do
          expect(target_repository).not_to receive(:fetch_ref)

978
          Gitlab::Git::OperationService.new(git_user, target_repository.raw_repository)
979
            .with_branch('feature', start_repository: project.repository.raw_repository) { new_rev }
980 981
        end
      end
982 983
    end

984 985 986 987
    context 'when temporary ref failed to be created from other project' do
      let(:target_project) { create(:project, :empty_repo) }

      before do
988
        expect(target_project.repository.raw_repository).to receive(:run_git)
989 990 991 992 993 994 995 996
      end

      it 'raises Rugged::ReferenceError' do
        raise_reference_error = raise_error(Rugged::ReferenceError) do |err|
          expect(err.cause).to be_nil
        end

        expect do
997
          Gitlab::Git::OperationService.new(git_user, target_project.repository.raw_repository)
998
            .with_branch('feature',
999
                         start_repository: project.repository.raw_repository,
1000 1001 1002 1003 1004
                         &:itself)
        end.to raise_reference_error
      end
    end

1005
    context 'when the update adds more than one commit' do
1006
      let(:old_rev) { '33f3729a45c02fc67d00adb1b8bca394b0e761d9' }
1007

1008
      it 'runs without errors' do
1009 1010 1011 1012 1013 1014
        # old_rev is an ancestor of new_rev
        expect(repository.rugged.merge_base(old_rev, new_rev)).to eq(old_rev)

        # old_rev is not a direct ancestor (parent) of new_rev
        expect(repository.rugged.lookup(new_rev).parent_ids).not_to include(old_rev)

1015 1016 1017
        branch = 'feature-ff-target'
        repository.add_branch(user, branch, old_rev)

1018
        expect do
1019
          Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch(branch) do
1020 1021 1022
            new_rev
          end
        end.not_to raise_error
1023 1024 1025 1026
      end
    end

    context 'when the update would remove commits from the target branch' do
1027 1028
      let(:branch) { 'master' }
      let(:old_rev) { repository.find_branch(branch).dereferenced_target.sha }
1029

1030
      it 'raises an exception' do
1031 1032 1033 1034 1035 1036
        # The 'master' branch is NOT an ancestor of new_rev.
        expect(repository.rugged.merge_base(old_rev, new_rev)).not_to eq(old_rev)

        # Updating 'master' to new_rev would lose the commits on 'master' that
        # are not contained in new_rev. This should not be allowed.
        expect do
1037
          Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch(branch) do
1038 1039
            new_rev
          end
1040
        end.to raise_error(Gitlab::Git::CommitError)
1041 1042 1043
      end
    end

1044
    context 'when pre hooks failed' do
1045
      it 'gets an error' do
1046
        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
1047 1048

        expect do
1049
          Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('feature') do
1050 1051
            new_rev
          end
1052
        end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
1053 1054
      end
    end
1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066

    context 'when target branch is different from source branch' do
      before do
        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, ''])
      end

      it 'expires branch cache' do
        expect(repository).not_to receive(:expire_exists_cache)
        expect(repository).not_to receive(:expire_root_ref_cache)
        expect(repository).not_to receive(:expire_emptiness_caches)
        expect(repository).to     receive(:expire_branches_cache)

J
Jacob Vosmaer 已提交
1067
        repository.with_branch(user, 'new-feature') do
J
Jacob Vosmaer 已提交
1068
          new_rev
J
Jacob Vosmaer 已提交
1069
        end
1070 1071 1072 1073 1074 1075 1076 1077 1078
      end
    end

    context 'when repository is empty' do
      before do
        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, ''])
      end

      it 'expires creation and branch cache' do
1079
        empty_repository = create(:project, :empty_repo).repository
1080 1081 1082 1083 1084 1085

        expect(empty_repository).to receive(:expire_exists_cache)
        expect(empty_repository).to receive(:expire_root_ref_cache)
        expect(empty_repository).to receive(:expire_emptiness_caches)
        expect(empty_repository).to receive(:expire_branches_cache)

1086
        empty_repository.create_file(user, 'CHANGELOG', 'Changelog!',
1087
                                     message: 'Updates file content',
1088
                                     branch_name: 'master')
1089 1090
      end
    end
1091
  end
1092

1093
  shared_examples 'repo exists check' do
1094 1095 1096 1097
    it 'returns true when a repository exists' do
      expect(repository.exists?).to eq(true)
    end

1098
    it 'returns false if no full path can be constructed' do
1099
      allow(repository).to receive(:full_path).and_return(nil)
1100 1101 1102

      expect(repository.exists?).to eq(false)
    end
1103

1104
    context 'with broken storage', :broken_storage do
1105 1106 1107 1108
      it 'should raise a storage error' do
        expect_to_raise_storage_error { broken_repository.exists? }
      end
    end
1109
  end
1110

1111 1112 1113 1114
  describe '#exists?' do
    context 'when repository_exists is disabled' do
      it_behaves_like 'repo exists check'
    end
1115

1116
    context 'when repository_exists is enabled', :skip_gitaly_mock do
1117
      it_behaves_like 'repo exists check'
1118
    end
1119 1120
  end

1121
  describe '#has_visible_content?' do
1122 1123 1124 1125 1126 1127 1128
    before do
      # If raw_repository.has_visible_content? gets called more than once then
      # caching is broken. We don't want that.
      expect(repository.raw_repository).to receive(:has_visible_content?)
        .once
        .and_return(result)
    end
1129

1130 1131
    context 'when true' do
      let(:result) { true }
1132

1133 1134 1135 1136 1137
      it 'returns true and caches it' do
        expect(repository.has_visible_content?).to eq(true)
        # Second call hits the cache
        expect(repository.has_visible_content?).to eq(true)
      end
1138 1139
    end

1140 1141
    context 'when false' do
      let(:result) { false }
1142

1143 1144 1145 1146
      it 'returns false and caches it' do
        expect(repository.has_visible_content?).to eq(false)
        # Second call hits the cache
        expect(repository.has_visible_content?).to eq(false)
1147 1148 1149 1150
      end
    end
  end

1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175
  describe '#branch_exists?' do
    it 'uses branch_names' do
      allow(repository).to receive(:branch_names).and_return(['foobar'])

      expect(repository.branch_exists?('foobar')).to eq(true)
      expect(repository.branch_exists?('master')).to eq(false)
    end
  end

  describe '#branch_names', :use_clean_rails_memory_store_caching do
    let(:fake_branch_names) { ['foobar'] }

    it 'gets cached across Repository instances' do
      allow(repository.raw_repository).to receive(:branch_names).once.and_return(fake_branch_names)

      expect(repository.branch_names).to eq(fake_branch_names)

      fresh_repository = Project.find(project.id).repository
      expect(fresh_repository.object_id).not_to eq(repository.object_id)

      expect(fresh_repository.raw_repository).not_to receive(:branch_names)
      expect(fresh_repository.branch_names).to eq(fake_branch_names)
    end
  end

1176 1177 1178 1179 1180 1181 1182
  describe '#update_autocrlf_option' do
    describe 'when autocrlf is not already set to :input' do
      before do
        repository.raw_repository.autocrlf = true
      end

      it 'sets autocrlf to :input' do
1183
        Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_autocrlf_option)
1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194

        expect(repository.raw_repository.autocrlf).to eq(:input)
      end
    end

    describe 'when autocrlf is already set to :input' do
      before do
        repository.raw_repository.autocrlf = :input
      end

      it 'does nothing' do
1195 1196
        expect(repository.raw_repository).not_to receive(:autocrlf=)
          .with(:input)
1197

1198
        Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_autocrlf_option)
1199 1200 1201 1202
      end
    end
  end

1203 1204 1205 1206
  describe '#empty?' do
    let(:empty_repository) { create(:project_empty_repo).repository }

    it 'returns true for an empty repository' do
1207
      expect(empty_repository).to be_empty
1208 1209 1210
    end

    it 'returns false for a non-empty repository' do
1211
      expect(repository).not_to be_empty
1212 1213 1214
    end

    it 'caches the output' do
1215
      expect(repository.raw_repository).to receive(:has_visible_content?).once
1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227

      repository.empty?
      repository.empty?
    end
  end

  describe '#root_ref' do
    it 'returns a branch name' do
      expect(repository.root_ref).to be_an_instance_of(String)
    end

    it 'caches the output' do
1228 1229 1230
      expect(repository.raw_repository).to receive(:root_ref)
        .once
        .and_return('master')
1231 1232 1233 1234 1235 1236 1237 1238 1239 1240

      repository.root_ref
      repository.root_ref
    end
  end

  describe '#expire_root_ref_cache' do
    it 'expires the root reference cache' do
      repository.root_ref

1241 1242 1243
      expect(repository.raw_repository).to receive(:root_ref)
        .once
        .and_return('foo')
1244 1245 1246 1247 1248 1249 1250

      repository.expire_root_ref_cache

      expect(repository.root_ref).to eq('foo')
    end
  end

Y
Yorick Peterse 已提交
1251
  describe '#expire_branch_cache' do
1252 1253 1254 1255 1256
    # This method is private but we need it for testing purposes. Sadly there's
    # no other proper way of testing caching operations.
    let(:cache) { repository.send(:cache) }

    it 'expires the cache for all branches' do
1257 1258 1259
      expect(cache).to receive(:expire)
        .at_least(repository.branches.length * 2)
        .times
1260 1261 1262 1263 1264

      repository.expire_branch_cache
    end

    it 'expires the cache for all branches when the root branch is given' do
1265 1266 1267
      expect(cache).to receive(:expire)
        .at_least(repository.branches.length * 2)
        .times
1268 1269 1270 1271 1272

      repository.expire_branch_cache(repository.root_ref)
    end

    it 'expires the cache for a specific branch' do
1273
      expect(cache).to receive(:expire).twice
1274 1275 1276 1277

      repository.expire_branch_cache('foo')
    end
  end
1278

1279 1280 1281
  describe '#expire_emptiness_caches' do
    let(:cache) { repository.send(:cache) }

1282 1283 1284
    it 'expires the caches for an empty repository' do
      allow(repository).to receive(:empty?).and_return(true)

1285
      expect(cache).to receive(:expire).with(:empty?)
1286
      expect(cache).to receive(:expire).with(:has_visible_content?)
1287 1288 1289

      repository.expire_emptiness_caches
    end
1290 1291 1292 1293 1294

    it 'does not expire the cache for a non-empty repository' do
      allow(repository).to receive(:empty?).and_return(false)

      expect(cache).not_to receive(:expire).with(:empty?)
1295
      expect(cache).not_to receive(:expire).with(:has_visible_content?)
1296 1297 1298

      repository.expire_emptiness_caches
    end
1299 1300
  end

1301
  describe 'skip_merges option' do
1302
    subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map { |k| k.id } }
1303 1304 1305

    it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') }
  end
1306 1307

  describe '#merge' do
1308 1309
    let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project) }

1310
    let(:message) { 'Test \r\n\r\n message' }
1311

J
Jacob Vosmaer 已提交
1312 1313 1314 1315 1316 1317 1318 1319
    shared_examples '#merge' do
      it 'merges the code and returns the commit id' do
        expect(merge_commit).to be_present
        expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present
      end

      it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do
        merge_commit_id = merge(repository, user, merge_request, message)
1320

J
Jacob Vosmaer 已提交
1321 1322 1323 1324 1325
        expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id)
      end

      it 'removes carriage returns from commit message' do
        merge_commit_id = merge(repository, user, merge_request, message)
1326

J
Jacob Vosmaer 已提交
1327 1328
        expect(repository.commit(merge_commit_id).message).to eq(message.delete("\r"))
      end
1329
    end
1330

J
Jacob Vosmaer 已提交
1331 1332 1333
    context 'with gitaly' do
      it_behaves_like '#merge'
    end
1334

J
Jacob Vosmaer 已提交
1335 1336
    context 'without gitaly', :skip_gitaly_mock do
      it_behaves_like '#merge'
1337 1338
    end

1339 1340
    def merge(repository, user, merge_request, message)
      repository.merge(user, merge_request.diff_head_sha, merge_request, message)
1341
    end
1342 1343
  end

1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371
  describe '#ff_merge' do
    before do
      repository.add_branch(user, 'ff-target', 'feature~5')
    end

    it 'merges the code and return the commit id' do
      merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'ff-target', source_project: project)
      merge_commit_id = repository.ff_merge(user,
                                            merge_request.diff_head_sha,
                                            merge_request.target_branch,
                                            merge_request: merge_request)
      merge_commit = repository.commit(merge_commit_id)

      expect(merge_commit).to be_present
      expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present
    end

    it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do
      merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'ff-target', source_project: project)
      merge_commit_id = repository.ff_merge(user,
                                            merge_request.diff_head_sha,
                                            merge_request.target_branch,
                                            merge_request: merge_request)

      expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id)
    end
  end

1372
  describe '#revert' do
1373 1374 1375 1376 1377 1378 1379 1380 1381
    shared_examples 'reverting a commit' do
      let(:new_image_commit) { repository.commit('33f3729a45c02fc67d00adb1b8bca394b0e761d9') }
      let(:update_image_commit) { repository.commit('2f63565e7aac07bcdadb654e253078b727143ec4') }
      let(:message) { 'revert message' }

      context 'when there is a conflict' do
        it 'raises an error' do
          expect { repository.revert(user, new_image_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
        end
1382 1383
      end

1384 1385 1386
      context 'when commit was already reverted' do
        it 'raises an error' do
          repository.revert(user, update_image_commit, 'master', message)
1387

1388 1389
          expect { repository.revert(user, update_image_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
        end
1390 1391
      end

1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405
      context 'when commit can be reverted' do
        it 'reverts the changes' do
          expect(repository.revert(user, update_image_commit, 'master', message)).to be_truthy
        end
      end

      context 'reverting a merge commit' do
        it 'reverts the changes' do
          merge_commit
          expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).to be_present

          repository.revert(user, merge_commit, 'master', message)
          expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).not_to be_present
        end
1406 1407 1408
      end
    end

1409 1410 1411
    context 'when Gitaly revert feature is enabled' do
      it_behaves_like 'reverting a commit'
    end
1412

1413 1414
    context 'when Gitaly revert feature is disabled', :disable_gitaly do
      it_behaves_like 'reverting a commit'
1415 1416
    end
  end
1417

P
P.S.V.R 已提交
1418
  describe '#cherry_pick' do
1419 1420 1421 1422 1423 1424 1425 1426 1427 1428
    shared_examples 'cherry-picking a commit' do
      let(:conflict_commit) { repository.commit('c642fe9b8b9f28f9225d7ea953fe14e74748d53b') }
      let(:pickable_commit) { repository.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
      let(:pickable_merge) { repository.commit('e56497bb5f03a90a51293fc6d516788730953899') }
      let(:message) { 'cherry-pick message' }

      context 'when there is a conflict' do
        it 'raises an error' do
          expect { repository.cherry_pick(user, conflict_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
        end
P
P.S.V.R 已提交
1429 1430
      end

1431 1432 1433
      context 'when commit was already cherry-picked' do
        it 'raises an error' do
          repository.cherry_pick(user, pickable_commit, 'master', message)
P
P.S.V.R 已提交
1434

1435 1436
          expect { repository.cherry_pick(user, pickable_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
        end
P
P.S.V.R 已提交
1437 1438
      end

1439 1440 1441 1442
      context 'when commit can be cherry-picked' do
        it 'cherry-picks the changes' do
          expect(repository.cherry_pick(user, pickable_commit, 'master', message)).to be_truthy
        end
P
P.S.V.R 已提交
1443 1444
      end

1445 1446 1447
      context 'cherry-picking a merge commit' do
        it 'cherry-picks the changes' do
          expect(repository.blob_at_branch('improve/awesome', 'foo/bar/.gitkeep')).to be_nil
P
P.S.V.R 已提交
1448

1449 1450
          cherry_pick_commit_sha = repository.cherry_pick(user, pickable_merge, 'improve/awesome', message)
          cherry_pick_commit_message = project.commit(cherry_pick_commit_sha).message
1451

1452 1453 1454
          expect(repository.blob_at_branch('improve/awesome', 'foo/bar/.gitkeep')).not_to be_nil
          expect(cherry_pick_commit_message).to eq(message)
        end
P
P.S.V.R 已提交
1455 1456
      end
    end
1457 1458 1459 1460 1461 1462 1463 1464

    context 'when Gitaly cherry_pick feature is enabled' do
      it_behaves_like 'cherry-picking a commit'
    end

    context 'when Gitaly cherry_pick feature is disabled', :disable_gitaly do
      it_behaves_like 'cherry-picking a commit'
    end
P
P.S.V.R 已提交
1465 1466
  end

1467 1468 1469 1470 1471 1472 1473
  describe '#before_delete' do
    describe 'when a repository does not exist' do
      before do
        allow(repository).to receive(:exists?).and_return(false)
      end

      it 'does not flush caches that depend on repository data' do
1474
        expect(repository).not_to receive(:expire_cache)
1475 1476 1477 1478

        repository.before_delete
      end

1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490
      it 'flushes the tags cache' do
        expect(repository).to receive(:expire_tags_cache)

        repository.before_delete
      end

      it 'flushes the branches cache' do
        expect(repository).to receive(:expire_branches_cache)

        repository.before_delete
      end

1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501
      it 'flushes the root ref cache' do
        expect(repository).to receive(:expire_root_ref_cache)

        repository.before_delete
      end

      it 'flushes the emptiness caches' do
        expect(repository).to receive(:expire_emptiness_caches)

        repository.before_delete
      end
1502 1503

      it 'flushes the exists cache' do
1504
        expect(repository).to receive(:expire_exists_cache).twice
1505 1506 1507

        repository.before_delete
      end
1508 1509 1510 1511 1512 1513 1514
    end

    describe 'when a repository exists' do
      before do
        allow(repository).to receive(:exists?).and_return(true)
      end

1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526
      it 'flushes the tags cache' do
        expect(repository).to receive(:expire_tags_cache)

        repository.before_delete
      end

      it 'flushes the branches cache' do
        expect(repository).to receive(:expire_branches_cache)

        repository.before_delete
      end

1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554
      it 'flushes the root ref cache' do
        expect(repository).to receive(:expire_root_ref_cache)

        repository.before_delete
      end

      it 'flushes the emptiness caches' do
        expect(repository).to receive(:expire_emptiness_caches)

        repository.before_delete
      end
    end
  end

  describe '#before_change_head' do
    it 'flushes the branch cache' do
      expect(repository).to receive(:expire_branch_cache)

      repository.before_change_head
    end

    it 'flushes the root ref cache' do
      expect(repository).to receive(:expire_root_ref_cache)

      repository.before_change_head
    end
  end

1555 1556 1557 1558 1559 1560 1561 1562 1563 1564
  describe '#after_change_head' do
    it 'flushes the readme cache' do
      expect(repository).to receive(:expire_method_caches).with([
        :readme,
        :changelog,
        :license,
        :contributing,
        :gitignore,
        :koding,
        :gitlab_ci,
S
Sean McGivern 已提交
1565 1566 1567
        :avatar,
        :issue_template,
        :merge_request_template
1568 1569 1570 1571 1572 1573
      ])

      repository.after_change_head
    end
  end

Y
Yorick Peterse 已提交
1574
  describe '#before_push_tag' do
1575
    it 'flushes the cache' do
1576 1577 1578
      expect(repository).to receive(:expire_statistics_caches)
      expect(repository).to receive(:expire_emptiness_caches)
      expect(repository).to receive(:expire_tags_cache)
1579

Y
Yorick Peterse 已提交
1580
      repository.before_push_tag
1581 1582 1583 1584
    end
  end

  describe '#after_import' do
1585 1586
    it 'flushes and builds the cache' do
      expect(repository).to receive(:expire_content_cache)
1587 1588 1589

      repository.after_import
    end
1590 1591 1592
  end

  describe '#after_push_commit' do
1593
    it 'expires statistics caches' do
1594 1595
      expect(repository).to receive(:expire_statistics_caches)
        .and_call_original
1596

1597 1598 1599
      expect(repository).to receive(:expire_branch_cache)
        .with('master')
        .and_call_original
1600

1601
      repository.after_push_commit('master')
1602 1603 1604 1605
    end
  end

  describe '#after_create_branch' do
1606
    it 'expires the branch caches' do
1607
      expect(repository).to receive(:expire_branches_cache)
1608 1609 1610 1611 1612 1613

      repository.after_create_branch
    end
  end

  describe '#after_remove_branch' do
1614
    it 'expires the branch caches' do
1615
      expect(repository).to receive(:expire_branches_cache)
1616 1617 1618 1619

      repository.after_remove_branch
    end
  end
1620

1621 1622 1623 1624 1625 1626
  describe '#after_create' do
    it 'flushes the exists cache' do
      expect(repository).to receive(:expire_exists_cache)

      repository.after_create
    end
1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638

    it 'flushes the root ref cache' do
      expect(repository).to receive(:expire_root_ref_cache)

      repository.after_create
    end

    it 'flushes the emptiness caches' do
      expect(repository).to receive(:expire_emptiness_caches)

      repository.after_create
    end
1639 1640
  end

1641 1642 1643 1644 1645 1646 1647 1648 1649 1650
  describe "#copy_gitattributes" do
    it 'returns true with a valid ref' do
      expect(repository.copy_gitattributes('master')).to be_truthy
    end

    it 'returns false with an invalid ref' do
      expect(repository.copy_gitattributes('invalid')).to be_falsey
    end
  end

Y
Yorick Peterse 已提交
1651 1652
  describe '#before_remove_tag' do
    it 'flushes the tag cache' do
1653 1654
      expect(repository).to receive(:expire_tags_cache).and_call_original
      expect(repository).to receive(:expire_statistics_caches).and_call_original
Y
Yorick Peterse 已提交
1655 1656 1657 1658 1659 1660 1661

      repository.before_remove_tag
    end
  end

  describe '#branch_count' do
    it 'returns the number of branches' do
D
Douwe Maan 已提交
1662
      expect(repository.branch_count).to be_an(Integer)
K
Cleanup  
Kim "BKC" Carlbäcker 已提交
1663 1664

      # NOTE: Until rugged goes away, make sure rugged and gitaly are in sync
1665
      rugged_count = repository.raw_repository.rugged.branches.count
K
Cleanup  
Kim "BKC" Carlbäcker 已提交
1666

1667
      expect(repository.branch_count).to eq(rugged_count)
Y
Yorick Peterse 已提交
1668 1669 1670 1671 1672
    end
  end

  describe '#tag_count' do
    it 'returns the number of tags' do
D
Douwe Maan 已提交
1673
      expect(repository.tag_count).to be_an(Integer)
K
Cleanup  
Kim "BKC" Carlbäcker 已提交
1674

1675 1676
      # NOTE: Until rugged goes away, make sure rugged and gitaly are in sync
      rugged_count = repository.raw_repository.rugged.tags.count
K
Cleanup  
Kim "BKC" Carlbäcker 已提交
1677

1678
      expect(repository.tag_count).to eq(rugged_count)
Y
Yorick Peterse 已提交
1679 1680 1681
    end
  end

1682
  describe '#expire_branches_cache' do
Y
Yorick Peterse 已提交
1683
    it 'expires the cache' do
1684
      expect(repository).to receive(:expire_method_caches)
1685
        .with(%i(branch_names branch_count has_visible_content?))
1686
        .and_call_original
Y
Yorick Peterse 已提交
1687

1688
      repository.expire_branches_cache
Y
Yorick Peterse 已提交
1689 1690 1691
    end
  end

1692
  describe '#expire_tags_cache' do
Y
Yorick Peterse 已提交
1693
    it 'expires the cache' do
1694 1695 1696
      expect(repository).to receive(:expire_method_caches)
        .with(%i(tag_names tag_count))
        .and_call_original
Y
Yorick Peterse 已提交
1697

1698
      repository.expire_tags_cache
Y
Yorick Peterse 已提交
1699 1700
    end
  end
1701

1702
  describe '#add_tag' do
1703
    let(:user) { build_stubbed(:user) }
1704

1705 1706 1707 1708
    shared_examples 'adding tag' do
      context 'with a valid target' do
        it 'creates the tag' do
          repository.add_tag(user, '8.5', 'master', 'foo')
1709

1710 1711 1712 1713 1714
          tag = repository.find_tag('8.5')
          expect(tag).to be_present
          expect(tag.message).to eq('foo')
          expect(tag.dereferenced_target.id).to eq(repository.commit('master').id)
        end
1715

1716 1717
        it 'returns a Gitlab::Git::Tag object' do
          tag = repository.add_tag(user, '8.5', 'master', 'foo')
1718

1719 1720 1721
          expect(tag).to be_a(Gitlab::Git::Tag)
        end
      end
1722

1723 1724 1725 1726
      context 'with an invalid target' do
        it 'returns false' do
          expect(repository.add_tag(user, '8.5', 'bar', 'foo')).to be false
        end
1727
      end
1728
    end
1729

1730 1731 1732
    context 'when Gitaly operation_user_add_tag feature is enabled' do
      it_behaves_like 'adding tag'
    end
1733

1734
    context 'when Gitaly operation_user_add_tag feature is disabled', :skip_gitaly_mock do
1735 1736 1737
      it_behaves_like 'adding tag'

      it 'passes commit SHA to pre-receive and update hooks and tag SHA to post-receive hook' do
1738 1739 1740
        pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', project)
        update_hook = Gitlab::Git::Hook.new('update', project)
        post_receive_hook = Gitlab::Git::Hook.new('post-receive', project)
1741

1742 1743
        allow(Gitlab::Git::Hook).to receive(:new)
          .and_return(pre_receive_hook, update_hook, post_receive_hook)
1744 1745 1746 1747 1748 1749 1750 1751 1752 1753

        allow(pre_receive_hook).to receive(:trigger).and_call_original
        allow(update_hook).to receive(:trigger).and_call_original
        allow(post_receive_hook).to receive(:trigger).and_call_original

        tag = repository.add_tag(user, '8.5', 'master', 'foo')

        commit_sha = repository.commit('master').id
        tag_sha = tag.target

1754
        expect(pre_receive_hook).to have_received(:trigger)
1755
          .with(anything, anything, anything, commit_sha, anything)
1756
        expect(update_hook).to have_received(:trigger)
1757
          .with(anything, anything, anything, commit_sha, anything)
1758
        expect(post_receive_hook).to have_received(:trigger)
1759
          .with(anything, anything, anything, tag_sha, anything)
1760
      end
1761 1762 1763
    end
  end

1764
  describe '#rm_branch' do
1765 1766 1767 1768 1769 1770 1771 1772
    shared_examples "user deleting a branch" do
      it 'removes a branch' do
        expect(repository).to receive(:before_remove_branch)
        expect(repository).to receive(:after_remove_branch)

        repository.rm_branch(user, 'feature')
      end
    end
1773

1774 1775
    context 'with gitaly enabled' do
      it_behaves_like "user deleting a branch"
1776

1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792
      context 'when pre hooks failed' do
        before do
          allow_any_instance_of(Gitlab::GitalyClient::OperationService)
            .to receive(:user_delete_branch).and_raise(Gitlab::Git::HooksService::PreReceiveError)
        end

        it 'gets an error and does not delete the branch' do
          expect do
            repository.rm_branch(user, 'feature')
          end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)

          expect(repository.find_branch('feature')).not_to be_nil
        end
      end
    end

1793
    context 'with gitaly disabled', :skip_gitaly_mock do
1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833
      it_behaves_like "user deleting a branch"

      let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
      let(:blank_sha) { '0000000000000000000000000000000000000000' }

      context 'when pre hooks were successful' do
        it 'runs without errors' do
          expect_any_instance_of(Gitlab::Git::HooksService).to receive(:execute)
            .with(git_user, repository.raw_repository, old_rev, blank_sha, 'refs/heads/feature')

          expect { repository.rm_branch(user, 'feature') }.not_to raise_error
        end

        it 'deletes the branch' do
          allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])

          expect { repository.rm_branch(user, 'feature') }.not_to raise_error

          expect(repository.find_branch('feature')).to be_nil
        end
      end

      context 'when pre hooks failed' do
        it 'gets an error' do
          allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])

          expect do
            repository.rm_branch(user, 'feature')
          end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
        end

        it 'does not delete the branch' do
          allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])

          expect do
            repository.rm_branch(user, 'feature')
          end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
          expect(repository.find_branch('feature')).not_to be_nil
        end
      end
1834 1835
    end
  end
1836 1837

  describe '#rm_tag' do
1838 1839 1840
    shared_examples 'removing tag' do
      it 'removes a tag' do
        expect(repository).to receive(:before_remove_tag)
1841

1842
        repository.rm_tag(build_stubbed(:user), 'v1.1.0')
L
Lin Jen-Shin 已提交
1843

1844 1845 1846 1847 1848 1849 1850 1851
        expect(repository.find_tag('v1.1.0')).to be_nil
      end
    end

    context 'when Gitaly operation_user_delete_tag feature is enabled' do
      it_behaves_like 'removing tag'
    end

1852
    context 'when Gitaly operation_user_delete_tag feature is disabled', :skip_gitaly_mock do
1853
      it_behaves_like 'removing tag'
1854 1855
    end
  end
1856 1857

  describe '#avatar' do
1858
    it 'returns nil if repo does not exist' do
1859 1860
      expect(repository).to receive(:file_on_head)
        .and_raise(Rugged::ReferenceError)
1861 1862 1863 1864

      expect(repository.avatar).to eq(nil)
    end

1865
    it 'returns the first avatar file found in the repository' do
1866 1867 1868
      expect(repository).to receive(:file_on_head)
        .with(:avatar)
        .and_return(double(:tree, path: 'logo.png'))
1869 1870 1871 1872 1873

      expect(repository.avatar).to eq('logo.png')
    end

    it 'caches the output' do
1874 1875 1876 1877
      expect(repository).to receive(:file_on_head)
        .with(:avatar)
        .once
        .and_return(double(:tree, path: 'logo.png'))
1878

1879
      2.times { expect(repository.avatar).to eq('logo.png') }
1880 1881
    end
  end
1882

1883 1884 1885 1886 1887 1888 1889 1890 1891 1892
  describe '#expire_exists_cache' do
    let(:cache) { repository.send(:cache) }

    it 'expires the cache' do
      expect(cache).to receive(:expire).with(:exists?)

      repository.expire_exists_cache
    end
  end

1893
  describe "#keep_around" do
1894 1895 1896 1897
    it "does not fail if we attempt to reference bad commit" do
      expect(repository.kept_around?('abc1234')).to be_falsey
    end

1898 1899 1900 1901 1902
    it "stores a reference to the specified commit sha so it isn't garbage collected" do
      repository.keep_around(sample_commit.id)

      expect(repository.kept_around?(sample_commit.id)).to be_truthy
    end
1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918

    it "attempting to call keep_around on truncated ref does not fail" do
      repository.keep_around(sample_commit.id)
      ref = repository.send(:keep_around_ref_name, sample_commit.id)
      path = File.join(repository.path, ref)
      # Corrupt the reference
      File.truncate(path, 0)

      expect(repository.kept_around?(sample_commit.id)).to be_falsey

      repository.keep_around(sample_commit.id)

      expect(repository.kept_around?(sample_commit.id)).to be_falsey

      File.delete(path)
    end
1919
  end
1920

1921
  describe '#update_ref' do
1922
    it 'can create a ref' do
1923
      Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_ref, 'refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
1924 1925 1926 1927 1928 1929

      expect(repository.find_branch('foobar')).not_to be_nil
    end

    it 'raises CommitError when the ref update fails' do
      expect do
1930 1931
        Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_ref, 'refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
      end.to raise_error(Gitlab::Git::CommitError)
1932 1933
    end
  end
1934

1935
  describe '#contribution_guide', :use_clean_rails_memory_store_caching do
1936
    it 'returns and caches the output' do
1937 1938 1939 1940
      expect(repository).to receive(:file_on_head)
        .with(:contributing)
        .and_return(Gitlab::Git::Tree.new(path: 'CONTRIBUTING.md'))
        .once
1941 1942

      2.times do
1943 1944
        expect(repository.contribution_guide)
          .to be_an_instance_of(Gitlab::Git::Tree)
1945 1946 1947 1948
      end
    end
  end

1949
  describe '#gitignore', :use_clean_rails_memory_store_caching do
1950
    it 'returns and caches the output' do
1951 1952 1953 1954
      expect(repository).to receive(:file_on_head)
        .with(:gitignore)
        .and_return(Gitlab::Git::Tree.new(path: '.gitignore'))
        .once
1955 1956

      2.times do
1957
        expect(repository.gitignore).to be_an_instance_of(Gitlab::Git::Tree)
1958 1959 1960 1961
      end
    end
  end

1962
  describe '#koding_yml', :use_clean_rails_memory_store_caching do
1963
    it 'returns and caches the output' do
1964 1965 1966 1967
      expect(repository).to receive(:file_on_head)
        .with(:koding)
        .and_return(Gitlab::Git::Tree.new(path: '.koding.yml'))
        .once
1968 1969

      2.times do
1970
        expect(repository.koding_yml).to be_an_instance_of(Gitlab::Git::Tree)
1971 1972 1973 1974
      end
    end
  end

1975
  describe '#readme', :use_clean_rails_memory_store_caching do
1976 1977
    context 'with a non-existing repository' do
      it 'returns nil' do
1978
        allow(repository).to receive(:tree).with(:head).and_return(nil)
1979

1980 1981 1982
        expect(repository.readme).to be_nil
      end
    end
1983

1984
    context 'with an existing repository' do
1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996
      context 'when no README exists' do
        it 'returns nil' do
          allow_any_instance_of(Tree).to receive(:readme).and_return(nil)

          expect(repository.readme).to be_nil
        end
      end

      context 'when a README exists' do
        it 'returns the README' do
          expect(repository.readme).to be_an_instance_of(ReadmeBlob)
        end
1997 1998 1999 2000
      end
    end
  end

2001 2002
  describe '#expire_statistics_caches' do
    it 'expires the caches' do
2003 2004
      expect(repository).to receive(:expire_method_caches)
        .with(%i(size commit_count))
2005

2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020
      repository.expire_statistics_caches
    end
  end

  describe '#expire_method_caches' do
    it 'expires the caches of the given methods' do
      expect_any_instance_of(RepositoryCache).to receive(:expire).with(:readme)
      expect_any_instance_of(RepositoryCache).to receive(:expire).with(:gitignore)

      repository.expire_method_caches(%i(readme gitignore))
    end
  end

  describe '#expire_all_method_caches' do
    it 'expires the caches of all methods' do
2021 2022
      expect(repository).to receive(:expire_method_caches)
        .with(Repository::CACHED_METHODS)
2023 2024 2025

      repository.expire_all_method_caches
    end
2026 2027 2028 2029 2030 2031 2032 2033 2034

    it 'all cache_method definitions are in the lists of method caches' do
      methods = repository.methods.map do |method|
        match = /^_uncached_(.*)/.match(method)
        match[1].to_sym if match
      end.compact

      expect(methods).to match_array(Repository::CACHED_METHODS + Repository::MEMOIZED_CACHED_METHODS)
    end
2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055
  end

  describe '#file_on_head' do
    context 'with a non-existing repository' do
      it 'returns nil' do
        expect(repository).to receive(:tree).with(:head).and_return(nil)

        expect(repository.file_on_head(:readme)).to be_nil
      end
    end

    context 'with a repository that has no blobs' do
      it 'returns nil' do
        expect_any_instance_of(Tree).to receive(:blobs).and_return([])

        expect(repository.file_on_head(:readme)).to be_nil
      end
    end

    context 'with an existing repository' do
      it 'returns a Gitlab::Git::Tree' do
2056 2057
        expect(repository.file_on_head(:readme))
          .to be_an_instance_of(Gitlab::Git::Tree)
2058 2059 2060 2061
      end
    end
  end

2062 2063 2064 2065 2066 2067
  describe '#head_tree' do
    context 'with an existing repository' do
      it 'returns a Tree' do
        expect(repository.head_tree).to be_an_instance_of(Tree)
      end
    end
2068

2069 2070 2071 2072 2073
    context 'with a non-existing repository' do
      it 'returns nil' do
        expect(repository).to receive(:head_commit).and_return(nil)

        expect(repository.head_tree).to be_nil
2074 2075 2076 2077
      end
    end
  end

2078 2079 2080 2081 2082
  describe '#tree' do
    context 'using a non-existing repository' do
      before do
        allow(repository).to receive(:head_commit).and_return(nil)
      end
2083

2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126
      it 'returns nil' do
        expect(repository.tree(:head)).to be_nil
      end

      it 'returns nil when using a path' do
        expect(repository.tree(:head, 'README.md')).to be_nil
      end
    end

    context 'using an existing repository' do
      it 'returns a Tree' do
        expect(repository.tree(:head)).to be_an_instance_of(Tree)
      end
    end
  end

  describe '#size' do
    context 'with a non-existing repository' do
      it 'returns 0' do
        expect(repository).to receive(:exists?).and_return(false)

        expect(repository.size).to eq(0.0)
      end
    end

    context 'with an existing repository' do
      it 'returns the repository size as a Float' do
        expect(repository.size).to be_an_instance_of(Float)
      end
    end
  end

  describe '#commit_count' do
    context 'with a non-existing repository' do
      it 'returns 0' do
        expect(repository).to receive(:root_ref).and_return(nil)

        expect(repository.commit_count).to eq(0)
      end
    end

    context 'with an existing repository' do
      it 'returns the commit count' do
D
Douwe Maan 已提交
2127
        expect(repository.commit_count).to be_an(Integer)
2128 2129 2130 2131
      end
    end
  end

2132
  describe '#commit_count_for_ref' do
2133
    let(:project) { create :project }
2134

2135 2136
    context 'with a non-existing repository' do
      it 'returns 0' do
2137 2138 2139 2140 2141 2142 2143 2144
        expect(project.repository.commit_count_for_ref('master')).to eq(0)
      end
    end

    context 'with empty repository' do
      it 'returns 0' do
        project.create_repository
        expect(project.repository.commit_count_for_ref('master')).to eq(0)
2145 2146 2147 2148 2149 2150 2151 2152 2153 2154
      end
    end

    context 'when searching for the root ref' do
      it 'returns the same count as #commit_count' do
        expect(repository.commit_count_for_ref(repository.root_ref)).to eq(repository.commit_count)
      end
    end
  end

2155
  describe '#cache_method_output', :use_clean_rails_memory_store_caching do
2156 2157
    let(:fallback) { 10 }

2158
    context 'with a non-existing repository' do
2159 2160 2161 2162 2163
      let(:project) { create(:project) } # No repository

      subject do
        repository.cache_method_output(:cats, fallback: fallback) do
          repository.cats_call_stub
2164 2165 2166
        end
      end

2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186
      it 'returns the fallback value' do
        expect(subject).to eq(fallback)
      end

      it 'avoids calling the original method' do
        expect(repository).not_to receive(:cats_call_stub)

        subject
      end
    end

    context 'with a method throwing a non-existing-repository error' do
      subject do
        repository.cache_method_output(:cats, fallback: fallback) do
          raise Gitlab::Git::Repository::NoRepository
        end
      end

      it 'returns the fallback value' do
        expect(subject).to eq(fallback)
2187 2188 2189
      end

      it 'does not cache the data' do
2190
        subject
2191 2192 2193

        expect(repository.instance_variable_defined?(:@cats)).to eq(false)
        expect(repository.send(:cache).exist?(:cats)).to eq(false)
2194 2195
      end
    end
2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216

    context 'with an existing repository' do
      it 'caches the output' do
        object = double

        expect(object).to receive(:number).once.and_return(10)

        2.times do
          val = repository.cache_method_output(:cats) { object.number }

          expect(val).to eq(10)
        end

        expect(repository.send(:cache).exist?(:cats)).to eq(true)
        expect(repository.instance_variable_get(:@cats)).to eq(10)
      end
    end
  end

  describe '#refresh_method_caches' do
    it 'refreshes the caches of the given types' do
2217 2218
      expect(repository).to receive(:expire_method_caches)
        .with(%i(rendered_readme license_blob license_key license))
2219

2220
      expect(repository).to receive(:rendered_readme)
2221 2222
      expect(repository).to receive(:license_blob)
      expect(repository).to receive(:license_key)
2223
      expect(repository).to receive(:license)
2224 2225 2226

      repository.refresh_method_caches(%i(readme license))
    end
2227
  end
D
Douwe Maan 已提交
2228 2229 2230

  describe '#gitlab_ci_yml_for' do
    before do
2231
      repository.create_file(User.last, '.gitlab-ci.yml', 'CONTENT', message: 'Add .gitlab-ci.yml', branch_name: 'master')
D
Douwe Maan 已提交
2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248
    end

    context 'when there is a .gitlab-ci.yml at the commit' do
      it 'returns the content' do
        expect(repository.gitlab_ci_yml_for(repository.commit.sha)).to eq('CONTENT')
      end
    end

    context 'when there is no .gitlab-ci.yml at the commit' do
      it 'returns nil' do
        expect(repository.gitlab_ci_yml_for(repository.commit.parent.sha)).to be_nil
      end
    end
  end

  describe '#route_map_for' do
    before do
2249
      repository.create_file(User.last, '.gitlab/route-map.yml', 'CONTENT', message: 'Add .gitlab/route-map.yml', branch_name: 'master')
D
Douwe Maan 已提交
2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263
    end

    context 'when there is a .gitlab/route-map.yml at the commit' do
      it 'returns the content' do
        expect(repository.route_map_for(repository.commit.sha)).to eq('CONTENT')
      end
    end

    context 'when there is no .gitlab/route-map.yml at the commit' do
      it 'returns nil' do
        expect(repository.route_map_for(repository.commit.parent.sha)).to be_nil
      end
    end
  end
2264

2265
  describe '#ancestor?' do
2266 2267
    let(:commit) { repository.commit }
    let(:ancestor) { commit.parents.first }
2268

2269 2270
    context 'with Gitaly enabled' do
      it 'it is an ancestor' do
2271
        expect(repository.ancestor?(ancestor.id, commit.id)).to eq(true)
2272 2273 2274
      end

      it 'it is not an ancestor' do
2275
        expect(repository.ancestor?(commit.id, ancestor.id)).to eq(false)
2276 2277 2278
      end

      it 'returns false on nil-values' do
2279 2280 2281
        expect(repository.ancestor?(nil, commit.id)).to eq(false)
        expect(repository.ancestor?(ancestor.id, nil)).to eq(false)
        expect(repository.ancestor?(nil, nil)).to eq(false)
2282 2283 2284 2285
      end
    end

    context 'with Gitaly disabled' do
2286
      before do
2287 2288
        allow(Gitlab::GitalyClient).to receive(:enabled?).and_return(false)
        allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:is_ancestor).and_return(false)
2289 2290
      end

2291
      it 'it is an ancestor' do
2292
        expect(repository.ancestor?(ancestor.id, commit.id)).to eq(true)
2293 2294 2295
      end

      it 'it is not an ancestor' do
2296
        expect(repository.ancestor?(commit.id, ancestor.id)).to eq(false)
2297
      end
2298

2299
      it 'returns false on nil-values' do
2300 2301 2302
        expect(repository.ancestor?(nil, commit.id)).to eq(false)
        expect(repository.ancestor?(ancestor.id, nil)).to eq(false)
        expect(repository.ancestor?(nil, nil)).to eq(false)
2303 2304 2305
      end
    end
  end
2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325

  describe 'commit cache' do
    set(:project) { create(:project, :repository) }

    it 'caches based on SHA' do
      # Gets the commit oid, and warms the cache
      oid = project.commit.id

      expect(Gitlab::Git::Commit).not_to receive(:find).once

      project.commit_by(oid: oid)
    end

    it 'caches nil values' do
      expect(Gitlab::Git::Commit).to receive(:find).once

      project.commit_by(oid: '1' * 40)
      project.commit_by(oid: '1' * 40)
    end
  end
2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345

  describe '#raw_repository' do
    subject { repository.raw_repository }

    it 'returns a Gitlab::Git::Repository representation of the repository' do
      expect(subject).to be_a(Gitlab::Git::Repository)
      expect(subject.relative_path).to eq(project.disk_path + '.git')
      expect(subject.gl_repository).to eq("project-#{project.id}")
    end

    context 'with a wiki repository' do
      let(:repository) { project.wiki.repository }

      it 'creates a Gitlab::Git::Repository with the proper attributes' do
        expect(subject).to be_a(Gitlab::Git::Repository)
        expect(subject.relative_path).to eq(project.disk_path + '.wiki.git')
        expect(subject.gl_repository).to eq("wiki-#{project.id}")
      end
    end
  end
2346
end