repository_spec.rb 78.0 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
  describe 'tags_sorted_by' do
H
haseeb 已提交
62 63
    context 'name_desc' do
      subject { repository.tags_sorted_by('name_desc').map(&:name) }
64 65 66 67

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

H
haseeb 已提交
68 69 70 71 72 73
    context 'name_asc' do
      subject { repository.tags_sorted_by('name_asc').map(&:name) }

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

74 75 76 77 78 79 80 81 82 83 84
    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)

85 86
          allow(tag_a).to receive(:dereferenced_target).and_return(double_first)
          allow(tag_b).to receive(:dereferenced_target).and_return(double_last)
87
          allow(repository).to receive(:tags).and_return([tag_a, tag_b])
88 89 90 91 92 93 94 95 96 97 98 99
        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)

100 101
          allow(tag_a).to receive(:dereferenced_target).and_return(double_last)
          allow(tag_b).to receive(:dereferenced_target).and_return(double_first)
102
          allow(repository).to receive(:tags).and_return([tag_a, tag_b])
103 104 105 106
        end

        it { is_expected.to eq(['v1.1.0', 'v1.0.0']) }
      end
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130

      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
131 132 133
    end
  end

134
  describe '#ref_name_for_sha' do
135
    it 'returns the ref' do
136 137
      allow(repository.raw_repository).to receive(:ref_name_for_sha)
        .and_return('refs/environments/production/77')
138

139
      expect(repository.ref_name_for_sha('bla', '0' * 40)).to eq 'refs/environments/production/77'
140 141 142
    end
  end

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
  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

163
  describe '#last_commit_for_path' do
164 165
    shared_examples 'getting last commit for path' do
      subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id }
166

167
      it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
168

169
      describe 'when storage is broken', :broken_storage  do
170 171 172 173 174 175
        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
176 177 178 179 180
    end

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

182
    context 'when Gitaly feature last_commit_for_path is disabled', :skip_gitaly_mock do
183 184
      it_behaves_like 'getting last commit for path'
    end
185
  end
186

H
Hiroyuki Sato 已提交
187
  describe '#last_commit_id_for_path' do
188 189
    shared_examples 'getting last commit ID for path' do
      subject { repository.last_commit_id_for_path(sample_commit.id, '.gitignore') }
H
Hiroyuki Sato 已提交
190

191 192 193 194 195 196 197 198 199 200 201
      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
202

203
      describe 'when storage is broken', :broken_storage  do
204 205 206 207 208 209
        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 已提交
210
    end
H
Hiroyuki Sato 已提交
211

212 213 214
    context 'when Gitaly feature last_commit_for_path is enabled' do
      it_behaves_like 'getting last commit ID for path'
    end
215

216
    context 'when Gitaly feature last_commit_for_path is disabled', :skip_gitaly_mock do
217
      it_behaves_like 'getting last commit ID for path'
H
Hiroyuki Sato 已提交
218 219 220
    end
  end

221 222 223 224
  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

225 226
      repository.commits('master', limit: 1, path: 'README.md')
      repository.commits('master', limit: 1, path: ['README.md'])
227 228 229 230 231
    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

232
      repository.commits('master', limit: 1, path: ['README.md', 'CHANGELOG'])
233 234 235 236 237
    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

238
      repository.commits('master', limit: 1)
239 240 241
    end
  end

242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
  describe '#commits_by' do
    set(:project) { create(:project, :repository) }

    shared_examples 'batch commits fetching' do
      let(:oids) { TestEnv::BRANCH_SHA.values }

      subject { project.repository.commits_by(oids: oids) }

      it 'finds each commit' do
        expect(subject).not_to include(nil)
        expect(subject.size).to eq(oids.size)
      end

      it 'returns only Commit instances' do
        expect(subject).to all( be_a(Commit) )
      end

      context 'when some commits are not found ' do
        let(:oids) do
          ['deadbeef'] + TestEnv::BRANCH_SHA.values.first(10)
        end

        it 'returns only found commits' do
          expect(subject).not_to include(nil)
          expect(subject.size).to eq(10)
        end
      end

      context 'when no oids are passed' do
        let(:oids) { [] }

        it 'does not call #batch_by_oid' do
          expect(Gitlab::Git::Commit).not_to receive(:batch_by_oid)

          subject
        end
      end
    end

    context 'when Gitaly list_commits_by_oid is enabled' do
      it_behaves_like 'batch commits fetching'
    end

    context 'when Gitaly list_commits_by_oid is enabled', :disable_gitaly do
      it_behaves_like 'batch commits fetching'
    end
  end

290
  describe '#find_commits_by_message' do
291 292 293 294 295 296 297 298 299 300 301 302 303 304
    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)
305

306 307
        expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
      end
308 309
    end

310 311 312
    context 'when Gitaly commits_by_message feature is enabled' do
      it_behaves_like 'finding commits by message'
    end
313

314
    context 'when Gitaly commits_by_message feature is disabled', :skip_gitaly_mock do
315
      it_behaves_like 'finding commits by message'
316
    end
317

318
    describe 'when storage is broken', :broken_storage  do
319 320 321 322
      it 'should raise a storage error' do
        expect_to_raise_storage_error { broken_repository.find_commits_by_message('s') }
      end
    end
323 324
  end

325
  describe '#blob_at' do
326 327 328 329 330 331
    context 'blank sha' do
      subject { repository.blob_at(Gitlab::Git::BLANK_SHA, '.gitignore') }

      it { is_expected.to be_nil }
    end
  end
332

333
  describe '#merged_to_root_ref?' do
334
    context 'merged branch without ff' do
335
      subject { repository.merged_to_root_ref?('branch-merged') }
F
Florent (HP) 已提交
336 337 338

      it { is_expected.to be_truthy }
    end
339

340 341
    # If the HEAD was ff then it will be false
    context 'merged with ff' do
F
Florent (HP) 已提交
342 343 344 345
      subject { repository.merged_to_root_ref?('improve/awesome') }

      it { is_expected.to be_truthy }
    end
346

347 348 349 350 351
    context 'not merged branch' do
      subject { repository.merged_to_root_ref?('not-merged-branch') }

      it { is_expected.to be_falsey }
    end
352 353 354 355 356 357

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

      it { is_expected.to be_falsey }
    end
358 359
  end

360
  describe '#can_be_merged?' do
