2019-07-31 22:56:46 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2021-01-03 14:25:43 +05:30
|
|
|
RSpec.describe QA::Git::Repository do
|
2019-07-07 11:18:12 +05:30
|
|
|
include Helpers::StubENV
|
2018-12-13 13:39:08 +05:30
|
|
|
|
2020-05-24 23:13:21 +05:30
|
|
|
shared_context 'unresolvable git directory' do
|
|
|
|
let(:repo_uri) { 'http://foo/bar.git' }
|
|
|
|
let(:repo_uri_with_credentials) { 'http://root@foo/bar.git' }
|
2021-01-03 14:25:43 +05:30
|
|
|
let(:env_vars) { [%q{HOME="temp"}] }
|
|
|
|
let(:extra_env_vars) { [] }
|
|
|
|
let(:run_params) { { env: env_vars + extra_env_vars, log_prefix: "Git: " } }
|
|
|
|
let(:repository) do
|
|
|
|
described_class.new.tap do |r|
|
|
|
|
r.uri = repo_uri
|
|
|
|
r.env_vars = env_vars
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
let(:tmp_git_dir) { Dir.mktmpdir }
|
|
|
|
let(:tmp_netrc_dir) { Dir.mktmpdir }
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
before do
|
|
|
|
stub_env('GITLAB_USERNAME', 'root')
|
|
|
|
allow(repository).to receive(:tmp_home_dir).and_return(tmp_netrc_dir)
|
2018-11-08 19:23:39 +05:30
|
|
|
end
|
|
|
|
|
2020-05-24 23:13:21 +05:30
|
|
|
around do |example|
|
|
|
|
FileUtils.cd(tmp_git_dir) do
|
|
|
|
example.run
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
after do
|
|
|
|
FileUtils.remove_entry_secure(tmp_git_dir, true)
|
|
|
|
FileUtils.remove_entry_secure(tmp_netrc_dir, true)
|
2018-11-08 19:23:39 +05:30
|
|
|
end
|
2020-05-24 23:13:21 +05:30
|
|
|
end
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2020-05-24 23:13:21 +05:30
|
|
|
shared_examples 'command with retries' do
|
|
|
|
let(:result_output) { +'Command successful' }
|
|
|
|
let(:result) { described_class::Result.new(any_args, 0, result_output) }
|
|
|
|
let(:command_return) { result_output }
|
|
|
|
|
|
|
|
context 'when command is successful' do
|
|
|
|
it 'returns the #run command Result output' do
|
2021-01-03 14:25:43 +05:30
|
|
|
expect(repository).to receive(:run).with(command, run_params.merge(max_attempts: 3)).and_return(result)
|
2020-05-24 23:13:21 +05:30
|
|
|
|
|
|
|
expect(call_method).to eq(command_return)
|
|
|
|
end
|
2019-03-02 22:35:43 +05:30
|
|
|
end
|
|
|
|
|
2020-05-24 23:13:21 +05:30
|
|
|
context 'when command is not successful the first time' do
|
|
|
|
context 'and retried command is successful' do
|
|
|
|
it 'retries the command twice and returns the successful #run command Result output' do
|
|
|
|
expect(Open3).to receive(:capture2e).and_return([+'', double(exitstatus: 1)]).twice
|
|
|
|
expect(Open3).to receive(:capture2e).and_return([result_output, double(exitstatus: 0)])
|
|
|
|
|
|
|
|
expect(call_method).to eq(command_return)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'and retried command is not successful after 3 attempts' do
|
2021-01-03 14:25:43 +05:30
|
|
|
it 'raises a CommandError exception' do
|
2020-05-24 23:13:21 +05:30
|
|
|
expect(Open3).to receive(:capture2e).and_return([+'FAILURE', double(exitstatus: 42)]).exactly(3).times
|
|
|
|
|
2021-01-03 14:25:43 +05:30
|
|
|
expect { call_method }.to raise_error(QA::Support::Run::CommandError, /The command .* failed \(42\) with the following output:\nFAILURE/)
|
2020-05-24 23:13:21 +05:30
|
|
|
end
|
|
|
|
end
|
2018-12-13 13:39:08 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
context 'with default credentials' do
|
2020-05-24 23:13:21 +05:30
|
|
|
include_context 'unresolvable git directory' do
|
2019-03-02 22:35:43 +05:30
|
|
|
before do
|
|
|
|
repository.use_default_credentials
|
2018-12-13 13:39:08 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
describe '#clone' do
|
2020-05-24 23:13:21 +05:30
|
|
|
let(:opts) { '' }
|
|
|
|
let(:call_method) { repository.clone }
|
|
|
|
let(:command) { "git clone #{opts} #{repo_uri_with_credentials} ./" }
|
|
|
|
|
|
|
|
context 'when no opts is given' do
|
|
|
|
it_behaves_like 'command with retries'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when opts is given' do
|
|
|
|
let(:opts) { '--depth 1' }
|
|
|
|
|
|
|
|
it_behaves_like 'command with retries' do
|
|
|
|
let(:call_method) { repository.clone(opts) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#shallow_clone' do
|
|
|
|
it_behaves_like 'command with retries' do
|
|
|
|
let(:call_method) { repository.shallow_clone }
|
|
|
|
let(:command) { "git clone --depth 1 #{repo_uri_with_credentials} ./" }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#delete_tag' do
|
|
|
|
it_behaves_like 'command with retries' do
|
|
|
|
let(:tag_name) { 'v1.0' }
|
|
|
|
let(:call_method) { repository.delete_tag(tag_name) }
|
|
|
|
let(:command) { "git push origin --delete #{tag_name}" }
|
2019-03-02 22:35:43 +05:30
|
|
|
end
|
2018-12-13 13:39:08 +05:30
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
describe '#push_changes' do
|
2021-02-22 17:27:13 +05:30
|
|
|
let(:branch) { QA::Runtime::Env.default_branch }
|
2020-05-24 23:13:21 +05:30
|
|
|
let(:call_method) { repository.push_changes }
|
|
|
|
let(:command) { "git push #{repo_uri_with_credentials} #{branch}" }
|
|
|
|
|
|
|
|
context 'when no branch is given' do
|
|
|
|
it_behaves_like 'command with retries'
|
2019-03-02 22:35:43 +05:30
|
|
|
end
|
|
|
|
|
2020-05-24 23:13:21 +05:30
|
|
|
context 'when branch is given' do
|
|
|
|
let(:branch) { 'my-branch' }
|
|
|
|
|
|
|
|
it_behaves_like 'command with retries' do
|
|
|
|
let(:call_method) { repository.push_changes(branch) }
|
|
|
|
end
|
2019-03-02 22:35:43 +05:30
|
|
|
end
|
2021-01-03 14:25:43 +05:30
|
|
|
|
|
|
|
context 'with push options' do
|
|
|
|
let(:command) { "git push #{push_options} #{repo_uri_with_credentials} #{branch}" }
|
|
|
|
|
|
|
|
context 'when set to create a merge request' do
|
|
|
|
it_behaves_like 'command with retries' do
|
|
|
|
let(:push_options) { '-o merge_request.create' }
|
|
|
|
let(:call_method) { repository.push_changes(push_options: { create: true }) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when set to merge when pipeline succeeds' do
|
|
|
|
it_behaves_like 'command with retries' do
|
|
|
|
let(:push_options) { '-o merge_request.merge_when_pipeline_succeeds' }
|
|
|
|
let(:call_method) { repository.push_changes(push_options: { merge_when_pipeline_succeeds: true }) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when set to remove source branch' do
|
|
|
|
it_behaves_like 'command with retries' do
|
|
|
|
let(:push_options) { '-o merge_request.remove_source_branch' }
|
|
|
|
let(:call_method) { repository.push_changes(push_options: { remove_source_branch: true }) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when title is given' do
|
|
|
|
it_behaves_like 'command with retries' do
|
|
|
|
let(:push_options) { '-o merge_request.title="Is A Title"' }
|
|
|
|
let(:call_method) { repository.push_changes(push_options: { title: 'Is A Title' }) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when description is given' do
|
|
|
|
it_behaves_like 'command with retries' do
|
|
|
|
let(:push_options) { '-o merge_request.description="Is A Description"' }
|
|
|
|
let(:call_method) { repository.push_changes(push_options: { description: 'Is A Description' }) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when target branch is given' do
|
|
|
|
it_behaves_like 'command with retries' do
|
|
|
|
let(:push_options) { '-o merge_request.target="is-a-target-branch"' }
|
|
|
|
let(:call_method) { repository.push_changes(push_options: { target: 'is-a-target-branch' }) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a label is given' do
|
|
|
|
it_behaves_like 'command with retries' do
|
|
|
|
let(:push_options) { '-o merge_request.label="is-a-label"' }
|
|
|
|
let(:call_method) { repository.push_changes(push_options: { label: ['is-a-label'] }) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when two labels are given' do
|
|
|
|
it_behaves_like 'command with retries' do
|
|
|
|
let(:push_options) { '-o merge_request.label="is-a-label" -o merge_request.label="is-another-label"' }
|
|
|
|
let(:call_method) { repository.push_changes(push_options: { label: %w[is-a-label is-another-label] }) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2018-12-13 13:39:08 +05:30
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
describe '#git_protocol=' do
|
|
|
|
[0, 1, 2].each do |version|
|
|
|
|
it "configures git to use protocol version #{version}" do
|
2021-01-03 14:25:43 +05:30
|
|
|
expect(repository).to receive(:run).with("git config protocol.version #{version}", run_params.merge(max_attempts: 1))
|
2020-05-24 23:13:21 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
repository.git_protocol = version
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'raises an error if the version is unsupported' do
|
|
|
|
expect { repository.git_protocol = 'foo' }.to raise_error(ArgumentError, "Please specify the protocol you would like to use: 0, 1, or 2")
|
|
|
|
end
|
2018-12-13 13:39:08 +05:30
|
|
|
end
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
describe '#fetch_supported_git_protocol' do
|
2020-05-24 23:13:21 +05:30
|
|
|
let(:call_method) { repository.fetch_supported_git_protocol }
|
|
|
|
|
|
|
|
it_behaves_like 'command with retries' do
|
|
|
|
let(:command) { "git ls-remote #{repo_uri_with_credentials}" }
|
|
|
|
let(:result_output) { +'packet: git< version 2' }
|
|
|
|
let(:command_return) { '2' }
|
2021-01-03 14:25:43 +05:30
|
|
|
let(:extra_env_vars) { ["GIT_TRACE_PACKET=1"] }
|
2020-05-24 23:13:21 +05:30
|
|
|
end
|
2020-04-08 14:13:33 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it "reports the detected version" do
|
2020-05-24 23:13:21 +05:30
|
|
|
expect(repository).to receive(:run).and_return(described_class::Result.new(any_args, 0, "packet: git< version 2"))
|
|
|
|
|
|
|
|
expect(call_method).to eq('2')
|
2019-03-02 22:35:43 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'reports unknown if version is unknown' do
|
2020-05-24 23:13:21 +05:30
|
|
|
expect(repository).to receive(:run).and_return(described_class::Result.new(any_args, 0, "packet: git< version -1"))
|
|
|
|
|
|
|
|
expect(call_method).to eq('unknown')
|
2019-03-02 22:35:43 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'reports unknown if content does not identify a version' do
|
2020-05-24 23:13:21 +05:30
|
|
|
expect(repository).to receive(:run).and_return(described_class::Result.new(any_args, 0, "foo"))
|
|
|
|
|
|
|
|
expect(call_method).to eq('unknown')
|
2019-03-02 22:35:43 +05:30
|
|
|
end
|
2018-11-08 19:23:39 +05:30
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
describe '#use_default_credentials' do
|
|
|
|
it 'adds credentials to .netrc' do
|
|
|
|
expect(File.read(File.join(tmp_netrc_dir, '.netrc')))
|
|
|
|
.to eq("machine foo login #{QA::Runtime::User.default_username} password #{QA::Runtime::User.default_password}\n")
|
|
|
|
end
|
|
|
|
end
|
2018-11-08 19:23:39 +05:30
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
context 'with specific credentials' do
|
2020-05-24 23:13:21 +05:30
|
|
|
include_context 'unresolvable git directory'
|
2019-03-02 22:35:43 +05:30
|
|
|
|
|
|
|
context 'before setting credentials' do
|
|
|
|
it 'does not add credentials to .netrc' do
|
|
|
|
expect(repository).not_to receive(:save_netrc_content)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#password=' do
|
|
|
|
it 'raises an error if no username was given' do
|
|
|
|
expect { repository.password = 'foo' }
|
|
|
|
.to raise_error(QA::Git::Repository::InvalidCredentialsError,
|
|
|
|
"Please provide a username when setting a password")
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'adds credentials to .netrc' do
|
|
|
|
repository.username = 'user'
|
|
|
|
repository.password = 'foo'
|
|
|
|
|
|
|
|
expect(File.read(File.join(tmp_netrc_dir, '.netrc')))
|
|
|
|
.to eq("machine foo login user password foo\n")
|
|
|
|
end
|
2019-07-31 22:56:46 +05:30
|
|
|
|
|
|
|
it 'adds credentials with special characters' do
|
|
|
|
password = %q[!"#$%&')(*+,-./:;<=>?]
|
|
|
|
repository.username = 'user'
|
|
|
|
repository.password = password
|
|
|
|
|
|
|
|
expect(File.read(File.join(tmp_netrc_dir, '.netrc')))
|
|
|
|
.to eq("machine foo login user password #{password}\n")
|
|
|
|
end
|
2019-03-02 22:35:43 +05:30
|
|
|
end
|
2018-11-08 19:23:39 +05:30
|
|
|
end
|
|
|
|
end
|