Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlejandro Rodríguez <alejorro70@gmail.com>2018-10-12 04:37:25 +0300
committerAlejandro Rodríguez <alejorro70@gmail.com>2018-10-30 19:07:10 +0300
commitc260c0bf2419c30e5866838be6e329265483cc0a (patch)
tree0f8c31aeafdc8563a5617ff61a90b4b610fd118b
parentea5f1fb37731242f751340d47576c9824ac31c67 (diff)
Expand Gitlab::Git::Repository unit specs with examples from rails
-rw-r--r--changelogs/unreleased/repository-spec.yml5
-rw-r--r--ruby/.rubocop_todo.yml4
-rw-r--r--ruby/spec/gitaly/ref_service_spec.rb2
-rw-r--r--ruby/spec/gitaly/repository_service_spec.rb2
-rw-r--r--ruby/spec/lib/gitlab/git/remote_repository_spec.rb2
-rw-r--r--ruby/spec/lib/gitlab/git/repository_spec.rb692
-rw-r--r--ruby/spec/spec_helper.rb4
-rw-r--r--ruby/spec/support/helpers/gitlab_shell_helper.rb14
-rw-r--r--ruby/spec/support/helpers/integration_helper.rb (renamed from ruby/spec/integration_helper.rb)14
-rw-r--r--ruby/spec/test_repo_helper.rb6
10 files changed, 727 insertions, 18 deletions
diff --git a/changelogs/unreleased/repository-spec.yml b/changelogs/unreleased/repository-spec.yml
new file mode 100644
index 000000000..133379f36
--- /dev/null
+++ b/changelogs/unreleased/repository-spec.yml
@@ -0,0 +1,5 @@
+---
+title: Expand Gitlab::Git::Repository unit specs with examples from rails
+merge_request: 945
+author:
+type: other
diff --git a/ruby/.rubocop_todo.yml b/ruby/.rubocop_todo.yml
index b2e050bec..09cd8aae3 100644
--- a/ruby/.rubocop_todo.yml
+++ b/ruby/.rubocop_todo.yml
@@ -44,7 +44,7 @@ Lint/HandleExceptions:
# Offense count: 1
Lint/NonLocalExitFromIterator:
Exclude:
- - 'spec/integration_helper.rb'
+ - 'spec/support/helpers/integration_helper.rb'
# Offense count: 3
Lint/ShadowingOuterLocalVariable:
@@ -244,7 +244,7 @@ Style/RescueStandardError:
- 'lib/gitaly_server/sentry_interceptor.rb'
- 'lib/gitlab/git/commit.rb'
- 'lib/gitlab/git/gitlab_projects.rb'
- - 'spec/integration_helper.rb'
+ - 'spec/support/helpers/integration_helper.rb'
- 'spec/lib/gitaly_server/exception_sanitizer_interceptor_spec.rb'
- 'spec/lib/gitaly_server/sentry/url_sanitizer_spec.rb'
- 'spec/lib/gitaly_server/sentry_interceptor_spec.rb'
diff --git a/ruby/spec/gitaly/ref_service_spec.rb b/ruby/spec/gitaly/ref_service_spec.rb
index 96a648639..c3ba9d30c 100644
--- a/ruby/spec/gitaly/ref_service_spec.rb
+++ b/ruby/spec/gitaly/ref_service_spec.rb
@@ -1,5 +1,5 @@
-require 'integration_helper'
require 'securerandom'
+require 'spec_helper'
describe Gitaly::RefService do
include IntegrationClient
diff --git a/ruby/spec/gitaly/repository_service_spec.rb b/ruby/spec/gitaly/repository_service_spec.rb
index 30fa25851..d18f18216 100644
--- a/ruby/spec/gitaly/repository_service_spec.rb
+++ b/ruby/spec/gitaly/repository_service_spec.rb
@@ -1,4 +1,4 @@
-require 'integration_helper'
+require 'spec_helper'
describe Gitaly::RepositoryService do
include IntegrationClient
diff --git a/ruby/spec/lib/gitlab/git/remote_repository_spec.rb b/ruby/spec/lib/gitlab/git/remote_repository_spec.rb
index 93d88f644..2788f482b 100644
--- a/ruby/spec/lib/gitlab/git/remote_repository_spec.rb
+++ b/ruby/spec/lib/gitlab/git/remote_repository_spec.rb
@@ -70,8 +70,6 @@ describe Gitlab::Git::RemoteRepository, :seed_helper do
subject { remote_repository.fetch_env }
before do
- ENV['GITALY_RUBY_GITALY_BIN_DIR'] = __dir__
-
allow(remote_repository).to receive(:gitaly_client).and_return(gitaly_client)
expect(gitaly_client).to receive(:address).with(repository.storage).and_return(address)
diff --git a/ruby/spec/lib/gitlab/git/repository_spec.rb b/ruby/spec/lib/gitlab/git/repository_spec.rb
index 2b9f77abb..b6d7e065e 100644
--- a/ruby/spec/lib/gitlab/git/repository_spec.rb
+++ b/ruby/spec/lib/gitlab/git/repository_spec.rb
@@ -1,11 +1,18 @@
require 'spec_helper'
-describe Gitlab::Git::Repository do
+describe Gitlab::Git::Repository do # rubocop:disable Metrics/BlockLength
include TestRepo
+ include Gitlab::EncodingHelper
+ using RSpec::Parameterized::TableSyntax
- let(:repository) { gitlab_git_from_gitaly(test_repo_read_only) }
+ let(:mutable_repository) { gitlab_git_from_gitaly(new_mutable_git_test_repo) }
+ let(:repository) { gitlab_git_from_gitaly(git_test_repo_read_only) }
+ let(:repository_path) { repository.path }
+ let(:repository_rugged) { Rugged::Repository.new(repository_path) }
+ let(:storage_path) { DEFAULT_STORAGE_DIR }
+ let(:user) { Gitlab::Git::User.new('johndone', 'John Doe', 'johndoe@mail.com', 'user-1') }
- describe '#from_gitaly_with_block' do
+ describe '.from_gitaly_with_block' do
let(:call_metadata) do
{
'user-agent' => 'grpc-go/1.9.1',
@@ -39,6 +46,620 @@ describe Gitlab::Git::Repository do
end
end
+ describe '.create_hooks' do
+ let(:repo_path) { File.join(storage_path, 'hook-test.git') }
+ let(:hooks_dir) { File.join(repo_path, 'hooks') }
+ let(:target_hooks_dir) { Gitlab::Git::Hook.directory }
+ let(:existing_target) { File.join(repo_path, 'foobar') }
+
+ before do
+ GitlabShellHelper.setup_gitlab_shell
+
+ FileUtils.rm_rf(repo_path)
+ FileUtils.mkdir_p(repo_path)
+ end
+
+ context 'hooks is a directory' do
+ let(:existing_file) { File.join(hooks_dir, 'my-file') }
+
+ before do
+ FileUtils.mkdir_p(hooks_dir)
+ FileUtils.touch(existing_file)
+ described_class.create_hooks(repo_path, target_hooks_dir)
+ end
+
+ it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) }
+ it { expect(Dir[File.join(repo_path, "hooks.old.*/my-file")].count).to eq(1) }
+ end
+
+ context 'hooks is a valid symlink' do
+ before do
+ FileUtils.mkdir_p existing_target
+ File.symlink(existing_target, hooks_dir)
+ described_class.create_hooks(repo_path, target_hooks_dir)
+ end
+
+ it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) }
+ end
+
+ context 'hooks is a broken symlink' do
+ before do
+ FileUtils.rm_f(existing_target)
+ File.symlink(existing_target, hooks_dir)
+ described_class.create_hooks(repo_path, target_hooks_dir)
+ end
+
+ it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) }
+ end
+ end
+
+ describe "Respond to" do
+ subject { repository }
+
+ it { is_expected.to respond_to(:root_ref) }
+ it { is_expected.to respond_to(:tags) }
+ end
+
+ describe '#root_ref' do
+ it 'calls #discover_default_branch' do
+ expect(repository).to receive(:discover_default_branch)
+ repository.root_ref
+ end
+ end
+
+ describe '#branch_names' do
+ subject { repository.branch_names }
+
+ it 'has SeedRepo::Repo::BRANCHES.size elements' do
+ expect(subject.size).to eq(SeedRepo::Repo::BRANCHES.size)
+ end
+
+ it { is_expected.to include("master") }
+ it { is_expected.not_to include("branch-from-space") }
+ end
+
+ describe '#empty?' do
+ it { expect(repository).not_to be_empty }
+ end
+
+ describe "#delete_branch" do
+ let(:repository) { mutable_repository }
+
+ it "removes the branch from the repo" do
+ branch_name = "to-be-deleted-soon"
+
+ create_branch(repository, branch_name)
+ expect(repository_rugged.branches[branch_name]).not_to be_nil
+
+ repository.delete_branch(branch_name)
+ expect(repository_rugged.branches[branch_name]).to be_nil
+ end
+
+ context "when branch does not exist" do
+ it "raises a DeleteBranchError exception" do
+ expect { repository.delete_branch("this-branch-does-not-exist") }.to raise_error(Gitlab::Git::Repository::DeleteBranchError)
+ end
+ end
+ end
+
+ describe '#delete_refs' do
+ let(:repository) { mutable_repository }
+
+ it 'deletes the ref' do
+ repository.delete_refs('refs/heads/feature')
+
+ expect(repository_rugged.references['refs/heads/feature']).to be_nil
+ end
+
+ it 'deletes all refs' do
+ refs = %w[refs/heads/wip refs/tags/v1.1.0]
+ repository.delete_refs(*refs)
+
+ refs.each do |ref|
+ expect(repository_rugged.references[ref]).to be_nil
+ end
+ end
+
+ it 'does not fail when deleting an empty list of refs' do
+ expect { repository.delete_refs }.not_to raise_error
+ end
+
+ it 'raises an error if it failed' do
+ expect { repository.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError)
+ end
+ end
+
+ describe '#fetch_repository_as_mirror' do
+ let(:new_repository) { gitlab_git_from_gitaly(new_empty_test_repo) }
+ let(:repository) { mutable_repository }
+ let(:remote_repository) { Gitlab::Git::RemoteRepository.new(repository) }
+ let(:fake_env) { {} }
+
+ subject { new_repository.fetch_repository_as_mirror(remote_repository) }
+
+ it 'fetches a repository as a mirror remote' do
+ expect(new_repository).to receive(:add_remote).with(anything, Gitlab::Git::Repository::GITALY_INTERNAL_URL, mirror_refmap: :all_refs)
+ expect(remote_repository).to receive(:fetch_env).and_return(fake_env)
+ expect(new_repository).to receive(:fetch_remote).with(anything, env: fake_env)
+ expect(new_repository).to receive(:remove_remote).with(anything)
+
+ subject
+ end
+ end
+
+ describe '#raw_changes_between' do
+ let(:changes) { repository.raw_changes_between(old_rev, new_rev) }
+
+ context 'initial commit' do
+ let(:old_rev) { Gitlab::Git::BLANK_SHA }
+ let(:new_rev) { '1a0b36b3cdad1d2ee32457c102a8c0b7056fa863' }
+
+ it 'returns the changes' do
+ expect(changes).to be_present
+ expect(changes.size).to eq(3)
+ end
+ end
+
+ context 'with an invalid rev' do
+ let(:old_rev) { 'foo' }
+ let(:new_rev) { 'bar' }
+
+ it 'returns an error' do
+ expect { changes }.to raise_error(Gitlab::Git::Repository::GitError)
+ end
+ end
+
+ context 'with valid revs' do
+ let(:old_rev) { 'fa1b1e6c004a68b7d8763b86455da9e6b23e36d6' }
+ let(:new_rev) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' }
+
+ it 'returns the changes' do
+ expect(changes.size).to eq(9)
+ expect(changes.first.operation).to eq(:modified)
+ expect(changes.first.new_path).to eq('.gitmodules')
+ expect(changes.last.operation).to eq(:added)
+ expect(changes.last.new_path).to eq('files/lfs/picture-invalid.png')
+ end
+ end
+ end
+
+ describe '#merge_base' do
+ where(:from, :to, :result) do
+ '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
+ '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
+ '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | 'foobar' | nil
+ 'foobar' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | nil
+ end
+
+ with_them do
+ it { expect(repository.merge_base(from, to)).to eq(result) }
+ end
+ end
+
+ describe '#find_branch' do
+ it 'should return a Branch for master' do
+ branch = repository.find_branch('master')
+
+ expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
+ expect(branch.name).to eq('master')
+ end
+
+ it 'should handle non-existent branch' do
+ branch = repository.find_branch('this-is-garbage')
+
+ expect(branch).to eq(nil)
+ end
+ end
+
+ describe '#branches' do
+ subject { repository.branches }
+
+ context 'with local and remote branches' do
+ let(:repository) { mutable_repository }
+
+ before do
+ create_remote_branch('joe', 'remote_branch', 'master')
+ create_branch(repository, 'local_branch', 'master')
+ end
+
+ it 'returns the local and remote branches' do
+ expect(subject.any? { |b| b.name == 'joe/remote_branch' }).to eq(true)
+ expect(subject.any? { |b| b.name == 'local_branch' }).to eq(true)
+ end
+ end
+ end
+
+ describe '#branch_exists?' do
+ it 'returns true for an existing branch' do
+ expect(repository.branch_exists?('master')).to eq(true)
+ end
+
+ it 'returns false for a non-existing branch' do
+ expect(repository.branch_exists?('kittens')).to eq(false)
+ end
+
+ it 'returns false when using an invalid branch name' do
+ expect(repository.branch_exists?('.bla')).to eq(false)
+ end
+ end
+
+ describe '#local_branches' do
+ let(:repository) { mutable_repository }
+
+ before do
+ create_remote_branch('joe', 'remote_branch', 'master')
+ create_branch(repository, 'local_branch', 'master')
+ end
+
+ it 'returns the local branches' do
+ expect(repository.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
+ expect(repository.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
+ end
+ end
+
+ describe '#fetch_source_branch!' do
+ let(:local_ref) { 'refs/merge-requests/1/head' }
+ let(:repository) { mutable_repository }
+ let(:source_repository) { repository }
+
+ context 'when the branch exists' do
+ context 'when the commit does not exist locally' do
+ let(:source_branch) { 'new-branch-for-fetch-source-branch' }
+ let(:source_path) { File.join(DEFAULT_STORAGE_DIR, source_repository.relative_path) }
+ let(:source_rugged) { Rugged::Repository.new(source_path) }
+ let(:new_oid) { new_commit_edit_old_file(source_rugged).oid }
+
+ before do
+ source_rugged.branches.create(source_branch, new_oid)
+ end
+
+ it 'writes the ref' do
+ expect(repository.fetch_source_branch!(source_repository, source_branch, local_ref)).to eq(true)
+ expect(repository.commit(local_ref).sha).to eq(new_oid)
+ end
+ end
+
+ context 'when the commit exists locally' do
+ let(:source_branch) { 'master' }
+ let(:expected_oid) { SeedRepo::LastCommit::ID }
+
+ it 'writes the ref' do
+ # Sanity check: the commit should already exist
+ expect(repository.commit(expected_oid)).not_to be_nil
+
+ expect(repository.fetch_source_branch!(source_repository, source_branch, local_ref)).to eq(true)
+ expect(repository.commit(local_ref).sha).to eq(expected_oid)
+ end
+ end
+ end
+
+ context 'when the branch does not exist' do
+ let(:source_branch) { 'definitely-not-master' }
+
+ it 'does not write the ref' do
+ expect(repository.fetch_source_branch!(source_repository, source_branch, local_ref)).to eq(false)
+ expect(repository.commit(local_ref)).to be_nil
+ end
+ end
+ end
+
+ describe '#rm_branch' do
+ let(:repository) { mutable_repository }
+ let(:branch_name) { "to-be-deleted-soon" }
+
+ before do
+ # TODO: project.add_developer(user)
+ create_branch(repository, branch_name)
+ end
+
+ it "removes the branch from the repo" do
+ repository.rm_branch(branch_name, user: user)
+
+ expect(repository_rugged.branches[branch_name]).to be_nil
+ end
+ end
+
+ describe '#write_ref' do
+ context 'validations' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:ref_path, :ref) do
+ 'foo bar' | '123'
+ 'foobar' | "12\x003"
+ end
+
+ with_them do
+ it 'raises ArgumentError' do
+ expect { repository.write_ref(ref_path, ref) }.to raise_error(ArgumentError)
+ end
+ end
+ end
+ end
+
+ describe '#write_config' do
+ before do
+ repository_rugged.config["gitlab.fullpath"] = repository_path
+ end
+
+ context 'is given a path' do
+ it 'writes it to disk' do
+ repository.write_config(full_path: "not-the/real-path.git")
+
+ config = File.read(File.join(repository_path, "config"))
+
+ expect(config).to include("[gitlab]")
+ expect(config).to include("fullpath = not-the/real-path.git")
+ end
+ end
+
+ context 'it is given an empty path' do
+ it 'does not write it to disk' do
+ repository.write_config(full_path: "")
+
+ config = File.read(File.join(repository_path, "config"))
+
+ expect(config).to include("[gitlab]")
+ expect(config).to include("fullpath = #{repository_path}")
+ end
+ end
+ end
+
+ describe '#merge' do
+ let(:repository) { mutable_repository }
+ let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' }
+ let(:target_branch) { 'test-merge-target-branch' }
+
+ before do
+ create_branch(repository, target_branch, '6d394385cf567f80a8fd85055db1ab4c5295806f')
+ end
+
+ it 'can perform a merge' do
+ merge_commit_id = nil
+ result = repository.merge(user, source_sha, target_branch, 'Test merge') do |commit_id|
+ merge_commit_id = commit_id
+ end
+
+ expect(result.newrev).to eq(merge_commit_id)
+ expect(result.repo_created).to eq(false)
+ expect(result.branch_created).to eq(false)
+ end
+
+ it 'returns nil if there was a concurrent branch update' do
+ concurrent_update_id = '33f3729a45c02fc67d00adb1b8bca394b0e761d9'
+ result = repository.merge(user, source_sha, target_branch, 'Test merge') do
+ # This ref update should make the merge fail
+ repository.write_ref(Gitlab::Git::BRANCH_REF_PREFIX + target_branch, concurrent_update_id)
+ end
+
+ # This 'nil' signals that the merge was not applied
+ expect(result).to be_nil
+
+ # Our concurrent ref update should not have been undone
+ expect(repository.find_branch(target_branch).target).to eq(concurrent_update_id)
+ end
+ end
+
+ describe '#ff_merge' do
+ let(:repository) { mutable_repository }
+ let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
+ let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
+ let(:target_branch) { 'test-ff-target-branch' }
+
+ before do
+ create_branch(repository, target_branch, branch_head)
+ end
+
+ subject { repository.ff_merge(user, source_sha, target_branch) }
+
+ it 'performs a ff_merge' do
+ expect(subject.newrev).to eq(source_sha)
+ expect(subject.repo_created).to be(false)
+ expect(subject.branch_created).to be(false)
+
+ expect(repository.commit(target_branch).id).to eq(source_sha)
+ end
+
+ context 'with a non-existing target branch' do
+ subject { repository.ff_merge(user, source_sha, 'this-isnt-real') }
+
+ it 'throws an ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+
+ context 'with a non-existing source commit' do
+ let(:source_sha) { 'f001' }
+
+ it 'throws an ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+
+ context 'when the source sha is not a descendant of the branch head' do
+ let(:source_sha) { '1a0b36b3cdad1d2ee32457c102a8c0b7056fa863' }
+
+ it "doesn't perform the ff_merge" do
+ expect { subject }.to raise_error(Gitlab::Git::CommitError)
+
+ expect(repository.commit(target_branch).id).to eq(branch_head)
+ end
+ end
+ end
+
+ describe '#delete_all_refs_except' do
+ let(:repository) { mutable_repository }
+
+ before do
+ repository.write_ref("refs/delete/a", "0b4bc9a49b562e85de7cc9e834518ea6828729b9")
+ repository.write_ref("refs/also-delete/b", "12d65c8dd2b2676fa3ac47d955accc085a37a9c1")
+ repository.write_ref("refs/keep/c", "6473c90867124755509e100d0d35ebdc85a0b6ae")
+ repository.write_ref("refs/also-keep/d", "0b4bc9a49b562e85de7cc9e834518ea6828729b9")
+ end
+
+ it 'deletes all refs except those with the specified prefixes' do
+ repository.delete_all_refs_except(%w[refs/keep refs/also-keep refs/heads])
+ expect(repository_rugged.references.exist?("refs/delete/a")).to be(false)
+ expect(repository_rugged.references.exist?("refs/also-delete/b")).to be(false)
+ expect(repository_rugged.references.exist?("refs/keep/c")).to be(true)
+ expect(repository_rugged.references.exist?("refs/also-keep/d")).to be(true)
+ expect(repository_rugged.references.exist?("refs/heads/master")).to be(true)
+ end
+ end
+
+ describe 'remotes' do
+ let(:repository) { mutable_repository }
+ let(:remote_name) { 'my-remote' }
+ let(:url) { 'http://my-repo.git' }
+
+ describe '#add_remote' do
+ let(:mirror_refmap) { '+refs/*:refs/*' }
+
+ it 'added the remote' do
+ begin
+ repository_rugged.remotes.delete(remote_name)
+ rescue Rugged::ConfigError # rubocop:disable Lint/HandleExceptions
+ end
+
+ repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap)
+
+ expect(repository_rugged.remotes[remote_name]).not_to be_nil
+ expect(repository_rugged.config["remote.#{remote_name}.mirror"]).to eq('true')
+ expect(repository_rugged.config["remote.#{remote_name}.prune"]).to eq('true')
+ expect(repository_rugged.config["remote.#{remote_name}.fetch"]).to eq(mirror_refmap)
+ end
+ end
+
+ describe '#remove_remote' do
+ it 'removes the remote' do
+ repository_rugged.remotes.create(remote_name, url)
+
+ repository.remove_remote(remote_name)
+
+ expect(repository_rugged.remotes[remote_name]).to be_nil
+ end
+ end
+ end
+
+ describe '#squash' do
+ let(:repository) { mutable_repository }
+ let(:squash_id) { '1' }
+ let(:branch_name) { 'fix' }
+ let(:start_sha) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' }
+ let(:end_sha) { '12d65c8dd2b2676fa3ac47d955accc085a37a9c1' }
+
+ subject do
+ opts = {
+ branch: branch_name,
+ start_sha: start_sha,
+ end_sha: end_sha,
+ author: user,
+ message: 'Squash commit message'
+ }
+
+ repository.squash(user, squash_id, opts)
+ end
+
+ describe 'sparse checkout' do
+ let(:expected_files) { %w[files files/js files/js/application.js] }
+
+ it 'checks out only the files in the diff' do
+ allow(repository).to receive(:with_worktree).and_wrap_original do |m, *args|
+ m.call(*args) do
+ worktree_path = args[0]
+ files_pattern = File.join(worktree_path, '**', '*')
+ expected = expected_files.map do |path|
+ File.expand_path(path, worktree_path)
+ end
+
+ expect(Dir[files_pattern]).to eq(expected)
+ end
+ end
+
+ subject
+ end
+
+ context 'when the diff contains a rename' do
+ let(:end_sha) { new_commit_move_file(repository_rugged).oid }
+
+ after do
+ # Erase our commits so other tests get the original repo
+ repository_rugged.references.update('refs/heads/master', SeedRepo::LastCommit::ID)
+ end
+
+ it 'does not include the renamed file in the sparse checkout' do
+ allow(repository).to receive(:with_worktree).and_wrap_original do |m, *args|
+ m.call(*args) do
+ worktree_path = args[0]
+ files_pattern = File.join(worktree_path, '**', '*')
+
+ expect(Dir[files_pattern]).not_to include('CHANGELOG')
+ expect(Dir[files_pattern]).not_to include('encoding/CHANGELOG')
+ end
+ end
+
+ subject
+ end
+ end
+ end
+
+ describe 'with an ASCII-8BIT diff' do
+ let(:diff) do
+ <<~RAW_DIFF
+ diff --git a/README.md b/README.md
+ index faaf198..43c5edf 100644
+ --- a/README.md
+ +++ b/README.md
+ @@ -1,4 +1,4 @@
+ -testme
+ +✓ testme
+ ======
+
+ Sample repo for testing gitlab features
+ RAW_DIFF
+ end
+
+ it 'applies a ASCII-8BIT diff' do
+ allow(repository).to receive(:run_git!).and_call_original
+ allow(repository).to receive(:run_git!)
+ .with(%W[diff --binary #{start_sha}...#{end_sha}])
+ .and_return(diff.force_encoding('ASCII-8BIT'))
+
+ expect(subject).to match(/\h{40}/)
+ end
+ end
+
+ describe 'with trailing whitespace in an invalid patch' do
+ let(:diff) do
+ # rubocop:disable Layout/TrailingWhitespace
+ <<~RAW_DIFF
+ diff --git a/README.md b/README.md
+ index faaf198..43c5edf 100644
+ --- a/README.md
+ +++ b/README.md
+ @@ -1,4 +1,4 @@
+ -testme
+ +
+ ======
+
+ Sample repo for testing gitlab features
+ RAW_DIFF
+ # rubocop:enable Layout/TrailingWhitespace
+ end
+
+ it 'does not include whitespace warnings in the error' do
+ allow(repository).to receive(:run_git!).and_call_original
+ allow(repository).to receive(:run_git!)
+ .with(%W[diff --binary #{start_sha}...#{end_sha}])
+ .and_return(diff.force_encoding('ASCII-8BIT'))
+
+ expect { subject }.to raise_error do |error|
+ expect(error).to be_a(described_class::GitError)
+ expect(error.message).not_to include('trailing whitespace')
+ end
+ end
+ end
+ end
+
describe '#cleanup' do
context 'when Rugged has been called' do
it 'calls close on Rugged::Repository' do
@@ -148,4 +769,69 @@ describe Gitlab::Git::Repository do
end
end
end
+
+ def create_remote_branch(remote_name, branch_name, source_branch_name)
+ source_branch = repository.branches.find { |branch| branch.name == source_branch_name }
+ repository_rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", source_branch.dereferenced_target.sha)
+ end
+
+ # Build the options hash that's passed to Rugged::Commit#create
+ def commit_options(repo, index, message)
+ options = {}
+ options[:tree] = index.write_tree(repo)
+ options[:author] = {
+ email: "test@example.com",
+ name: "Test Author",
+ time: Time.gm(2014, "mar", 3, 20, 15, 1)
+ }
+ options[:committer] = {
+ email: "test@example.com",
+ name: "Test Author",
+ time: Time.gm(2014, "mar", 3, 20, 15, 1)
+ }
+ options[:message] ||= message
+ options[:parents] = repo.empty? ? [] : [repo.head.target].compact
+ options[:update_ref] = "HEAD"
+
+ options
+ end
+
+ # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the
+ # contents of CHANGELOG with a single new line of text.
+ def new_commit_edit_old_file(repo)
+ oid = repo.write("I replaced the changelog with this text", :blob)
+ index = repo.index
+ index.read_tree(repo.head.target.tree)
+ index.add(path: "CHANGELOG", oid: oid, mode: 0o100644)
+
+ options = commit_options(
+ repo,
+ index,
+ "Edit CHANGELOG in its original location"
+ )
+
+ sha = Rugged::Commit.create(repo, options)
+ repo.lookup(sha)
+ end
+
+ # Writes a new commit to the repo and returns a Rugged::Commit. Moves the
+ # CHANGELOG file to the encoding/ directory.
+ def new_commit_move_file(repo)
+ blob_oid = repo.head.target.tree.detect { |i| i[:name] == "CHANGELOG" }[:oid]
+ file_content = repo.lookup(blob_oid).content
+ oid = repo.write(file_content, :blob)
+ index = repo.index
+ index.read_tree(repo.head.target.tree)
+ index.add(path: "encoding/CHANGELOG", oid: oid, mode: 0o100644)
+ index.remove("CHANGELOG")
+
+ options = commit_options(repo, index, "Move CHANGELOG to encoding/")
+
+ sha = Rugged::Commit.create(repo, options)
+ repo.lookup(sha)
+ end
+
+ def create_branch(repository, branch_name, start_point = 'HEAD')
+ repository.rugged.branches.create(branch_name, start_point)
+ end
end
diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb
index d31f8f8b7..183cac7ba 100644
--- a/ruby/spec/spec_helper.rb
+++ b/ruby/spec/spec_helper.rb
@@ -6,10 +6,14 @@ require 'test_repo_helper'
require 'rspec-parameterized'
require 'factory_bot'
+# Load these helpers first, since they're required by other helpers
+require File.join(__dir__, 'support/helpers/gitlab_shell_helper.rb')
+
Dir[File.join(__dir__, 'support/helpers/*.rb')].each { |f| require f }
ENV['GITALY_RUBY_GIT_BIN_PATH'] ||= 'git'
ENV['GITALY_GIT_HOOKS_DIR'] ||= File.join(Gitlab.config.gitlab_shell.path.to_s, "hooks")
+ENV['GITALY_RUBY_GITALY_BIN_DIR'] = __dir__
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
diff --git a/ruby/spec/support/helpers/gitlab_shell_helper.rb b/ruby/spec/support/helpers/gitlab_shell_helper.rb
new file mode 100644
index 000000000..ad127bf2e
--- /dev/null
+++ b/ruby/spec/support/helpers/gitlab_shell_helper.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+GITALY_RUBY_DIR = File.expand_path('../../..', __dir__).freeze
+TMP_DIR_NAME = 'tmp'.freeze
+TMP_DIR = File.join(GITALY_RUBY_DIR, TMP_DIR_NAME).freeze
+GITLAB_SHELL_DIR = File.join(TMP_DIR, 'gitlab-shell').freeze
+
+module GitlabShellHelper
+ def self.setup_gitlab_shell
+ ENV['GITALY_RUBY_GITLAB_SHELL_PATH'] = GITLAB_SHELL_DIR
+
+ FileUtils.mkdir_p([TMP_DIR, File.join(GITLAB_SHELL_DIR, 'hooks')])
+ end
+end
diff --git a/ruby/spec/integration_helper.rb b/ruby/spec/support/helpers/integration_helper.rb
index a8c4e586e..8402ee1e4 100644
--- a/ruby/spec/integration_helper.rb
+++ b/ruby/spec/support/helpers/integration_helper.rb
@@ -4,13 +4,11 @@ require 'gitaly'
require 'spec_helper'
SOCKET_PATH = 'gitaly.socket'.freeze
-GITALY_RUBY_DIR = File.expand_path('..', __dir__)
-TMP_DIR = File.expand_path('../tmp', __dir__)
module IntegrationClient
def gitaly_stub(service)
klass = Gitaly.const_get(service).const_get(:Stub)
- klass.new("unix:tmp/#{SOCKET_PATH}", :this_channel_is_insecure)
+ klass.new("unix:#{File.join(TMP_DIR_NAME, SOCKET_PATH)}", :this_channel_is_insecure)
end
def gitaly_repo(storage, relative_path)
@@ -19,17 +17,15 @@ module IntegrationClient
end
def start_gitaly
- build_dir = File.expand_path('../../_build', __dir__)
- gitlab_shell_dir = File.join(TMP_DIR, 'gitlab-shell')
-
- FileUtils.mkdir_p([TMP_DIR, File.join(gitlab_shell_dir, 'hooks')])
+ build_dir = File.expand_path(File.join(GITALY_RUBY_DIR, '../_build'))
+ GitlabShellHelper.setup_gitlab_shell
config_toml = <<~CONFIG
socket_path = "#{SOCKET_PATH}"
bin_dir = "#{build_dir}/bin"
[gitlab-shell]
- dir = "#{gitlab_shell_dir}"
+ dir = "#{GITLAB_SHELL_DIR}"
[gitaly-ruby]
dir = "#{GITALY_RUBY_DIR}"
@@ -47,7 +43,7 @@ def start_gitaly
gitaly_pid = spawn(File.join(build_dir, 'bin/gitaly'), config_path, options)
at_exit { Process.kill('KILL', gitaly_pid) }
- wait_ready!(File.join('tmp', SOCKET_PATH))
+ wait_ready!(File.join(TMP_DIR_NAME, SOCKET_PATH))
end
def wait_ready!(socket)
diff --git a/ruby/spec/test_repo_helper.rb b/ruby/spec/test_repo_helper.rb
index f2df1751a..c013d8caa 100644
--- a/ruby/spec/test_repo_helper.rb
+++ b/ruby/spec/test_repo_helper.rb
@@ -40,6 +40,12 @@ module TestRepo
Gitaly::Repository.new(storage_name: DEFAULT_STORAGE_NAME, relative_path: relative_path)
end
+ def new_mutable_git_test_repo
+ relative_path = random_repository_relative_path(:mutable)
+ TestRepo.clone_new_repo!(GIT_TEST_REPO_ORIGIN, File.join(DEFAULT_STORAGE_DIR, relative_path))
+ Gitaly::Repository.new(storage_name: DEFAULT_STORAGE_NAME, relative_path: relative_path)
+ end
+
def new_broken_test_repo
relative_path = random_repository_relative_path(:broken)
repo_path = File.join(DEFAULT_STORAGE_DIR, relative_path)