361 362 363
    shared_examples 'can be merged' do
      context 'mergeable branches' do
        subject { repository.can_be_merged?('0b4bc9a49b562e85de7cc9e834518ea6828729b9', 'master') }
364

365 366
        it { is_expected.to be_truthy }
      end
367

368
      context 'non-mergeable branches without conflict sides missing' do
369
        subject { repository.can_be_merged?('bb5206fee213d983da88c47f9cf4cc6caf9c66dc', 'feature') }
370

371 372
        it { is_expected.to be_falsey }
      end
F
Florent (HP) 已提交
373

374 375 376 377 378 379
      context 'non-mergeable branches with conflict sides missing' do
        subject { repository.can_be_merged?('conflict-missing-side', 'conflict-start') }

        it { is_expected.to be_falsey }
      end

380 381
      context 'non merged branch' do
        subject { repository.merged_to_root_ref?('fix') }
F
Florent (HP) 已提交
382

383 384 385 386 387 388 389 390
        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
F
Florent (HP) 已提交
391 392
    end

393 394 395
    context 'when Gitaly can_be_merged feature is enabled' do
      it_behaves_like 'can be merged'
    end
F
Florent (HP) 已提交
396

397 398
    context 'when Gitaly can_be_merged feature is disabled', :disable_gitaly do
      it_behaves_like 'can be merged'
F
Florent (HP) 已提交
399
    end
400 401
  end

402 403 404
  describe '#commit' do
    context 'when ref exists' do
      it 'returns commit object' do
D
Douwe Maan 已提交
405 406
        expect(repository.commit('master'))
          .to be_an_instance_of Commit
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
      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

431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
  describe '#create_hooks' do
    let(:hook_path) { File.join(repository.path_to_repo, 'hooks') }

    it 'symlinks the global hooks directory' do
      repository.create_hooks

      expect(File.symlink?(hook_path)).to be true
      expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
    end

    it 'replaces existing symlink with the right directory' do
      FileUtils.mkdir_p(hook_path)

      expect(File.symlink?(hook_path)).to be false

      repository.create_hooks

      expect(File.symlink?(hook_path)).to be true
      expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
    end
  end

D
Douwe Maan 已提交
453
  describe "#create_dir" do
454 455
    it "commits a change that creates a new directory" do
      expect do
456
        repository.create_dir(user, 'newdir',
457
          message: 'Create newdir', branch_name: 'master')
458
      end.to change { repository.count_commits(ref: 'master') }.by(1)
459 460 461 462 463

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

464
    context "when committing to another project" do
465
      let(:forked_project) { create(:project, :repository) }
466 467 468

      it "creates a fork and commit to the forked project" do
        expect do
469
          repository.create_dir(user, 'newdir',
470
            message: 'Create newdir', branch_name: 'patch',
L
Lin Jen-Shin 已提交
471
            start_branch_name: 'master', start_project: forked_project)
472
        end.to change { repository.count_commits(ref: 'master') }.by(0)
473 474 475 476 477 478 479 480 481

        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

482 483 484
    context "when an author is specified" do
      it "uses the given email/name to set the commit's author" do
        expect do
485
          repository.create_dir(user, 'newdir',
486 487 488
            message: 'Add newdir',
            branch_name: 'master',
            author_email: author_email, author_name: author_name)
489
        end.to change { repository.count_commits(ref: 'master') }.by(1)
490 491 492 493 494 495 496 497 498

        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

499 500
  describe "#create_file" do
    it 'commits new file successfully' do
501
      expect do
502 503 504
        repository.create_file(user, 'NEWCHANGELOG', 'Changelog!',
                               message: 'Create changelog',
                               branch_name: 'master')
505
      end.to change { repository.count_commits(ref: 'master') }.by(1)
506

507
      blob = repository.blob_at('master', 'NEWCHANGELOG')
508

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

512
    it 'creates new file and dir when file_path has a forward slash' do
513
      expect do
514 515
        repository.create_file(user, 'new_dir/new_file.txt', 'File!',
                               message: 'Create new_file with new_dir',
516
                               branch_name: 'master')
517
      end.to change { repository.count_commits(ref: 'master') }.by(1)
518

519 520
      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!')
521 522
    end

523
    it 'respects the autocrlf setting' do
524
      repository.create_file(user, 'hello.txt', "Hello,\r\nWorld",
525
                             message: 'Add hello world',
526
                             branch_name: 'master')
527 528 529 530 531 532

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

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

533 534 535
    context "when an author is specified" do
      it "uses the given email/name to set the commit's author" do
        expect do
536
          repository.create_file(user, 'NEWREADME', 'README!',
537 538 539 540
                                 message: 'Add README',
                                 branch_name: 'master',
                                 author_email: author_email,
                                 author_name: author_name)
541
        end.to change { repository.count_commits(ref: 'master') }.by(1)
542 543 544 545 546 547 548

        last_commit = repository.commit

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

551
  describe "#update_file" do
552 553 554 555 556
    it 'updates file successfully' do
      expect do
        repository.update_file(user, 'CHANGELOG', 'Changelog!',
                               message: 'Update changelog',
                               branch_name: 'master')
557
      end.to change { repository.count_commits(ref: 'master') }.by(1)
558 559 560 561 562 563

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

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

T
tiagonbotelho 已提交
564
    it 'updates filename successfully' do
565 566
      expect do
        repository.update_file(user, 'NEWLICENSE', 'Copyright!',
567
                                     branch_name: 'master',
T
tiagonbotelho 已提交
568
                                     previous_path: 'LICENSE',
569
                                     message: 'Changes filename')
570
      end.to change { repository.count_commits(ref: 'master') }.by(1)
T
tiagonbotelho 已提交
571 572 573 574 575 576

      files = repository.ls_files('master')

      expect(files).not_to include('LICENSE')
      expect(files).to include('NEWLICENSE')
    end
577 578 579 580

    context "when an author is specified" do
      it "uses the given email/name to set the commit's author" do
        expect do
581 582 583 584 585 586
          repository.update_file(user, 'README', 'Updated README!',
                                 branch_name: 'master',
                                 previous_path: 'README',
                                 message: 'Update README',
                                 author_email: author_email,
                                 author_name: author_name)
587
        end.to change { repository.count_commits(ref: 'master') }.by(1)
588 589 590 591 592 593 594 595 596

        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 已提交
597
  describe "#delete_file" do
598 599
    it 'removes file successfully' do
      expect do
