a99bf447a2
Cleanup code, and refactor tests that still use Rugged. After this, there should be no Rugged code that access the instance's repositories on non-test environments. There is still some rugged code for other tasks like the repository import task, but since it doesn't access any repository storage path it can stay.
1958 lines
62 KiB
Ruby
1958 lines
62 KiB
Ruby
# coding: utf-8
|
|
require "spec_helper"
|
|
|
|
describe Gitlab::Git::Repository, :seed_helper do
|
|
include Gitlab::EncodingHelper
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
shared_examples 'wrapping gRPC errors' do |gitaly_client_class, gitaly_client_method|
|
|
it 'wraps gRPC not found error' do
|
|
expect_any_instance_of(gitaly_client_class).to receive(gitaly_client_method)
|
|
.and_raise(GRPC::NotFound)
|
|
expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
|
|
end
|
|
|
|
it 'wraps gRPC unknown error' do
|
|
expect_any_instance_of(gitaly_client_class).to receive(gitaly_client_method)
|
|
.and_raise(GRPC::Unknown)
|
|
expect { subject }.to raise_error(Gitlab::Git::CommandError)
|
|
end
|
|
end
|
|
|
|
let(:mutable_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
|
|
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
|
|
let(:repository_path) { File.join(TestEnv.repos_path, repository.relative_path) }
|
|
let(:repository_rugged) { Rugged::Repository.new(repository_path) }
|
|
let(:storage_path) { TestEnv.repos_path }
|
|
let(:user) { build(:user) }
|
|
|
|
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.config.gitlab_shell.hooks_path }
|
|
let(:existing_target) { File.join(repo_path, 'foobar') }
|
|
|
|
before do
|
|
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 'returns UTF-8' do
|
|
expect(repository.root_ref).to be_utf8
|
|
end
|
|
|
|
it 'gets the branch name from GitalyClient' do
|
|
expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:default_branch_name)
|
|
repository.root_ref
|
|
end
|
|
|
|
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :default_branch_name do
|
|
subject { 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 'returns UTF-8' do
|
|
expect(subject.first).to be_utf8
|
|
end
|
|
|
|
it { is_expected.to include("master") }
|
|
it { is_expected.not_to include("branch-from-space") }
|
|
|
|
it 'gets the branch names from GitalyClient' do
|
|
expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:branch_names)
|
|
subject
|
|
end
|
|
|
|
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :branch_names
|
|
end
|
|
|
|
describe '#tag_names' do
|
|
subject { repository.tag_names }
|
|
|
|
it { is_expected.to be_kind_of Array }
|
|
|
|
it 'has SeedRepo::Repo::TAGS.size elements' do
|
|
expect(subject.size).to eq(SeedRepo::Repo::TAGS.size)
|
|
end
|
|
|
|
it 'returns UTF-8' do
|
|
expect(subject.first).to be_utf8
|
|
end
|
|
|
|
describe '#last' do
|
|
subject { super().last }
|
|
it { is_expected.to eq("v1.2.1") }
|
|
end
|
|
it { is_expected.to include("v1.0.0") }
|
|
it { is_expected.not_to include("v5.0.0") }
|
|
|
|
it 'gets the tag names from GitalyClient' do
|
|
expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:tag_names)
|
|
subject
|
|
end
|
|
|
|
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :tag_names
|
|
end
|
|
|
|
describe '#archive_metadata' do
|
|
let(:storage_path) { '/tmp' }
|
|
let(:cache_key) { File.join(repository.gl_repository, SeedRepo::LastCommit::ID) }
|
|
|
|
let(:append_sha) { true }
|
|
let(:ref) { 'master' }
|
|
let(:format) { nil }
|
|
|
|
let(:expected_extension) { 'tar.gz' }
|
|
let(:expected_filename) { "#{expected_prefix}.#{expected_extension}" }
|
|
let(:expected_path) { File.join(storage_path, cache_key, expected_filename) }
|
|
let(:expected_prefix) { "gitlab-git-test-#{ref}-#{SeedRepo::LastCommit::ID}" }
|
|
|
|
subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha) }
|
|
|
|
it 'sets CommitId to the commit SHA' do
|
|
expect(metadata['CommitId']).to eq(SeedRepo::LastCommit::ID)
|
|
end
|
|
|
|
it 'sets ArchivePrefix to the expected prefix' do
|
|
expect(metadata['ArchivePrefix']).to eq(expected_prefix)
|
|
end
|
|
|
|
it 'sets ArchivePath to the expected globally-unique path' do
|
|
# This is really important from a security perspective. Think carefully
|
|
# before changing it: https://gitlab.com/gitlab-org/gitlab-ce/issues/45689
|
|
expect(expected_path).to include(File.join(repository.gl_repository, SeedRepo::LastCommit::ID))
|
|
|
|
expect(metadata['ArchivePath']).to eq(expected_path)
|
|
end
|
|
|
|
context 'append_sha varies archive path and filename' do
|
|
where(:append_sha, :ref, :expected_prefix) do
|
|
sha = SeedRepo::LastCommit::ID
|
|
|
|
true | 'master' | "gitlab-git-test-master-#{sha}"
|
|
true | sha | "gitlab-git-test-#{sha}-#{sha}"
|
|
false | 'master' | "gitlab-git-test-master"
|
|
false | sha | "gitlab-git-test-#{sha}"
|
|
nil | 'master' | "gitlab-git-test-master-#{sha}"
|
|
nil | sha | "gitlab-git-test-#{sha}"
|
|
end
|
|
|
|
with_them do
|
|
it { expect(metadata['ArchivePrefix']).to eq(expected_prefix) }
|
|
it { expect(metadata['ArchivePath']).to eq(expected_path) }
|
|
end
|
|
end
|
|
|
|
context 'format varies archive path and filename' do
|
|
where(:format, :expected_extension) do
|
|
nil | 'tar.gz'
|
|
'madeup' | 'tar.gz'
|
|
'tbz2' | 'tar.bz2'
|
|
'zip' | 'zip'
|
|
end
|
|
|
|
with_them do
|
|
it { expect(metadata['ArchivePrefix']).to eq(expected_prefix) }
|
|
it { expect(metadata['ArchivePath']).to eq(expected_path) }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#size' do
|
|
subject { repository.size }
|
|
|
|
it { is_expected.to be < 2 }
|
|
end
|
|
|
|
describe '#empty?' do
|
|
it { expect(repository).not_to be_empty }
|
|
end
|
|
|
|
describe '#ref_names' do
|
|
let(:ref_names) { repository.ref_names }
|
|
subject { ref_names }
|
|
|
|
it { is_expected.to be_kind_of Array }
|
|
|
|
describe '#first' do
|
|
subject { super().first }
|
|
it { is_expected.to eq('feature') }
|
|
end
|
|
|
|
describe '#last' do
|
|
subject { super().last }
|
|
it { is_expected.to eq('v1.2.1') }
|
|
end
|
|
end
|
|
|
|
describe '#submodule_url_for' do
|
|
let(:ref) { 'master' }
|
|
|
|
def submodule_url(path)
|
|
repository.submodule_url_for(ref, path)
|
|
end
|
|
|
|
it { expect(submodule_url('six')).to eq('git://github.com/randx/six.git') }
|
|
it { expect(submodule_url('nested/six')).to eq('git://github.com/randx/six.git') }
|
|
it { expect(submodule_url('deeper/nested/six')).to eq('git://github.com/randx/six.git') }
|
|
it { expect(submodule_url('invalid/path')).to eq(nil) }
|
|
|
|
context 'uncommitted submodule dir' do
|
|
let(:ref) { 'fix-existing-submodule-dir' }
|
|
|
|
it { expect(submodule_url('submodule-existing-dir')).to eq(nil) }
|
|
end
|
|
|
|
context 'tags' do
|
|
let(:ref) { 'v1.2.1' }
|
|
|
|
it { expect(submodule_url('six')).to eq('git://github.com/randx/six.git') }
|
|
end
|
|
|
|
context 'no .gitmodules at commit' do
|
|
let(:ref) { '9596bc54a6f0c0c98248fe97077eb5ccf48a98d0' }
|
|
|
|
it { expect(submodule_url('six')).to eq(nil) }
|
|
end
|
|
|
|
context 'no gitlink entry' do
|
|
let(:ref) { '6d39438' }
|
|
|
|
it { expect(submodule_url('six')).to eq(nil) }
|
|
end
|
|
end
|
|
|
|
describe '#commit_count' do
|
|
it { expect(repository.commit_count("master")).to eq(25) }
|
|
it { expect(repository.commit_count("feature")).to eq(9) }
|
|
it { expect(repository.commit_count("does-not-exist")).to eq(0) }
|
|
|
|
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :commit_count do
|
|
subject { repository.commit_count('master') }
|
|
end
|
|
end
|
|
|
|
describe '#has_local_branches?' do
|
|
context 'check for local branches' do
|
|
it { expect(repository.has_local_branches?).to eq(true) }
|
|
|
|
context 'mutable' do
|
|
let(:repository) { mutable_repository }
|
|
|
|
after do
|
|
ensure_seeds
|
|
end
|
|
|
|
it 'returns false when there are no branches' do
|
|
# Sanity check
|
|
expect(repository.has_local_branches?).to eq(true)
|
|
|
|
FileUtils.rm_rf(File.join(repository_path, 'packed-refs'))
|
|
heads_dir = File.join(repository_path, 'refs/heads')
|
|
FileUtils.rm_rf(heads_dir)
|
|
FileUtils.mkdir_p(heads_dir)
|
|
|
|
repository.expire_has_local_branches_cache
|
|
expect(repository.has_local_branches?).to eq(false)
|
|
end
|
|
end
|
|
|
|
context 'memoizes the value' do
|
|
it 'returns true' do
|
|
expect(repository).to receive(:uncached_has_local_branches?).once.and_call_original
|
|
|
|
2.times do
|
|
expect(repository.has_local_branches?).to eq(true)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#delete_branch" do
|
|
let(:repository) { mutable_repository }
|
|
|
|
after do
|
|
ensure_seeds
|
|
end
|
|
|
|
it "removes the branch from the repo" do
|
|
branch_name = "to-be-deleted-soon"
|
|
|
|
repository.create_branch(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 "#create_branch" do
|
|
let(:repository) { mutable_repository }
|
|
|
|
after do
|
|
ensure_seeds
|
|
end
|
|
|
|
it "should create a new branch" do
|
|
expect(repository.create_branch('new_branch', 'master')).not_to be_nil
|
|
end
|
|
|
|
it "should create a new branch with the right name" do
|
|
expect(repository.create_branch('another_branch', 'master').name).to eq('another_branch')
|
|
end
|
|
|
|
it "should fail if we create an existing branch" do
|
|
repository.create_branch('duplicated_branch', 'master')
|
|
expect {repository.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists")
|
|
end
|
|
|
|
it "should fail if we create a branch from a non existing ref" do
|
|
expect {repository.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge")
|
|
end
|
|
end
|
|
|
|
describe '#delete_refs' do
|
|
let(:repository) { mutable_repository }
|
|
|
|
after do
|
|
ensure_seeds
|
|
end
|
|
|
|
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 '#branch_names_contains_sha' do
|
|
let(:head_id) { repository_rugged.head.target.oid }
|
|
let(:new_branch) { head_id }
|
|
let(:utf8_branch) { 'branch-é' }
|
|
|
|
before do
|
|
repository.create_branch(new_branch, 'master')
|
|
repository.create_branch(utf8_branch, 'master')
|
|
end
|
|
|
|
after do
|
|
repository.delete_branch(new_branch)
|
|
repository.delete_branch(utf8_branch)
|
|
end
|
|
|
|
it 'displays that branch' do
|
|
expect(repository.branch_names_contains_sha(head_id)).to include('master', new_branch, utf8_branch)
|
|
end
|
|
end
|
|
|
|
describe "#refs_hash" do
|
|
subject { repository.refs_hash }
|
|
|
|
it "should have as many entries as branches and tags" do
|
|
expected_refs = SeedRepo::Repo::BRANCHES + SeedRepo::Repo::TAGS
|
|
# We flatten in case a commit is pointed at by more than one branch and/or tag
|
|
expect(subject.values.flatten.size).to eq(expected_refs.size)
|
|
end
|
|
|
|
it 'has valid commit ids as keys' do
|
|
expect(subject.keys).to all( match(Commit::COMMIT_SHA_PATTERN) )
|
|
end
|
|
end
|
|
|
|
describe '#fetch_repository_as_mirror' do
|
|
let(:new_repository) do
|
|
Gitlab::Git::Repository.new('default', 'my_project.git', '')
|
|
end
|
|
|
|
subject { new_repository.fetch_repository_as_mirror(repository) }
|
|
|
|
before do
|
|
Gitlab::Shell.new.create_repository('default', 'my_project')
|
|
end
|
|
|
|
after do
|
|
Gitlab::Shell.new.remove_repository('default', 'my_project')
|
|
end
|
|
|
|
it 'fetches a repository as a mirror remote' do
|
|
subject
|
|
|
|
expect(refs(new_repository_path)).to eq(refs(repository_path))
|
|
end
|
|
|
|
context 'with keep-around refs' do
|
|
let(:sha) { SeedRepo::Commit::ID }
|
|
let(:keep_around_ref) { "refs/keep-around/#{sha}" }
|
|
let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
|
|
|
|
before do
|
|
repository_rugged.references.create(keep_around_ref, sha, force: true)
|
|
repository_rugged.references.create(tmp_ref, sha, force: true)
|
|
end
|
|
|
|
it 'includes the temporary and keep-around refs' do
|
|
subject
|
|
|
|
expect(refs(new_repository_path)).to include(keep_around_ref)
|
|
expect(refs(new_repository_path)).to include(tmp_ref)
|
|
end
|
|
end
|
|
|
|
def new_repository_path
|
|
File.join(TestEnv.repos_path, new_repository.relative_path)
|
|
end
|
|
end
|
|
|
|
describe '#find_remote_root_ref' do
|
|
it 'gets the remote root ref from GitalyClient' do
|
|
expect_any_instance_of(Gitlab::GitalyClient::RemoteService)
|
|
.to receive(:find_remote_root_ref).and_call_original
|
|
|
|
expect(repository.find_remote_root_ref('origin')).to eq 'master'
|
|
end
|
|
|
|
it 'returns UTF-8' do
|
|
expect(repository.find_remote_root_ref('origin')).to be_utf8
|
|
end
|
|
|
|
it 'returns nil when remote name is nil' do
|
|
expect_any_instance_of(Gitlab::GitalyClient::RemoteService)
|
|
.not_to receive(:find_remote_root_ref)
|
|
|
|
expect(repository.find_remote_root_ref(nil)).to be_nil
|
|
end
|
|
|
|
it 'returns nil when remote name is empty' do
|
|
expect_any_instance_of(Gitlab::GitalyClient::RemoteService)
|
|
.not_to receive(:find_remote_root_ref)
|
|
|
|
expect(repository.find_remote_root_ref('')).to be_nil
|
|
end
|
|
|
|
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RemoteService, :find_remote_root_ref do
|
|
subject { repository.find_remote_root_ref('origin') }
|
|
end
|
|
end
|
|
|
|
describe "#log" do
|
|
shared_examples 'repository log' do
|
|
let(:commit_with_old_name) do
|
|
Gitlab::Git::Commit.find(repository, @commit_with_old_name_id)
|
|
end
|
|
let(:commit_with_new_name) do
|
|
Gitlab::Git::Commit.find(repository, @commit_with_new_name_id)
|
|
end
|
|
let(:rename_commit) do
|
|
Gitlab::Git::Commit.find(repository, @rename_commit_id)
|
|
end
|
|
|
|
before do
|
|
# Add new commits so that there's a renamed file in the commit history
|
|
@commit_with_old_name_id = new_commit_edit_old_file(repository_rugged).oid
|
|
@rename_commit_id = new_commit_move_file(repository_rugged).oid
|
|
@commit_with_new_name_id = new_commit_edit_new_file(repository_rugged).oid
|
|
end
|
|
|
|
after do
|
|
# Erase our commits so other tests get the original repo
|
|
repository_rugged.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
|
|
end
|
|
|
|
context "where 'follow' == true" do
|
|
let(:options) { { ref: "master", follow: true } }
|
|
|
|
context "and 'path' is a directory" do
|
|
it "does not follow renames" do
|
|
log_commits = repository.log(options.merge(path: "encoding"))
|
|
|
|
aggregate_failures do
|
|
expect(log_commits).to include(commit_with_new_name)
|
|
expect(log_commits).to include(rename_commit)
|
|
expect(log_commits).not_to include(commit_with_old_name)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "and 'path' is a file that matches the new filename" do
|
|
context 'without offset' do
|
|
it "follows renames" do
|
|
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG"))
|
|
|
|
aggregate_failures do
|
|
expect(log_commits).to include(commit_with_new_name)
|
|
expect(log_commits).to include(rename_commit)
|
|
expect(log_commits).to include(commit_with_old_name)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with offset=1' do
|
|
it "follows renames and skip the latest commit" do
|
|
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1))
|
|
|
|
aggregate_failures do
|
|
expect(log_commits).not_to include(commit_with_new_name)
|
|
expect(log_commits).to include(rename_commit)
|
|
expect(log_commits).to include(commit_with_old_name)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with offset=1', 'and limit=1' do
|
|
it "follows renames, skip the latest commit and return only one commit" do
|
|
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1))
|
|
|
|
expect(log_commits).to contain_exactly(rename_commit)
|
|
end
|
|
end
|
|
|
|
context 'with offset=1', 'and limit=2' do
|
|
it "follows renames, skip the latest commit and return only two commits" do
|
|
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2))
|
|
|
|
aggregate_failures do
|
|
expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with offset=2' do
|
|
it "follows renames and skip the latest commit" do
|
|
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2))
|
|
|
|
aggregate_failures do
|
|
expect(log_commits).not_to include(commit_with_new_name)
|
|
expect(log_commits).not_to include(rename_commit)
|
|
expect(log_commits).to include(commit_with_old_name)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with offset=2', 'and limit=1' do
|
|
it "follows renames, skip the two latest commit and return only one commit" do
|
|
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1))
|
|
|
|
expect(log_commits).to contain_exactly(commit_with_old_name)
|
|
end
|
|
end
|
|
|
|
context 'with offset=2', 'and limit=2' do
|
|
it "follows renames, skip the two latest commit and return only one commit" do
|
|
log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2))
|
|
|
|
aggregate_failures do
|
|
expect(log_commits).not_to include(commit_with_new_name)
|
|
expect(log_commits).not_to include(rename_commit)
|
|
expect(log_commits).to include(commit_with_old_name)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "and 'path' is a file that matches the old filename" do
|
|
it "does not follow renames" do
|
|
log_commits = repository.log(options.merge(path: "CHANGELOG"))
|
|
|
|
aggregate_failures do
|
|
expect(log_commits).not_to include(commit_with_new_name)
|
|
expect(log_commits).to include(rename_commit)
|
|
expect(log_commits).to include(commit_with_old_name)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "unknown ref" do
|
|
it "returns an empty array" do
|
|
log_commits = repository.log(options.merge(ref: 'unknown'))
|
|
|
|
expect(log_commits).to eq([])
|
|
end
|
|
end
|
|
end
|
|
|
|
context "where 'follow' == false" do
|
|
options = { follow: false }
|
|
|
|
context "and 'path' is a directory" do
|
|
let(:log_commits) do
|
|
repository.log(options.merge(path: "encoding"))
|
|
end
|
|
|
|
it "does not follow renames" do
|
|
expect(log_commits).to include(commit_with_new_name)
|
|
expect(log_commits).to include(rename_commit)
|
|
expect(log_commits).not_to include(commit_with_old_name)
|
|
end
|
|
end
|
|
|
|
context "and 'path' is a file that matches the new filename" do
|
|
let(:log_commits) do
|
|
repository.log(options.merge(path: "encoding/CHANGELOG"))
|
|
end
|
|
|
|
it "does not follow renames" do
|
|
expect(log_commits).to include(commit_with_new_name)
|
|
expect(log_commits).to include(rename_commit)
|
|
expect(log_commits).not_to include(commit_with_old_name)
|
|
end
|
|
end
|
|
|
|
context "and 'path' is a file that matches the old filename" do
|
|
let(:log_commits) do
|
|
repository.log(options.merge(path: "CHANGELOG"))
|
|
end
|
|
|
|
it "does not follow renames" do
|
|
expect(log_commits).to include(commit_with_old_name)
|
|
expect(log_commits).to include(rename_commit)
|
|
expect(log_commits).not_to include(commit_with_new_name)
|
|
end
|
|
end
|
|
|
|
context "and 'path' includes a directory that used to be a file" do
|
|
let(:log_commits) do
|
|
repository.log(options.merge(ref: "refs/heads/fix-blob-path", path: "files/testdir/file.txt"))
|
|
end
|
|
|
|
it "returns a list of commits" do
|
|
expect(log_commits.size).to eq(1)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "where provides 'after' timestamp" do
|
|
options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') }
|
|
|
|
it "should returns commits on or after that timestamp" do
|
|
commits = repository.log(options)
|
|
|
|
expect(commits.size).to be > 0
|
|
expect(commits).to satisfy do |commits|
|
|
commits.all? { |commit| commit.committed_date >= options[:after] }
|
|
end
|
|
end
|
|
end
|
|
|
|
context "where provides 'before' timestamp" do
|
|
options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') }
|
|
|
|
it "should returns commits on or before that timestamp" do
|
|
commits = repository.log(options)
|
|
|
|
expect(commits.size).to be > 0
|
|
expect(commits).to satisfy do |commits|
|
|
commits.all? { |commit| commit.committed_date <= options[:before] }
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when multiple paths are provided' do
|
|
let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } }
|
|
|
|
def commit_files(commit)
|
|
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
|
|
commit.deltas.flat_map do |delta|
|
|
[delta.old_path, delta.new_path].uniq.compact
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'only returns commits matching at least one path' do
|
|
commits = repository.log(options)
|
|
|
|
expect(commits.size).to be > 0
|
|
expect(commits).to satisfy do |commits|
|
|
commits.none? { |commit| (commit_files(commit) & options[:path]).empty? }
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'limit validation' do
|
|
where(:limit) do
|
|
[0, nil, '', 'foo']
|
|
end
|
|
|
|
with_them do
|
|
it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) }
|
|
end
|
|
end
|
|
|
|
context 'with all' do
|
|
it 'returns a list of commits' do
|
|
commits = repository.log({ all: true, limit: 50 })
|
|
|
|
expect(commits.size).to eq(37)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when Gitaly find_commits feature is enabled' do
|
|
it_behaves_like 'repository log'
|
|
end
|
|
end
|
|
|
|
describe '#count_commits_between' do
|
|
subject { repository.count_commits_between('feature', 'master') }
|
|
|
|
it { is_expected.to eq(17) }
|
|
end
|
|
|
|
describe '#raw_changes_between' do
|
|
let(:old_rev) { }
|
|
let(:new_rev) { }
|
|
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 '#count_commits' do
|
|
describe 'extended commit counting' do
|
|
context 'with after timestamp' do
|
|
it 'returns the number of commits after timestamp' do
|
|
options = { ref: 'master', after: Time.iso8601('2013-03-03T20:15:01+00:00') }
|
|
|
|
expect(repository.count_commits(options)).to eq(25)
|
|
end
|
|
end
|
|
|
|
context 'with before timestamp' do
|
|
it 'returns the number of commits before timestamp' do
|
|
options = { ref: 'feature', before: Time.iso8601('2015-03-03T20:15:01+00:00') }
|
|
|
|
expect(repository.count_commits(options)).to eq(9)
|
|
end
|
|
end
|
|
|
|
context 'with max_count' do
|
|
it 'returns the number of commits with path ' do
|
|
options = { ref: 'master', max_count: 5 }
|
|
|
|
expect(repository.count_commits(options)).to eq(5)
|
|
end
|
|
end
|
|
|
|
context 'with path' do
|
|
it 'returns the number of commits with path ' do
|
|
options = { ref: 'master', path: 'encoding' }
|
|
|
|
expect(repository.count_commits(options)).to eq(2)
|
|
end
|
|
end
|
|
|
|
context 'with option :from and option :to' do
|
|
it 'returns the number of commits ahead for fix-mode..fix-blob-path' do
|
|
options = { from: 'fix-mode', to: 'fix-blob-path' }
|
|
|
|
expect(repository.count_commits(options)).to eq(2)
|
|
end
|
|
|
|
it 'returns the number of commits ahead for fix-blob-path..fix-mode' do
|
|
options = { from: 'fix-blob-path', to: 'fix-mode' }
|
|
|
|
expect(repository.count_commits(options)).to eq(1)
|
|
end
|
|
|
|
context 'with option :left_right' do
|
|
it 'returns the number of commits for fix-mode...fix-blob-path' do
|
|
options = { from: 'fix-mode', to: 'fix-blob-path', left_right: true }
|
|
|
|
expect(repository.count_commits(options)).to eq([1, 2])
|
|
end
|
|
|
|
context 'with max_count' do
|
|
it 'returns the number of commits with path ' do
|
|
options = { from: 'fix-mode', to: 'fix-blob-path', left_right: true, max_count: 1 }
|
|
|
|
expect(repository.count_commits(options)).to eq([1, 1])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with max_count' do
|
|
it 'returns the number of commits up to the passed limit' do
|
|
options = { ref: 'master', max_count: 10, after: Time.iso8601('2013-03-03T20:15:01+00:00') }
|
|
|
|
expect(repository.count_commits(options)).to eq(10)
|
|
end
|
|
end
|
|
|
|
context "with all" do
|
|
it "returns the number of commits in the whole repository" do
|
|
options = { all: true }
|
|
|
|
expect(repository.count_commits(options)).to eq(34)
|
|
end
|
|
end
|
|
|
|
context 'without all or ref being specified' do
|
|
it "raises an ArgumentError" do
|
|
expect { repository.count_commits({}) }.to raise_error(ArgumentError)
|
|
end
|
|
end
|
|
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 '#ref_name_for_sha' do
|
|
let(:ref_path) { 'refs/heads' }
|
|
let(:sha) { repository.find_branch('master').dereferenced_target.id }
|
|
let(:ref_name) { 'refs/heads/master' }
|
|
|
|
it 'returns the ref name for the given sha' do
|
|
expect(repository.ref_name_for_sha(ref_path, sha)).to eq(ref_name)
|
|
end
|
|
|
|
it "returns an empty name if the ref doesn't exist" do
|
|
expect(repository.ref_name_for_sha(ref_path, "000000")).to eq("")
|
|
end
|
|
|
|
it "raise an exception if the ref is empty" do
|
|
expect { repository.ref_name_for_sha(ref_path, "") }.to raise_error(ArgumentError)
|
|
end
|
|
|
|
it "raise an exception if the ref is nil" do
|
|
expect { repository.ref_name_for_sha(ref_path, nil) }.to raise_error(ArgumentError)
|
|
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')
|
|
repository.create_branch('local_branch', 'master')
|
|
end
|
|
|
|
after do
|
|
ensure_seeds
|
|
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
|
|
|
|
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :branches
|
|
end
|
|
|
|
describe '#branch_count' do
|
|
it 'returns the number of branches' do
|
|
expect(repository.branch_count).to eq(11)
|
|
end
|
|
|
|
context 'with local and remote branches' do
|
|
let(:repository) { mutable_repository }
|
|
|
|
before do
|
|
create_remote_branch('joe', 'remote_branch', 'master')
|
|
repository.create_branch('local_branch', 'master')
|
|
end
|
|
|
|
after do
|
|
ensure_seeds
|
|
end
|
|
|
|
it 'returns the count of local branches' do
|
|
expect(repository.branch_count).to eq(repository.local_branches.count)
|
|
end
|
|
|
|
context 'with Gitaly disabled' do
|
|
before do
|
|
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
|
|
end
|
|
|
|
it 'returns the count of local branches' do
|
|
expect(repository.branch_count).to eq(repository.local_branches.count)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#merged_branch_names' do
|
|
context 'when branch names are passed' do
|
|
it 'only returns the names we are asking' do
|
|
names = repository.merged_branch_names(%w[merge-test])
|
|
|
|
expect(names).to contain_exactly('merge-test')
|
|
end
|
|
|
|
it 'does not return unmerged branch names' do
|
|
names = repository.merged_branch_names(%w[feature])
|
|
|
|
expect(names).to be_empty
|
|
end
|
|
end
|
|
|
|
context 'when no root ref is available' do
|
|
it 'returns empty list' do
|
|
project = create(:project, :empty_repo)
|
|
|
|
names = project.repository.merged_branch_names(%w[feature])
|
|
|
|
expect(names).to be_empty
|
|
end
|
|
end
|
|
|
|
context 'when no branch names are specified' do
|
|
before do
|
|
repository.create_branch('identical', 'master')
|
|
end
|
|
|
|
after do
|
|
ensure_seeds
|
|
end
|
|
|
|
it 'returns all merged branch names except for identical one' do
|
|
names = repository.merged_branch_names
|
|
|
|
expect(names).to include('merge-test')
|
|
expect(names).to include('fix-mode')
|
|
expect(names).not_to include('feature')
|
|
expect(names).not_to include('identical')
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#diff_stats' do
|
|
let(:left_commit_id) { 'feature' }
|
|
let(:right_commit_id) { 'master' }
|
|
|
|
it 'returns a DiffStatsCollection' do
|
|
collection = repository.diff_stats(left_commit_id, right_commit_id)
|
|
|
|
expect(collection).to be_a(Gitlab::Git::DiffStatsCollection)
|
|
expect(collection).to be_a(Enumerable)
|
|
end
|
|
|
|
it 'yields Gitaly::DiffStats objects' do
|
|
collection = repository.diff_stats(left_commit_id, right_commit_id)
|
|
|
|
expect(collection.to_a).to all(be_a(Gitaly::DiffStats))
|
|
end
|
|
|
|
it 'returns no Gitaly::DiffStats when SHAs are invalid' do
|
|
collection = repository.diff_stats('foo', 'bar')
|
|
|
|
expect(collection).to be_a(Gitlab::Git::DiffStatsCollection)
|
|
expect(collection).to be_a(Enumerable)
|
|
expect(collection.to_a).to be_empty
|
|
end
|
|
|
|
it 'returns no Gitaly::DiffStats when there is a nil SHA' do
|
|
collection = repository.diff_stats(nil, 'master')
|
|
|
|
expect(collection).to be_a(Gitlab::Git::DiffStatsCollection)
|
|
expect(collection).to be_a(Enumerable)
|
|
expect(collection.to_a).to be_empty
|
|
end
|
|
end
|
|
|
|
describe "#ls_files" do
|
|
let(:master_file_paths) { repository.ls_files("master") }
|
|
let(:utf8_file_paths) { repository.ls_files("ls-files-utf8") }
|
|
let(:not_existed_branch) { repository.ls_files("not_existed_branch") }
|
|
|
|
it "read every file paths of master branch" do
|
|
expect(master_file_paths.length).to equal(40)
|
|
end
|
|
|
|
it "reads full file paths of master branch" do
|
|
expect(master_file_paths).to include("files/html/500.html")
|
|
end
|
|
|
|
it "does not read submodule directory and empty directory of master branch" do
|
|
expect(master_file_paths).not_to include("six")
|
|
end
|
|
|
|
it "does not include 'nil'" do
|
|
expect(master_file_paths).not_to include(nil)
|
|
end
|
|
|
|
it "returns empty array when not existed branch" do
|
|
expect(not_existed_branch.length).to equal(0)
|
|
end
|
|
|
|
it "returns valid utf-8 data" do
|
|
expect(utf8_file_paths.map { |file| file.force_encoding('utf-8') }).to all(be_valid_encoding)
|
|
end
|
|
end
|
|
|
|
describe "#copy_gitattributes" do
|
|
let(:attributes_path) { File.join(SEED_STORAGE_PATH, TEST_REPO_PATH, 'info/attributes') }
|
|
|
|
after do
|
|
FileUtils.rm_rf(attributes_path) if Dir.exist?(attributes_path)
|
|
end
|
|
|
|
it "raises an error with invalid ref" do
|
|
expect { repository.copy_gitattributes("invalid") }.to raise_error(Gitlab::Git::Repository::InvalidRef)
|
|
end
|
|
|
|
context 'when forcing encoding issues' do
|
|
let(:branch_name) { "ʕ•ᴥ•ʔ" }
|
|
|
|
before do
|
|
repository.create_branch(branch_name, "master")
|
|
end
|
|
|
|
after do
|
|
repository.rm_branch(branch_name, user: build(:admin))
|
|
end
|
|
|
|
it "doesn't raise with a valid unicode ref" do
|
|
expect { repository.copy_gitattributes(branch_name) }.not_to raise_error
|
|
|
|
repository
|
|
end
|
|
end
|
|
|
|
context "with no .gitattrbutes" do
|
|
before do
|
|
repository.copy_gitattributes("master")
|
|
end
|
|
|
|
it "does not have an info/attributes" do
|
|
expect(File.exist?(attributes_path)).to be_falsey
|
|
end
|
|
end
|
|
|
|
context "with .gitattrbutes" do
|
|
before do
|
|
repository.copy_gitattributes("gitattributes")
|
|
end
|
|
|
|
it "has an info/attributes" do
|
|
expect(File.exist?(attributes_path)).to be_truthy
|
|
end
|
|
|
|
it "has the same content in info/attributes as .gitattributes" do
|
|
contents = File.open(attributes_path, "rb") { |f| f.read }
|
|
expect(contents).to eq("*.md binary\n")
|
|
end
|
|
end
|
|
|
|
context "with updated .gitattrbutes" do
|
|
before do
|
|
repository.copy_gitattributes("gitattributes")
|
|
repository.copy_gitattributes("gitattributes-updated")
|
|
end
|
|
|
|
it "has an info/attributes" do
|
|
expect(File.exist?(attributes_path)).to be_truthy
|
|
end
|
|
|
|
it "has the updated content in info/attributes" do
|
|
contents = File.read(attributes_path)
|
|
expect(contents).to eq("*.txt binary\n")
|
|
end
|
|
end
|
|
|
|
context "with no .gitattrbutes in HEAD but with previous info/attributes" do
|
|
before do
|
|
repository.copy_gitattributes("gitattributes")
|
|
repository.copy_gitattributes("master")
|
|
end
|
|
|
|
it "does not have an info/attributes" do
|
|
expect(File.exist?(attributes_path)).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#ref_exists?' do
|
|
it 'returns true for an existing tag' do
|
|
expect(repository.ref_exists?('refs/heads/master')).to eq(true)
|
|
end
|
|
|
|
it 'returns false for a non-existing tag' do
|
|
expect(repository.ref_exists?('refs/tags/THIS_TAG_DOES_NOT_EXIST')).to eq(false)
|
|
end
|
|
|
|
it 'raises an ArgumentError for an empty string' do
|
|
expect { repository.ref_exists?('') }.to raise_error(ArgumentError)
|
|
end
|
|
|
|
it 'raises an ArgumentError for an invalid ref' do
|
|
expect { repository.ref_exists?('INVALID') }.to raise_error(ArgumentError)
|
|
end
|
|
end
|
|
|
|
describe '#tag_exists?' do
|
|
it 'returns true for an existing tag' do
|
|
tag = repository.tag_names.first
|
|
|
|
expect(repository.tag_exists?(tag)).to eq(true)
|
|
end
|
|
|
|
it 'returns false for a non-existing tag' do
|
|
expect(repository.tag_exists?('v9000')).to eq(false)
|
|
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')
|
|
repository.create_branch('local_branch', 'master')
|
|
end
|
|
|
|
after do
|
|
ensure_seeds
|
|
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
|
|
|
|
it 'returns a Branch with UTF-8 fields' do
|
|
branches = repository.local_branches.to_a
|
|
expect(branches.size).to be > 0
|
|
branches.each do |branch|
|
|
expect(branch.name).to be_utf8
|
|
expect(branch.target).to be_utf8 unless branch.target.nil?
|
|
end
|
|
end
|
|
|
|
it 'gets the branches from GitalyClient' do
|
|
expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:local_branches)
|
|
.and_return([])
|
|
repository.local_branches
|
|
end
|
|
|
|
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :local_branches do
|
|
subject { repository.local_branches }
|
|
end
|
|
end
|
|
|
|
describe '#languages' do
|
|
it 'returns exactly the expected results' do
|
|
languages = repository.languages('4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6')
|
|
expected_languages = [
|
|
{ value: 66.63, label: "Ruby", color: "#701516", highlight: "#701516" },
|
|
{ value: 22.96, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" },
|
|
{ value: 7.9, label: "HTML", color: "#e34c26", highlight: "#e34c26" },
|
|
{ value: 2.51, label: "CoffeeScript", color: "#244776", highlight: "#244776" }
|
|
]
|
|
|
|
expect(languages.size).to eq(expected_languages.size)
|
|
|
|
expected_languages.size.times do |i|
|
|
a = expected_languages[i]
|
|
b = languages[i]
|
|
|
|
expect(a.keys.sort).to eq(b.keys.sort)
|
|
expect(a[:value]).to be_within(0.1).of(b[:value])
|
|
|
|
non_float_keys = a.keys - [:value]
|
|
expect(a.values_at(*non_float_keys)).to eq(b.values_at(*non_float_keys))
|
|
end
|
|
end
|
|
|
|
it "uses the repository's HEAD when no ref is passed" do
|
|
lang = repository.languages.first
|
|
|
|
expect(lang[:label]).to eq('Ruby')
|
|
end
|
|
end
|
|
|
|
describe '#license_short_name' do
|
|
subject { repository.license_short_name }
|
|
|
|
context 'when no license file can be found' do
|
|
let(:project) { create(:project, :repository) }
|
|
let(:repository) { project.repository.raw_repository }
|
|
|
|
before do
|
|
project.repository.delete_file(project.owner, 'LICENSE', message: 'remove license', branch_name: 'master')
|
|
end
|
|
|
|
it { is_expected.to be_nil }
|
|
end
|
|
|
|
context 'when an mit license is found' do
|
|
it { is_expected.to eq('mit') }
|
|
end
|
|
end
|
|
|
|
describe '#fetch_source_branch!' do
|
|
let(:local_ref) { 'refs/merge-requests/1/head' }
|
|
let(:source_repository) { mutable_repository }
|
|
|
|
after do
|
|
ensure_seeds
|
|
end
|
|
|
|
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(TestEnv.repos_path, 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(:project) { create(:project, :repository) }
|
|
let(:repository) { project.repository.raw }
|
|
let(:branch_name) { "to-be-deleted-soon" }
|
|
|
|
before do
|
|
project.add_developer(user)
|
|
repository.create_branch(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
|
|
|
|
context 'repository does not exist' do
|
|
it 'raises NoRepository and does not call Gitaly WriteConfig' do
|
|
repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '')
|
|
|
|
expect(repository.gitaly_repository_client).not_to receive(:write_config)
|
|
|
|
expect do
|
|
repository.write_config(full_path: 'foo/bar.git')
|
|
end.to raise_error(Gitlab::Git::Repository::NoRepository)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#set_config' do
|
|
let(:repository) { mutable_repository }
|
|
let(:entries) do
|
|
{
|
|
'test.foo1' => 'bla bla',
|
|
'test.foo2' => 1234,
|
|
'test.foo3' => true
|
|
}
|
|
end
|
|
|
|
it 'can set config settings' do
|
|
expect(repository.set_config(entries)).to be_nil
|
|
|
|
expect(repository_rugged.config['test.foo1']).to eq('bla bla')
|
|
expect(repository_rugged.config['test.foo2']).to eq('1234')
|
|
expect(repository_rugged.config['test.foo3']).to eq('true')
|
|
end
|
|
|
|
after do
|
|
entries.keys.each { |k| repository_rugged.config.delete(k) }
|
|
end
|
|
end
|
|
|
|
describe '#delete_config' do
|
|
let(:repository) { mutable_repository }
|
|
let(:entries) do
|
|
{
|
|
'test.foo1' => 'bla bla',
|
|
'test.foo2' => 1234,
|
|
'test.foo3' => true
|
|
}
|
|
end
|
|
|
|
it 'can delete config settings' do
|
|
entries.each do |key, value|
|
|
repository_rugged.config[key] = value
|
|
end
|
|
|
|
expect(repository.delete_config(*%w[does.not.exist test.foo1 test.foo2])).to be_nil
|
|
|
|
config_keys = repository_rugged.config.each_key.to_a
|
|
expect(config_keys).not_to include('test.foo1')
|
|
expect(config_keys).not_to include('test.foo2')
|
|
end
|
|
end
|
|
|
|
describe '#merge' do
|
|
let(:repository) { mutable_repository }
|
|
let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' }
|
|
let(:target_branch) { 'test-merge-target-branch' }
|
|
|
|
before do
|
|
repository.create_branch(target_branch, '6d394385cf567f80a8fd85055db1ab4c5295806f')
|
|
end
|
|
|
|
after do
|
|
ensure_seeds
|
|
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
|
|
repository.create_branch(target_branch, branch_head)
|
|
end
|
|
|
|
after do
|
|
ensure_seeds
|
|
end
|
|
|
|
subject { repository.ff_merge(user, source_sha, target_branch) }
|
|
|
|
shared_examples '#ff_merge' do
|
|
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
|
|
|
|
it "calls Gitaly's OperationService" do
|
|
expect_any_instance_of(Gitlab::GitalyClient::OperationService)
|
|
.to receive(:user_ff_branch).with(user, source_sha, target_branch)
|
|
.and_return(nil)
|
|
|
|
subject
|
|
end
|
|
|
|
it_behaves_like '#ff_merge'
|
|
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
|
|
|
|
after do
|
|
ensure_seeds
|
|
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.ref_exists?("refs/delete/a")).to be(false)
|
|
expect(repository.ref_exists?("refs/also-delete/b")).to be(false)
|
|
expect(repository.ref_exists?("refs/keep/c")).to be(true)
|
|
expect(repository.ref_exists?("refs/also-keep/d")).to be(true)
|
|
expect(repository.ref_exists?("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' }
|
|
|
|
after do
|
|
ensure_seeds
|
|
end
|
|
|
|
describe '#add_remote' do
|
|
let(:mirror_refmap) { '+refs/*:refs/*' }
|
|
|
|
it 'added the remote' do
|
|
begin
|
|
repository_rugged.remotes.delete(remote_name)
|
|
rescue Rugged::ConfigError
|
|
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 '#bundle_to_disk' do
|
|
let(:save_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
|
|
|
|
after do
|
|
FileUtils.rm_rf(save_path)
|
|
end
|
|
|
|
it 'saves a bundle to disk' do
|
|
repository.bundle_to_disk(save_path)
|
|
|
|
success = system(
|
|
*%W(#{Gitlab.config.git.bin_path} -C #{repository_path} bundle verify #{save_path}),
|
|
[:out, :err] => '/dev/null'
|
|
)
|
|
expect(success).to be true
|
|
end
|
|
end
|
|
|
|
describe '#create_from_bundle' do
|
|
let(:bundle_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
|
|
let(:project) { create(:project) }
|
|
let(:imported_repo) { project.repository.raw }
|
|
|
|
before do
|
|
expect(repository.bundle_to_disk(bundle_path)).to be_truthy
|
|
end
|
|
|
|
after do
|
|
FileUtils.rm_rf(bundle_path)
|
|
end
|
|
|
|
it 'creates a repo from a bundle file' do
|
|
expect(imported_repo).not_to exist
|
|
|
|
result = imported_repo.create_from_bundle(bundle_path)
|
|
|
|
expect(result).to be_truthy
|
|
expect(imported_repo).to exist
|
|
expect { imported_repo.fsck }.not_to raise_exception
|
|
end
|
|
|
|
it 'creates a symlink to the global hooks dir' do
|
|
imported_repo.create_from_bundle(bundle_path)
|
|
hooks_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { File.join(imported_repo.path, 'hooks') }
|
|
|
|
expect(File.readlink(hooks_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
|
|
end
|
|
end
|
|
|
|
describe '#checksum' do
|
|
it 'calculates the checksum for non-empty repo' do
|
|
expect(repository.checksum).to eq '51d0a9662681f93e1fee547a6b7ba2bcaf716059'
|
|
end
|
|
|
|
it 'returns 0000000000000000000000000000000000000000 for an empty repo' do
|
|
FileUtils.rm_rf(File.join(storage_path, 'empty-repo.git'))
|
|
|
|
system(git_env, *%W(#{Gitlab.config.git.bin_path} init --bare empty-repo.git),
|
|
chdir: storage_path,
|
|
out: '/dev/null',
|
|
err: '/dev/null')
|
|
|
|
empty_repo = described_class.new('default', 'empty-repo.git', '')
|
|
|
|
expect(empty_repo.checksum).to eq '0000000000000000000000000000000000000000'
|
|
end
|
|
|
|
it 'raises Gitlab::Git::Repository::InvalidRepository error for non-valid git repo' do
|
|
FileUtils.rm_rf(File.join(storage_path, 'non-valid.git'))
|
|
|
|
system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} non-valid.git),
|
|
chdir: SEED_STORAGE_PATH,
|
|
out: '/dev/null',
|
|
err: '/dev/null')
|
|
|
|
File.truncate(File.join(storage_path, 'non-valid.git/HEAD'), 0)
|
|
|
|
non_valid = described_class.new('default', 'non-valid.git', '')
|
|
|
|
expect { non_valid.checksum }.to raise_error(Gitlab::Git::Repository::InvalidRepository)
|
|
end
|
|
|
|
it 'raises Gitlab::Git::Repository::NoRepository error when there is no repo' do
|
|
broken_repo = described_class.new('default', 'a/path.git', '')
|
|
|
|
expect { broken_repo.checksum }.to raise_error(Gitlab::Git::Repository::NoRepository)
|
|
end
|
|
end
|
|
|
|
describe '#clean_stale_repository_files' do
|
|
let(:worktree_path) { File.join(repository_path, 'worktrees', 'delete-me') }
|
|
|
|
it 'cleans up the files' do
|
|
create_worktree = %W[git -C #{repository_path} worktree add --detach #{worktree_path} master]
|
|
raise 'preparation failed' unless system(*create_worktree, err: '/dev/null')
|
|
|
|
FileUtils.touch(worktree_path, mtime: Time.now - 8.hours)
|
|
# git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object,
|
|
# but the HEAD must be 40 characters long or git will ignore it.
|
|
File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA)
|
|
|
|
# git 2.16 fails with "fatal: bad object HEAD"
|
|
expect(rev_list_all).to be false
|
|
|
|
repository.clean_stale_repository_files
|
|
|
|
expect(rev_list_all).to be true
|
|
expect(File.exist?(worktree_path)).to be_falsey
|
|
end
|
|
|
|
def rev_list_all
|
|
system(*%W[git -C #{repository_path} rev-list --all], out: '/dev/null', err: '/dev/null')
|
|
end
|
|
|
|
it 'increments a counter upon an error' do
|
|
expect(repository.gitaly_repository_client).to receive(:cleanup).and_raise(Gitlab::Git::CommandError)
|
|
|
|
counter = double(:counter)
|
|
|
|
expect(counter).to receive(:increment)
|
|
expect(Gitlab::Metrics).to receive(:counter).with(:failed_repository_cleanup_total,
|
|
'Number of failed repository cleanup events').and_return(counter)
|
|
|
|
repository.clean_stale_repository_files
|
|
end
|
|
end
|
|
|
|
describe '#squash' do
|
|
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
|
|
|
|
# Should be ported to gitaly-ruby rspec suite https://gitlab.com/gitlab-org/gitaly/issues/1234
|
|
skip '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
|
|
|
|
# Should be ported to gitaly-ruby rspec suite https://gitlab.com/gitlab-org/gitaly/issues/1234
|
|
skip 'with an ASCII-8BIT diff' do
|
|
let(:diff) { "diff --git a/README.md b/README.md\nindex faaf198..43c5edf 100644\n--- a/README.md\n+++ b/README.md\n@@ -1,4 +1,4 @@\n-testme\n+✓ testme\n ======\n \n Sample repo for testing gitlab features\n" }
|
|
|
|
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
|
|
|
|
# Should be ported to gitaly-ruby rspec suite https://gitlab.com/gitlab-org/gitaly/issues/1234
|
|
skip 'with trailing whitespace in an invalid patch' do
|
|
let(:diff) { "diff --git a/README.md b/README.md\nindex faaf198..43c5edf 100644\n--- a/README.md\n+++ b/README.md\n@@ -1,4 +1,4 @@\n-testme\n+ \n ====== \n \n Sample repo for testing gitlab features\n" }
|
|
|
|
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
|
|
|
|
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: 0100644)
|
|
|
|
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. Replaces the
|
|
# contents of encoding/CHANGELOG with new text.
|
|
def new_commit_edit_new_file(repo)
|
|
oid = repo.write("I'm a new changelog with different text", :blob)
|
|
index = repo.index
|
|
index.read_tree(repo.head.target.tree)
|
|
index.add(path: "encoding/CHANGELOG", oid: oid, mode: 0100644)
|
|
|
|
options = commit_options(repo, index, "Edit encoding/CHANGELOG")
|
|
|
|
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: 0100644)
|
|
index.remove("CHANGELOG")
|
|
|
|
options = commit_options(repo, index, "Move CHANGELOG to encoding/")
|
|
|
|
sha = Rugged::Commit.create(repo, options)
|
|
repo.lookup(sha)
|
|
end
|
|
|
|
def refs(dir)
|
|
IO.popen(%W[git -C #{dir} for-each-ref], &:read).split("\n").map do |line|
|
|
line.split("\t").last
|
|
end
|
|
end
|
|
end
|