600
        repository.delete_file(user, 'README',
601
          message: 'Remove README', branch_name: 'master')
602
      end.to change { repository.count_commits(ref: 'master') }.by(1)
603 604 605 606 607 608 609

      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
610
          repository.delete_file(user, 'README',
611 612
            message: 'Remove README', branch_name: 'master',
            author_email: author_email, author_name: author_name)
613
        end.to change { repository.count_commits(ref: 'master') }.by(1)
614 615 616 617 618 619 620 621 622

        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

V
Valery Sizov 已提交
623 624
  describe "search_files_by_content" do
    let(:results) { repository.search_files_by_content('feature', 'master') }
625 626 627 628
    subject { results }

    it { is_expected.to be_an Array }

629
    it 'regex-escapes the query string' do
V
Valery Sizov 已提交
630
      results = repository.search_files_by_content("test\\", 'master')
631 632 633 634

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

635
    it 'properly handles an unmatched parenthesis' do
V
Valery Sizov 已提交
636
      results = repository.search_files_by_content("test(", 'master')
637 638 639 640

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

V
Valery Sizov 已提交
641
    it 'properly handles when query is not present' do
V
Valery Sizov 已提交
642
      results = repository.search_files_by_content('', 'master')
V
Valery Sizov 已提交
643 644 645 646 647

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

    it 'properly handles query when repo is empty' do
648
      repository = create(:project, :empty_repo).repository
V
Valery Sizov 已提交
649
      results = repository.search_files_by_content('test', 'master')
V
Valery Sizov 已提交
650 651 652 653

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

654
    describe 'when storage is broken', :broken_storage  do
655 656 657 658 659 660 661
      it 'should raise a storage error' do
        expect_to_raise_storage_error do
          broken_repository.search_files_by_content('feature', 'master')
        end
      end
    end

662 663 664 665
    describe 'result' do
      subject { results.first }

      it { is_expected.to be_an String }
666
      it { expect(subject.lines[2]).to eq("master:CHANGELOG\x00190\x00  - Feature: Replace teams with group membership\n") }
667 668
    end
  end
Z
Zeger-Jan van de Weg 已提交
669

V
Valery Sizov 已提交
670 671 672 673 674 675 676
  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

A
Andrew McCallum 已提交
677 678
    it 'ignores leading slashes' do
      results = repository.search_files_by_name('/files', 'master')
679

680 681 682
      expect(results.first).to eq('files/html/500.html')
    end

683
    it 'properly handles when query is only slashes' do
684
      results = repository.search_files_by_name('//', 'master')
685

686
      expect(results).to match_array([])
687 688
    end

V
Valery Sizov 已提交
689 690 691 692 693 694 695
    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
696
      repository = create(:project, :empty_repo).repository
V
Valery Sizov 已提交
697 698 699 700 701

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

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

703
    describe 'when storage is broken', :broken_storage  do
704 705 706 707 708 709 710
      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
711
    let(:broken_repository) { create(:project, :broken_storage).repository }
712

713
    describe 'when storage is broken', :broken_storage  do
714
      it 'should raise a storage error' do
715 716 717
        expect_to_raise_storage_error do
          broken_repository.fetch_ref(broken_repository, source_ref: '1', target_ref: '2')
        end
718 719
      end
    end
V
Valery Sizov 已提交
720 721
  end

722
  describe '#create_ref' do
723
    it 'redirects the call to write_ref' do
724 725
      ref, ref_path = '1', '2'

726
      expect(repository.raw_repository).to receive(:write_ref).with(ref_path, ref)
727 728 729 730 731

      repository.create_ref(ref, ref_path)
    end
  end

732
  describe "#changelog", :use_clean_rails_memory_store_caching do
733 734 735
    it 'accepts changelog' do
      expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('changelog')])

D
Douwe Maan 已提交
736
      expect(repository.changelog.path).to eq('changelog')
737 738 739 740 741
    end

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

D
Douwe Maan 已提交
742
      expect(repository.changelog.path).to eq('news')
743 744 745 746 747
    end

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

D
Douwe Maan 已提交
748
      expect(repository.changelog.path).to eq('history')
749 750 751 752 753
    end

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

D
Douwe Maan 已提交
754
      expect(repository.changelog.path).to eq('changes')
755 756 757 758 759
    end

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

D
Douwe Maan 已提交
760
      expect(repository.changelog.path).to eq('CHANGELOG')
761 762 763
    end
  end

764
  describe "#license_blob", :use_clean_rails_memory_store_caching do
765
    before do
766
      repository.delete_file(
767
        user, 'LICENSE', message: 'Remove LICENSE', branch_name: 'master')
768 769
    end

770
    it 'handles when HEAD points to non-existent ref' do
771
      repository.create_file(
772
        user, 'LICENSE', 'Copyright!',
773
        message: 'Add LICENSE', branch_name: 'master')
774

775 776
      allow(repository).to receive(:file_on_head)
        .and_raise(Rugged::ReferenceError)
777 778 779 780

      expect(repository.license_blob).to be_nil
    end

781
    it 'looks in the root_ref only' do
782
      repository.delete_file(user, 'LICENSE',
783
        message: 'Remove LICENSE', branch_name: 'markdown')
784
      repository.create_file(user, 'LICENSE',
785
        Licensee::License.new('mit').content,
786
        message: 'Add LICENSE', branch_name: 'markdown')
787 788 789 790

      expect(repository.license_blob).to be_nil
    end

791
    it 'detects license file with no recognizable open-source license content' do
792 793
      repository.create_file(user, 'LICENSE', 'Copyright!',
        message: 'Add LICENSE', branch_name: 'master')
794

D
Douwe Maan 已提交
795
      expect(repository.license_blob.path).to eq('LICENSE')
796 797
    end

798 799
    %w[LICENSE LICENCE LiCensE LICENSE.md LICENSE.foo COPYING COPYING.md].each do |filename|
      it "detects '#{filename}'" do
800
        repository.create_file(user, filename,
801
          Licensee::License.new('mit').content,
802
          message: "Add #{filename}", branch_name: 'master')
803

804 805
        expect(repository.license_blob.name).to eq(filename)
      end
806 807 808
    end
  end

809
  describe '#license_key', :use_clean_rails_memory_store_caching do
810
    before do
811
      repository.delete_file(user, 'LICENSE',
812
        message: 'Remove LICENSE', branch_name: 'master')
813
    end
Z
Zeger-Jan van de Weg 已提交
814

815
    it 'returns nil when no license is detected' do
816 817 818
      expect(repository.license_key).to be_nil
    end

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

822 823 824
      expect(repository.license_key).to be_nil
    end

D
Douwe Maan 已提交
825
    it 'returns nil when the content is not recognizable' do
826 827
      repository.create_file(user, 'LICENSE', 'Copyright!',
        message: 'Add LICENSE', branch_name: 'master')
828 829

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

832
    it 'returns the license key' do
833
      repository.create_file(user, 'LICENSE',
834
        Licensee::License.new('mit').content,
835
        message: 'Add LICENSE', branch_name: 'master')
836

837
      expect(repository.license_key).to eq('mit')
838
    end
Z
Zeger-Jan van de Weg 已提交
839
  end
840

D
Douwe Maan 已提交
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873
  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

874
  describe "#gitlab_ci_yml", :use_clean_rails_memory_store_caching do
875 876 877 878
    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 已提交
879
      expect(repository.gitlab_ci_yml.path).to eq('.gitlab-ci.yml')
880 881 882 883 884 885 886 887
    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
888
      allow(repository).to receive(:file_on_head).and_raise(Rugged::ReferenceError)
889 890 891 892
      expect(repository.gitlab_ci_yml).to be_nil
    end
  end

893
  describe '#add_branch' do
894 895
    let(:branch_name) { 'new_feature' }
    let(:target) { 'master' }
896

897
    subject { repository.add_branch(user, branch_name, target) }
898

899 900 901 902 903
    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)
904

905 906
        subject
      end
907

908 909 910
      it 'creates_the_branch' do
        expect(subject.name).to eq(branch_name)
        expect(repository.find_branch(branch_name)).not_to be_nil
911
      end
912

913 914
      context 'with a non-existing target' do
        let(:target) { 'fake-target' }
915

916 917 918 919
        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
920
      end
921 922
    end

923
    context 'with Gitaly disabled', :skip_gitaly_mock do
924 925 926 927
      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)
928

929 930 931 932 933 934 935 936 937 938 939 940 941 942
          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
943 944
      end

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

949 950 951 952 953 954 955 956 957
          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
958 959 960 961
      end
    end
  end

962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979
  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

980
  describe '#update_branch_with_hooks' do
981
    let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
982
    let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev
983 984 985
    let(:updating_ref) { 'refs/heads/feature' }
    let(:target_project) { project }
    let(:target_repository) { target_project.repository }
986

987
    context 'when pre hooks were successful' do
988
      before do
989 990
        service = Gitlab::Git::HooksService.new
        expect(Gitlab::Git::HooksService).to receive(:new).and_return(service)
991
        expect(service).to receive(:execute)
992
          .with(git_user, target_repository.raw_repository, old_rev, new_rev, updating_ref)
993
          .and_yield(service).and_return(true)
994
      end
995

996
      it 'runs without errors' do
997
        expect do
998
          Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('feature') do
999 1000
            new_rev
          end
1001 1002
        end.not_to raise_error
      end
1003

1004
      it 'ensures the autocrlf Git option is set to :input' do
1005
        service = Gitlab::Git::OperationService.new(git_user, repository.raw_repository)
1006 1007

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

1009
        service.with_branch('feature') { new_rev }
1010
      end
1011 1012 1013

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

1016
          Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('feature') do
1017 1018 1019
            new_rev
          end

1020
          expect(repository.find_branch('feature').dereferenced_target.id).to eq(new_rev)
1021 1022
        end
      end
1023 1024 1025 1026 1027 1028 1029 1030

      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
1031
          expect(target_project.repository.raw_repository).to receive(:fetch_ref)
1032 1033
            .and_call_original

1034
          Gitlab::Git::OperationService.new(git_user, target_repository.raw_repository)
1035 1036
            .with_branch(
              'master',
1037
              start_repository: project.repository.raw_repository,
1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
              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)

1050
          Gitlab::Git::OperationService.new(git_user, target_repository.raw_repository)
1051
            .with_branch('feature', start_repository: project.repository.raw_repository) { new_rev }
1052 1053
        end
      end
1054 1055
    end

1056 1057 1058 1059
    context 'when temporary ref failed to be created from other project' do
      let(:target_project) { create(:project, :empty_repo) }

      before do
1060
        expect(target_project.repository.raw_repository).to receive(:run_git)
1061 1062 1063 1064 1065 1066 1067 1068
      end

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

        expect do
1069
          Gitlab::Git::OperationService.new(git_user, target_project.repository.raw_repository)
1070
            .with_branch('feature',
1071
                         start_repository: project.repository.raw_repository,
1072 1073 1074 1075 1076
                         &:itself)
        end.to raise_reference_error
      end
    end

1077
    context 'when the update adds more than one commit' do
1078
      let(:old_rev) { '33f3729a45c02fc67d00adb1b8bca394b0e761d9' }
1079

1080
      it 'runs without errors' do
1081
        # old_rev is an ancestor of new_rev
1082
        expect(repository.merge_base(old_rev, new_rev)).to eq(old_rev)
1083 1084 1085 1086

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

1087 1088 1089
        branch = 'feature-ff-target'
        repository.add_branch(user, branch, old_rev)

1090
        expect do
1091
          Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch(branch) do
1092 1093 1094
            new_rev
          end
        end.not_to raise_error
1095 1096 1097 1098
      end
    end

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

1102
      it 'raises an exception' do
1103
        # The 'master' branch is NOT an ancestor of new_rev.
1104
        expect(repository.merge_base(old_rev, new_rev)).not_to eq(old_rev)
1105 1106 1107 1108

        # 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
1109
          Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch(branch) do
1110 1111
            new_rev
          end
1112
        end.to raise_error(Gitlab::Git::CommitError)
1113 1114 1115
      end
    end

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

        expect do
1121
          Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('feature') do
1122 1123
            new_rev
          end
1124
        end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
1125 1126
      end
    end
1127 1128 1129 1130 1131 1132

    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

1133 1134
      subject do
        Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('new-feature') do
J
Jacob Vosmaer 已提交
1135
          new_rev
J
Jacob Vosmaer 已提交
1136
        end
1137
      end
1138 1139 1140 1141 1142

      it 'returns branch_created as true' do
        expect(subject).not_to be_repo_created
        expect(subject).to     be_branch_created
      end
1143 1144 1145 1146 1147 1148 1149 1150
    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
1151
        empty_repository = create(:project, :empty_repo).repository
1152 1153 1154 1155 1156 1157

        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)

1158
        empty_repository.create_file(user, 'CHANGELOG', 'Changelog!',
1159
                                     message: 'Updates file content',
1160
                                     branch_name: 'master')
1161 1162
      end
    end
1163
  end
1164

1165
  shared_examples 'repo exists check' do
1166 1167 1168 1169
    it 'returns true when a repository exists' do
      expect(repository.exists?).to eq(true)
    end

1170
    it 'returns false if no full path can be constructed' do
1171
      allow(repository).to receive(:full_path).and_return(nil)
1172 1173 1174

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

1176
    context 'with broken storage', :broken_storage do
1177 1178 1179 1180
      it 'should raise a storage error' do
        expect_to_raise_storage_error { broken_repository.exists? }
      end
    end
1181
  end
1182

1183 1184 1185 1186
  describe '#exists?' do
    context 'when repository_exists is disabled' do
      it_behaves_like 'repo exists check'
    end
1187

1188
    context 'when repository_exists is enabled', :skip_gitaly_mock do
1189
      it_behaves_like 'repo exists check'
1190
    end
1191 1192
  end

1193
  describe '#has_visible_content?' do
1194 1195 1196 1197 1198 1199 1200
    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
1201

1202 1203
    context 'when true' do
      let(:result) { true }
1204

1205 1206 1207 1208 1209
      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
1210 1211
    end

1212 1213
    context 'when false' do
      let(:result) { false }
1214

1215 1216 1217 1218
      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)
1219 1220 1221 1222
      end
    end
  end

1223 1224 1225 1226 1227 1228 1229 1230 1231
  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

1232 1233 1234 1235 1236 1237 1238 1239 1240
  describe '#tag_exists?' do
    it 'uses tag_names' do
      allow(repository).to receive(:tag_names).and_return(['foobar'])

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

1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256
  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

1257 1258 1259 1260 1261 1262 1263
  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
1264
        Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_autocrlf_option)
1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275

        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
1276 1277
        expect(repository.raw_repository).not_to receive(:autocrlf=)
          .with(:input)
1278

1279
        Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_autocrlf_option)
1280 1281 1282 1283
      end
    end
  end

1284 1285 1286 1287
  describe '#empty?' do
    let(:empty_repository) { create(:project_empty_repo).repository }

    it 'returns true for an empty repository' do
1288
      expect(empty_repository).to be_empty
1289 1290 1291
    end

    it 'returns false for a non-empty repository' do
1292
      expect(repository).not_to be_empty
1293 1294 1295
    end

    it 'caches the output' do
1296
      expect(repository.raw_repository).to receive(:has_visible_content?).once
1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308

      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
1309 1310 1311
      expect(repository.raw_repository).to receive(:root_ref)
        .once
        .and_return('master')
1312 1313 1314 1315 1316 1317 1318 1319 1320 1321

      repository.root_ref
      repository.root_ref
    end
  end

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

1322 1323 1324
      expect(repository.raw_repository).to receive(:root_ref)
        .once
        .and_return('foo')
1325 1326 1327 1328 1329 1330 1331

      repository.expire_root_ref_cache

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

Y
Yorick Peterse 已提交
1332
  describe '#expire_branch_cache' do
1333 1334 1335 1336 1337
    # 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
1338 1339 1340
      expect(cache).to receive(:expire)
        .at_least(repository.branches.length * 2)
        .times
1341 1342 1343 1344 1345

      repository.expire_branch_cache
    end

    it 'expires the cache for all branches when the root branch is given' do
1346 1347 1348
      expect(cache).to receive(:expire)
        .at_least(repository.branches.length * 2)
        .times
1349 1350 1351 1352 1353

      repository.expire_branch_cache(repository.root_ref)
    end

    it 'expires the cache for a specific branch' do
1354
      expect(cache).to receive(:expire).twice
1355 1356 1357 1358

      repository.expire_branch_cache('foo')
    end
  end
1359

1360 1361 1362
  describe '#expire_emptiness_caches' do
    let(:cache) { repository.send(:cache) }

1363 1364 1365
    it 'expires the caches for an empty repository' do
      allow(repository).to receive(:empty?).and_return(true)

1366
      expect(cache).to receive(:expire).with(:empty?)
1367
      expect(cache).to receive(:expire).with(:has_visible_content?)
1368 1369 1370

      repository.expire_emptiness_caches
    end
1371 1372 1373 1374 1375

    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?)
1376
      expect(cache).not_to receive(:expire).with(:has_visible_content?)
1377 1378 1379

      repository.expire_emptiness_caches
    end
1380 1381
  end

1382
  describe 'skip_merges option' do
1383
    subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map { |k| k.id } }
1384 1385 1386

    it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') }
  end
1387 1388

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

1391
    let(:message) { 'Test \r\n\r\n message' }
1392

J
Jacob Vosmaer 已提交
1393 1394 1395 1396 1397 1398 1399 1400
    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)
1401

J
Jacob Vosmaer 已提交
1402 1403 1404 1405 1406
        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)
1407

J
Jacob Vosmaer 已提交
1408 1409
        expect(repository.commit(merge_commit_id).message).to eq(message.delete("\r"))
      end
1410
    end
1411

J
Jacob Vosmaer 已提交
1412 1413 1414
    context 'with gitaly' do
      it_behaves_like '#merge'
    end
1415

J
Jacob Vosmaer 已提交
1416 1417
    context 'without gitaly', :skip_gitaly_mock do
      it_behaves_like '#merge'
1418 1419
    end

1420 1421
    def merge(repository, user, merge_request, message)
      repository.merge(user, merge_request.diff_head_sha, merge_request, message)
1422
    end
1423 1424
  end

1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452
  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

1453
  describe '#revert' do
1454 1455 1456 1457 1458 1459 1460 1461 1462
    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
1463 1464
      end

1465 1466 1467
      context 'when commit was already reverted' do
        it 'raises an error' do
          repository.revert(user, update_image_commit, 'master', message)
1468

1469 1470
          expect { repository.revert(user, update_image_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
        end
1471 1472
      end

1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486
      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
1487 1488 1489
      end
    end

1490 1491 1492
    context 'when Gitaly revert feature is enabled' do
      it_behaves_like 'reverting a commit'
    end
1493

1494 1495
    context 'when Gitaly revert feature is disabled', :disable_gitaly do
      it_behaves_like 'reverting a commit'
1496 1497
    end
  end
1498

P
P.S.V.R 已提交
1499
  describe '#cherry_pick' do
1500 1501 1502 1503 1504 1505 1506 1507 1508 1509
    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 已提交
1510 1511
      end

1512 1513 1514
      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 已提交
1515

1516 1517
          expect { repository.cherry_pick(user, pickable_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
        end
P
P.S.V.R 已提交
1518 1519
      end

1520 1521 1522 1523
      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 已提交
1524 1525
      end

1526 1527 1528
      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 已提交
1529

1530 1531
          cherry_pick_commit_sha = repository.cherry_pick(user, pickable_merge, 'improve/awesome', message)
          cherry_pick_commit_message = project.commit(cherry_pick_commit_sha).message
1532

1533 1534 1535
          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 已提交
1536 1537
      end
    end
1538 1539 1540 1541 1542 1543 1544 1545

    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 已提交
1546 1547
  end

1548 1549 1550 1551 1552 1553 1554
  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
1555
        expect(repository).not_to receive(:expire_cache)
1556 1557 1558 1559

        repository.before_delete
      end

1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571
      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

1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582
      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
1583 1584

      it 'flushes the exists cache' do
1585
        expect(repository).to receive(:expire_exists_cache).twice
1586 1587 1588

        repository.before_delete
      end
1589 1590 1591 1592 1593 1594 1595
    end

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

1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607
      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

1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635
      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

1636 1637 1638 1639 1640 1641 1642 1643 1644 1645
  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 已提交
1646 1647 1648
        :avatar,
        :issue_template,
        :merge_request_template
1649 1650 1651 1652 1653 1654
      ])

      repository.after_change_head
    end
  end

Y
Yorick Peterse 已提交
1655
  describe '#before_push_tag' do
1656
    it 'flushes the cache' do
1657 1658 1659
      expect(repository).to receive(:expire_statistics_caches)
      expect(repository).to receive(:expire_emptiness_caches)
      expect(repository).to receive(:expire_tags_cache)
1660

Y
Yorick Peterse 已提交
1661
      repository.before_push_tag
1662 1663 1664 1665
    end
  end

  describe '#after_import' do
1666 1667
    it 'flushes and builds the cache' do
      expect(repository).to receive(:expire_content_cache)
1668 1669 1670

      repository.after_import
    end
1671 1672 1673
  end

  describe '#after_push_commit' do
1674
    it 'expires statistics caches' do
1675 1676
      expect(repository).to receive(:expire_statistics_caches)
        .and_call_original
1677

1678 1679 1680
      expect(repository).to receive(:expire_branch_cache)
        .with('master')
        .and_call_original
1681

1682
      repository.after_push_commit('master')
1683 1684 1685 1686
    end
  end

  describe '#after_create_branch' do
1687
    it 'expires the branch caches' do
1688
      expect(repository).to receive(:expire_branches_cache)
1689 1690 1691 1692 1693 1694

      repository.after_create_branch
    end
  end

  describe '#after_remove_branch' do
1695
    it 'expires the branch caches' do
1696
      expect(repository).to receive(:expire_branches_cache)
1697 1698 1699 1700

      repository.after_remove_branch
    end
  end
1701

1702 1703 1704 1705 1706 1707
  describe '#after_create' do
    it 'flushes the exists cache' do
      expect(repository).to receive(:expire_exists_cache)

      repository.after_create
    end
1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719

    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
1720 1721
  end

1722 1723 1724 1725 1726 1727 1728 1729 1730 1731
  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 已提交
1732 1733
  describe '#before_remove_tag' do
    it 'flushes the tag cache' do
1734 1735
      expect(repository).to receive(:expire_tags_cache).and_call_original
      expect(repository).to receive(:expire_statistics_caches).and_call_original
Y
Yorick Peterse 已提交
1736 1737 1738 1739 1740 1741 1742

      repository.before_remove_tag
    end
  end

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

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

1748
      expect(repository.branch_count).to eq(rugged_count)
Y
Yorick Peterse 已提交
1749 1750 1751 1752 1753
    end
  end

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

1756 1757
      # 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 已提交
1758

1759
      expect(repository.tag_count).to eq(rugged_count)
Y
Yorick Peterse 已提交
1760 1761 1762
    end
  end

1763
  describe '#expire_branches_cache' do
Y
Yorick Peterse 已提交
1764
    it 'expires the cache' do
1765
      expect(repository).to receive(:expire_method_caches)
1766
        .with(%i(branch_names branch_count has_visible_content?))
1767
        .and_call_original
Y
Yorick Peterse 已提交
1768

1769
      repository.expire_branches_cache
Y
Yorick Peterse 已提交
1770 1771 1772
    end
  end

1773
  describe '#expire_tags_cache' do
Y
Yorick Peterse 已提交
1774
    it 'expires the cache' do
1775 1776 1777
      expect(repository).to receive(:expire_method_caches)
        .with(%i(tag_names tag_count))
        .and_call_original
Y
Yorick Peterse 已提交
1778

1779
      repository.expire_tags_cache
Y
Yorick Peterse 已提交
1780 1781
    end
  end
1782

1783
  describe '#add_tag' do
1784
    let(:user) { build_stubbed(:user) }
1785

1786 1787 1788 1789
    shared_examples 'adding tag' do
      context 'with a valid target' do
        it 'creates the tag' do
          repository.add_tag(user, '8.5', 'master', 'foo')
1790

1791 1792 1793 1794 1795
          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
1796

1797 1798
        it 'returns a Gitlab::Git::Tag object' do
          tag = repository.add_tag(user, '8.5', 'master', 'foo')
1799

1800 1801 1802
          expect(tag).to be_a(Gitlab::Git::Tag)
        end
      end
1803

1804 1805 1806 1807
      context 'with an invalid target' do
        it 'returns false' do
          expect(repository.add_tag(user, '8.5', 'bar', 'foo')).to be false
        end
1808
      end
1809
    end
1810

1811 1812 1813
    context 'when Gitaly operation_user_add_tag feature is enabled' do
      it_behaves_like 'adding tag'
    end
1814

1815
    context 'when Gitaly operation_user_add_tag feature is disabled', :skip_gitaly_mock do
1816 1817 1818
      it_behaves_like 'adding tag'

      it 'passes commit SHA to pre-receive and update hooks and tag SHA to post-receive hook' do
1819 1820 1821
        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)
1822

1823 1824
        allow(Gitlab::Git::Hook).to receive(:new)
          .and_return(pre_receive_hook, update_hook, post_receive_hook)
1825 1826 1827 1828 1829 1830 1831 1832 1833 1834

        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

1835
        expect(pre_receive_hook).to have_received(:trigger)
1836
          .with(anything, anything, anything, commit_sha, anything)
1837
        expect(update_hook).to have_received(:trigger)
1838
          .with(anything, anything, anything, commit_sha, anything)
1839
        expect(post_receive_hook).to have_received(:trigger)
1840
          .with(anything, anything, anything, tag_sha, anything)
1841
      end
1842 1843 1844
    end
  end

1845
  describe '#rm_branch' do
1846 1847 1848 1849 1850 1851 1852 1853
    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
1854

1855 1856
    context 'with gitaly enabled' do
      it_behaves_like "user deleting a branch"
1857

1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873
      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

1874
    context 'with gitaly disabled', :skip_gitaly_mock do
1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914
      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
1915 1916
    end
  end
1917 1918

  describe '#rm_tag' do
1919 1920 1921
    shared_examples 'removing tag' do
      it 'removes a tag' do
        expect(repository).to receive(:before_remove_tag)
1922

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

1925 1926 1927 1928 1929 1930 1931 1932
        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

1933
    context 'when Gitaly operation_user_delete_tag feature is disabled', :skip_gitaly_mock do
1934
      it_behaves_like 'removing tag'
1935 1936
    end
  end
1937 1938

  describe '#avatar' do
1939
    it 'returns nil if repo does not exist' do
1940 1941
      expect(repository).to receive(:file_on_head)
        .and_raise(Rugged::ReferenceError)
1942 1943 1944 1945

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

1946
    it 'returns the first avatar file found in the repository' do
1947 1948 1949
      expect(repository).to receive(:file_on_head)
        .with(:avatar)
        .and_return(double(:tree, path: 'logo.png'))
1950 1951 1952 1953 1954

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

    it 'caches the output' do
1955 1956 1957 1958
      expect(repository).to receive(:file_on_head)
        .with(:avatar)
        .once
        .and_return(double(:tree, path: 'logo.png'))
1959

1960
      2.times { expect(repository.avatar).to eq('logo.png') }
1961 1962
    end
  end
1963

1964 1965 1966 1967 1968 1969 1970 1971 1972 1973
  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

1974
  describe "#keep_around" do
1975 1976 1977 1978
    it "does not fail if we attempt to reference bad commit" do
      expect(repository.kept_around?('abc1234')).to be_falsey
    end

1979 1980 1981 1982 1983
    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
1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999

    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
2000
  end
2001

2002
  describe '#update_ref' do
2003
    it 'can create a ref' do
2004
      Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_ref, 'refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
2005 2006 2007 2008 2009 2010

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

    it 'raises CommitError when the ref update fails' do
      expect do
2011 2012
        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)
2013 2014
    end
  end
2015

2016
  describe '#contribution_guide', :use_clean_rails_memory_store_caching do
2017
    it 'returns and caches the output' do
2018 2019 2020 2021
      expect(repository).to receive(:file_on_head)
        .with(:contributing)
        .and_return(Gitlab::Git::Tree.new(path: 'CONTRIBUTING.md'))
        .once
2022 2023

      2.times do
2024 2025
        expect(repository.contribution_guide)
          .to be_an_instance_of(Gitlab::Git::Tree)
2026 2027 2028 2029
      end
    end
  end

2030
  describe '#gitignore', :use_clean_rails_memory_store_caching do
2031
    it 'returns and caches the output' do
2032 2033 2034 2035
      expect(repository).to receive(:file_on_head)
        .with(:gitignore)
        .and_return(Gitlab::Git::Tree.new(path: '.gitignore'))
        .once
2036 2037

      2.times do
2038
        expect(repository.gitignore).to be_an_instance_of(Gitlab::Git::Tree)
2039 2040 2041 2042
      end
    end
  end

2043
  describe '#koding_yml', :use_clean_rails_memory_store_caching do
2044
    it 'returns and caches the output' do
2045 2046 2047 2048
      expect(repository).to receive(:file_on_head)
        .with(:koding)
        .and_return(Gitlab::Git::Tree.new(path: '.koding.yml'))
        .once
2049 2050

      2.times do
2051
        expect(repository.koding_yml).to be_an_instance_of(Gitlab::Git::Tree)
2052 2053 2054 2055
      end
    end
  end

2056
  describe '#readme', :use_clean_rails_memory_store_caching do
2057 2058
    context 'with a non-existing repository' do
      it 'returns nil' do
2059
        allow(repository).to receive(:tree).with(:head).and_return(nil)
2060

2061 2062 2063
        expect(repository.readme).to be_nil
      end
    end
2064

2065
    context 'with an existing repository' do
2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077
      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
2078 2079 2080 2081
      end
    end
  end

2082 2083
  describe '#expire_statistics_caches' do
    it 'expires the caches' do
2084 2085
      expect(repository).to receive(:expire_method_caches)
        .with(%i(size commit_count))
2086

2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101
      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
2102 2103
      expect(repository).to receive(:expire_method_caches)
        .with(Repository::CACHED_METHODS)
2104 2105 2106

      repository.expire_all_method_caches
    end
2107 2108 2109 2110 2111 2112 2113 2114 2115

    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
2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136
  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
2137 2138
        expect(repository.file_on_head(:readme))
          .to be_an_instance_of(Gitlab::Git::Tree)
2139 2140 2141 2142
      end
    end
  end

2143 2144 2145 2146 2147 2148
  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
2149

2150 2151 2152 2153 2154
    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
2155 2156 2157 2158
      end
    end
  end

2159 2160 2161 2162 2163
  describe '#tree' do
    context 'using a non-existing repository' do
      before do
        allow(repository).to receive(:head_commit).and_return(nil)
      end
2164

2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207
      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 已提交
2208
        expect(repository.commit_count).to be_an(Integer)
2209 2210 2211 2212
      end
    end
  end

2213
  describe '#commit_count_for_ref' do
2214
    let(:project) { create :project }
2215

2216 2217
    context 'with a non-existing repository' do
      it 'returns 0' do
2218 2219 2220 2221 2222 2223 2224 2225
        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)
2226 2227 2228 2229 2230 2231 2232 2233 2234 2235
      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

2236 2237 2238 2239 2240 2241 2242 2243 2244
  describe '#diverging_commit_counts' do
    it 'returns the commit counts behind and ahead of default branch' do
      result = repository.diverging_commit_counts(
        repository.find_branch('fix'))

      expect(result).to eq(behind: 29, ahead: 2)
    end
  end

2245
  describe '#cache_method_output', :use_clean_rails_memory_store_caching do
2246 2247
    let(:fallback) { 10 }

2248
    context 'with a non-existing repository' do
2249 2250 2251 2252 2253
      let(:project) { create(:project) } # No repository

      subject do
        repository.cache_method_output(:cats, fallback: fallback) do
          repository.cats_call_stub
2254 2255 2256
        end
      end

2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276
      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)
2277 2278 2279
      end

      it 'does not cache the data' do
2280
        subject
2281 2282 2283

        expect(repository.instance_variable_defined?(:@cats)).to eq(false)
        expect(repository.send(:cache).exist?(:cats)).to eq(false)
2284 2285
      end
    end
2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306

    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
2307 2308
      expect(repository).to receive(:expire_method_caches)
        .with(%i(rendered_readme license_blob license_key license))
2309

2310
      expect(repository).to receive(:rendered_readme)
2311 2312
      expect(repository).to receive(:license_blob)
      expect(repository).to receive(:license_key)
2313
      expect(repository).to receive(:license)
2314 2315 2316

      repository.refresh_method_caches(%i(readme license))
    end
2317
  end
D
Douwe Maan 已提交
2318 2319 2320

  describe '#gitlab_ci_yml_for' do
    before do
2321
      repository.create_file(User.last, '.gitlab-ci.yml', 'CONTENT', message: 'Add .gitlab-ci.yml', branch_name: 'master')
D
Douwe Maan 已提交
2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338
    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
2339
      repository.create_file(User.last, '.gitlab/route-map.yml', 'CONTENT', message: 'Add .gitlab/route-map.yml', branch_name: 'master')
D
Douwe Maan 已提交
2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353
    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
2354

2355
  describe '#ancestor?' do
2356 2357
    let(:commit) { repository.commit }
    let(:ancestor) { commit.parents.first }
2358

2359
    shared_examples '#ancestor?' do
2360
      it 'it is an ancestor' do
2361
        expect(repository.ancestor?(ancestor.id, commit.id)).to eq(true)
2362 2363 2364
      end

      it 'it is not an ancestor' do
2365
        expect(repository.ancestor?(commit.id, ancestor.id)).to eq(false)
2366 2367 2368
      end

      it 'returns false on nil-values' do
2369 2370 2371
        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)
2372
      end
2373

2374 2375 2376
      it 'returns false for invalid commit IDs' do
        expect(repository.ancestor?(commit.id, Gitlab::Git::BLANK_SHA)).to eq(false)
        expect(repository.ancestor?( Gitlab::Git::BLANK_SHA, commit.id)).to eq(false)
2377
      end
2378
    end
2379

2380 2381 2382
    context 'with Gitaly enabled' do
      it_behaves_like('#ancestor?')
    end
2383

2384 2385
    context 'with Gitaly disabled', :skip_gitaly_mock do
      it_behaves_like('#ancestor?')
2386 2387
    end
  end
2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407

  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
2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427

  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
2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534

  describe '#contributors' do
    let(:author_a) { build(:author, email: 'tiagonbotelho@hotmail.com', name: 'tiagonbotelho') }
    let(:author_b) { build(:author, email: 'gitlab@winniehell.de', name: 'Winnie') }
    let(:author_c) { build(:author, email: 'douwe@gitlab.com', name: 'Douwe Maan') }
    let(:stubbed_commits) do
      [build(:commit, author: author_a),
       build(:commit, author: author_a),
       build(:commit, author: author_b),
       build(:commit, author: author_c),
       build(:commit, author: author_c),
       build(:commit, author: author_c)]
    end
    let(:order_by) { nil }
    let(:sort) { nil }

    before do
      allow(repository).to receive(:commits).with(nil, limit: 2000, offset: 0, skip_merges: true).and_return(stubbed_commits)
    end

    subject { repository.contributors(order_by: order_by, sort: sort) }

    def expect_contributors(*contributors)
      expect(subject.map(&:email)).to eq(contributors.map(&:email))
    end

    it 'returns the array of Gitlab::Contributor for the repository' do
      expect_contributors(author_a, author_b, author_c)
    end

    context 'order_by email' do
      let(:order_by) { 'email' }

      context 'asc' do
        let(:sort) { 'asc' }

        it 'returns all the contributors ordered by email asc case insensitive' do
          expect_contributors(author_c, author_b, author_a)
        end
      end

      context 'desc' do
        let(:sort) { 'desc' }

        it 'returns all the contributors ordered by email desc case insensitive' do
          expect_contributors(author_a, author_b, author_c)
        end
      end
    end

    context 'order_by name' do
      let(:order_by) { 'name' }

      context 'asc' do
        let(:sort) { 'asc' }

        it 'returns all the contributors ordered by name asc case insensitive' do
          expect_contributors(author_c, author_a, author_b)
        end
      end

      context 'desc' do
        let(:sort) { 'desc' }

        it 'returns all the contributors ordered by name desc case insensitive' do
          expect_contributors(author_b, author_a, author_c)
        end
      end
    end

    context 'order_by commits' do
      let(:order_by) { 'commits' }

      context 'asc' do
        let(:sort) { 'asc' }

        it 'returns all the contributors ordered by commits asc' do
          expect_contributors(author_b, author_a, author_c)
        end
      end

      context 'desc' do
        let(:sort) { 'desc' }

        it 'returns all the contributors ordered by commits desc' do
          expect_contributors(author_c, author_a, author_b)
        end
      end
    end

    context 'invalid ordering' do
      let(:order_by) { 'unknown' }

      it 'returns the contributors unsorted' do
        expect_contributors(author_a, author_b, author_c)
      end
    end

    context 'invalid sorting' do
      let(:order_by) { 'name' }
      let(:sort) { 'unknown' }

      it 'returns the contributors unsorted' do
        expect_contributors(author_a, author_b, author_c)
      end
    end
  end
2535
end