From a00578ce5cdf4bab95990bca9e806c6322bb1384 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 4 Jan 2017 13:43:06 -0500 Subject: Absorb gitlab_git --- spec/lib/gitlab/git/attributes_spec.rb | 150 ++++ spec/lib/gitlab/git/blame_spec.rb | 66 ++ spec/lib/gitlab/git/blob_snippet_spec.rb | 19 + spec/lib/gitlab/git/blob_spec.rb | 489 +++++++++++ spec/lib/gitlab/git/branch_spec.rb | 31 + spec/lib/gitlab/git/commit_spec.rb | 408 +++++++++ spec/lib/gitlab/git/compare_spec.rb | 109 +++ spec/lib/gitlab/git/diff_collection_spec.rb | 460 +++++++++++ spec/lib/gitlab/git/diff_spec.rb | 287 +++++++ spec/lib/gitlab/git/encoding_helper_spec.rb | 84 ++ spec/lib/gitlab/git/repository_spec.rb | 1184 +++++++++++++++++++++++++++ spec/lib/gitlab/git/tag_spec.rb | 25 + spec/lib/gitlab/git/tree_spec.rb | 76 ++ spec/lib/gitlab/git/util_spec.rb | 16 + 14 files changed, 3404 insertions(+) create mode 100644 spec/lib/gitlab/git/attributes_spec.rb create mode 100644 spec/lib/gitlab/git/blame_spec.rb create mode 100644 spec/lib/gitlab/git/blob_snippet_spec.rb create mode 100644 spec/lib/gitlab/git/blob_spec.rb create mode 100644 spec/lib/gitlab/git/branch_spec.rb create mode 100644 spec/lib/gitlab/git/commit_spec.rb create mode 100644 spec/lib/gitlab/git/compare_spec.rb create mode 100644 spec/lib/gitlab/git/diff_collection_spec.rb create mode 100644 spec/lib/gitlab/git/diff_spec.rb create mode 100644 spec/lib/gitlab/git/encoding_helper_spec.rb create mode 100644 spec/lib/gitlab/git/repository_spec.rb create mode 100644 spec/lib/gitlab/git/tag_spec.rb create mode 100644 spec/lib/gitlab/git/tree_spec.rb create mode 100644 spec/lib/gitlab/git/util_spec.rb (limited to 'spec/lib') diff --git a/spec/lib/gitlab/git/attributes_spec.rb b/spec/lib/gitlab/git/attributes_spec.rb new file mode 100644 index 00000000000..9c011e34c11 --- /dev/null +++ b/spec/lib/gitlab/git/attributes_spec.rb @@ -0,0 +1,150 @@ +require 'spec_helper' + +describe Gitlab::Git::Attributes, seed_helper: true do + let(:path) do + File.join(SEED_REPOSITORY_PATH, 'with-git-attributes.git') + end + + subject { described_class.new(path) } + + describe '#attributes' do + context 'using a path with attributes' do + it 'returns the attributes as a Hash' do + expect(subject.attributes('test.txt')).to eq({ 'text' => true }) + end + + it 'returns a Hash containing multiple attributes' do + expect(subject.attributes('test.sh')). + to eq({ 'eol' => 'lf', 'gitlab-language' => 'shell' }) + end + + it 'returns a Hash containing attributes for a file with multiple extensions' do + expect(subject.attributes('test.haml.html')). + to eq({ 'gitlab-language' => 'haml' }) + end + + it 'returns a Hash containing attributes for a file in a directory' do + expect(subject.attributes('foo/bar.txt')).to eq({ 'foo' => true }) + end + + it 'returns a Hash containing attributes with query string parameters' do + expect(subject.attributes('foo.cgi')). + to eq({ 'key' => 'value?p1=v1&p2=v2' }) + end + + it 'returns a Hash containing the attributes for an absolute path' do + expect(subject.attributes('/test.txt')).to eq({ 'text' => true }) + end + + it 'returns a Hash containing the attributes when a pattern is defined using an absolute path' do + # When a path is given without a leading slash it should still match + # patterns defined with a leading slash. + expect(subject.attributes('foo.png')). + to eq({ 'gitlab-language' => 'png' }) + + expect(subject.attributes('/foo.png')). + to eq({ 'gitlab-language' => 'png' }) + end + + it 'returns an empty Hash for a defined path without attributes' do + expect(subject.attributes('bla/bla.txt')).to eq({}) + end + + context 'when the "binary" option is set for a path' do + it 'returns true for the "binary" option' do + expect(subject.attributes('test.binary')['binary']).to eq(true) + end + + it 'returns false for the "diff" option' do + expect(subject.attributes('test.binary')['diff']).to eq(false) + end + end + end + + context 'using a path without any attributes' do + it 'returns an empty Hash' do + expect(subject.attributes('test.foo')).to eq({}) + end + end + end + + describe '#patterns' do + it 'parses a file with entries' do + expect(subject.patterns).to be_an_instance_of(Hash) + end + + it 'parses an entry that uses a tab to separate the pattern and attributes' do + expect(subject.patterns[File.join(path, '*.md')]). + to eq({ 'gitlab-language' => 'markdown' }) + end + + it 'stores patterns in reverse order' do + first = subject.patterns.to_a[0] + + expect(first[0]).to eq(File.join(path, 'bla/bla.txt')) + end + + # It's a bit hard to test for something _not_ being processed. As such we'll + # just test the number of entries. + it 'ignores any comments and empty lines' do + expect(subject.patterns.length).to eq(10) + end + + it 'does not parse anything when the attributes file does not exist' do + expect(File).to receive(:exist?). + with(File.join(path, 'info/attributes')). + and_return(false) + + expect(subject.patterns).to eq({}) + end + end + + describe '#parse_attributes' do + it 'parses a boolean attribute' do + expect(subject.parse_attributes('text')).to eq({ 'text' => true }) + end + + it 'parses a negated boolean attribute' do + expect(subject.parse_attributes('-text')).to eq({ 'text' => false }) + end + + it 'parses a key-value pair' do + expect(subject.parse_attributes('foo=bar')).to eq({ 'foo' => 'bar' }) + end + + it 'parses multiple attributes' do + input = 'boolean key=value -negated' + + expect(subject.parse_attributes(input)). + to eq({ 'boolean' => true, 'key' => 'value', 'negated' => false }) + end + + it 'parses attributes with query string parameters' do + expect(subject.parse_attributes('foo=bar?baz=1')). + to eq({ 'foo' => 'bar?baz=1' }) + end + end + + describe '#each_line' do + it 'iterates over every line in the attributes file' do + args = [String] * 14 # the number of lines in the file + + expect { |b| subject.each_line(&b) }.to yield_successive_args(*args) + end + + it 'does not yield when the attributes file does not exist' do + expect(File).to receive(:exist?). + with(File.join(path, 'info/attributes')). + and_return(false) + + expect { |b| subject.each_line(&b) }.not_to yield_control + end + + it 'does not yield when the attributes file has an unsupported encoding' do + path = File.join(SEED_REPOSITORY_PATH, 'with-invalid-git-attributes.git') + attrs = described_class.new(path) + + expect { |b| attrs.each_line(&b) }.not_to yield_control + end + end +end diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb new file mode 100644 index 00000000000..e169f5af6b6 --- /dev/null +++ b/spec/lib/gitlab/git/blame_spec.rb @@ -0,0 +1,66 @@ +# coding: utf-8 +require "spec_helper" + +describe Gitlab::Git::Blame, seed_helper: true do + let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:blame) do + Gitlab::Git::Blame.new(repository, SeedRepo::Commit::ID, "CONTRIBUTING.md") + end + + context "each count" do + it do + data = [] + blame.each do |commit, line| + data << { + commit: commit, + line: line + } + end + + expect(data.size).to eq(95) + expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) + expect(data.first[:line]).to eq("# Contribute to GitLab") + end + end + + context "ISO-8859 encoding" do + let(:blame) do + Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt") + end + + it 'converts to UTF-8' do + data = [] + blame.each do |commit, line| + data << { + commit: commit, + line: line + } + end + + expect(data.size).to eq(1) + expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) + expect(data.first[:line]).to eq("Ä ü") + end + end + + context "unknown encoding" do + let(:blame) do + Gitlab::Git::Blame.new(repository, SeedRepo::EncodingCommit::ID, "encoding/iso8859.txt") + end + + it 'converts to UTF-8' do + expect(CharlockHolmes::EncodingDetector).to receive(:detect).and_return(nil) + data = [] + blame.each do |commit, line| + data << { + commit: commit, + line: line + } + end + + expect(data.size).to eq(1) + expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) + expect(data.first[:line]).to eq(" ") + end + end +end diff --git a/spec/lib/gitlab/git/blob_snippet_spec.rb b/spec/lib/gitlab/git/blob_snippet_spec.rb new file mode 100644 index 00000000000..79b1311ac91 --- /dev/null +++ b/spec/lib/gitlab/git/blob_snippet_spec.rb @@ -0,0 +1,19 @@ +# encoding: UTF-8 + +require "spec_helper" + +describe Gitlab::Git::BlobSnippet, seed_helper: true do + describe :data do + context 'empty lines' do + let(:snippet) { Gitlab::Git::BlobSnippet.new('master', nil, nil, nil) } + + it { expect(snippet.data).to be_nil } + end + + context 'present lines' do + let(:snippet) { Gitlab::Git::BlobSnippet.new('master', ['wow', 'much'], 1, 'wow.rb') } + + it { expect(snippet.data).to eq("wow\nmuch") } + end + end +end diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb new file mode 100644 index 00000000000..84f79ec2391 --- /dev/null +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -0,0 +1,489 @@ +# encoding: utf-8 + +require "spec_helper" + +describe Gitlab::Git::Blob, seed_helper: true do + let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + + describe :initialize do + let(:blob) { Gitlab::Git::Blob.new(name: 'test') } + + it 'handles nil data' do + expect(blob.name).to eq('test') + expect(blob.size).to eq(nil) + expect(blob.loaded_size).to eq(nil) + end + end + + describe :find do + context 'file in subdir' do + let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") } + + it { expect(blob.id).to eq(SeedRepo::RubyBlob::ID) } + it { expect(blob.name).to eq(SeedRepo::RubyBlob::NAME) } + it { expect(blob.path).to eq("files/ruby/popen.rb") } + it { expect(blob.commit_id).to eq(SeedRepo::Commit::ID) } + it { expect(blob.data[0..10]).to eq(SeedRepo::RubyBlob::CONTENT[0..10]) } + it { expect(blob.size).to eq(669) } + it { expect(blob.mode).to eq("100644") } + end + + context 'file in root' do + let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, ".gitignore") } + + it { expect(blob.id).to eq("dfaa3f97ca337e20154a98ac9d0be76ddd1fcc82") } + it { expect(blob.name).to eq(".gitignore") } + it { expect(blob.path).to eq(".gitignore") } + it { expect(blob.commit_id).to eq(SeedRepo::Commit::ID) } + it { expect(blob.data[0..10]).to eq("*.rbc\n*.sas") } + it { expect(blob.size).to eq(241) } + it { expect(blob.mode).to eq("100644") } + it { expect(blob).not_to be_binary } + end + + context 'file in root with leading slash' do + let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "/.gitignore") } + + it { expect(blob.id).to eq("dfaa3f97ca337e20154a98ac9d0be76ddd1fcc82") } + it { expect(blob.name).to eq(".gitignore") } + it { expect(blob.path).to eq(".gitignore") } + it { expect(blob.commit_id).to eq(SeedRepo::Commit::ID) } + it { expect(blob.data[0..10]).to eq("*.rbc\n*.sas") } + it { expect(blob.size).to eq(241) } + it { expect(blob.mode).to eq("100644") } + end + + context 'non-exist file' do + let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "missing.rb") } + + it { expect(blob).to be_nil } + end + + context 'six submodule' do + let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, 'six') } + + it { expect(blob.id).to eq('409f37c4f05865e4fb208c771485f211a22c4c2d') } + it { expect(blob.data).to eq('') } + + it 'does not get messed up by load_all_data!' do + blob.load_all_data!(repository) + expect(blob.data).to eq('') + end + + it 'does not mark the blob as binary' do + expect(blob).not_to be_binary + end + end + + context 'large file' do + let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, 'files/images/6049019_460s.jpg') } + let(:blob_size) { 111803 } + + it { expect(blob.size).to eq(blob_size) } + it { expect(blob.data.length).to eq(blob_size) } + + it 'check that this test is sane' do + expect(blob.size).to be <= Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE + end + + it 'can load all data' do + blob.load_all_data!(repository) + expect(blob.data.length).to eq(blob_size) + end + + it 'marks the blob as binary' do + expect(Gitlab::Git::Blob).to receive(:new). + with(hash_including(binary: true)). + and_call_original + + expect(blob).to be_binary + end + end + end + + describe :raw do + let(:raw_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::RubyBlob::ID) } + it { expect(raw_blob.id).to eq(SeedRepo::RubyBlob::ID) } + it { expect(raw_blob.data[0..10]).to eq("require \'fi") } + it { expect(raw_blob.size).to eq(669) } + it { expect(raw_blob.truncated?).to be_falsey } + + context 'large file' do + it 'limits the size of a large file' do + blob_size = Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE + 1 + buffer = Array.new(blob_size, 0) + rugged_blob = Rugged::Blob.from_buffer(repository.rugged, buffer.join('')) + blob = Gitlab::Git::Blob.raw(repository, rugged_blob) + + expect(blob.size).to eq(blob_size) + expect(blob.loaded_size).to eq(Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE) + expect(blob.data.length).to eq(Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE) + expect(blob.truncated?).to be_truthy + + blob.load_all_data!(repository) + expect(blob.loaded_size).to eq(blob_size) + end + end + end + + describe 'encoding' do + context 'file with russian text' do + let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "encoding/russian.rb") } + + it { expect(blob.name).to eq("russian.rb") } + it { expect(blob.data.lines.first).to eq("Хороший файл") } + it { expect(blob.size).to eq(23) } + it { expect(blob.truncated?).to be_falsey } + # Run it twice since data is encoded after the first run + it { expect(blob.truncated?).to be_falsey } + it { expect(blob.mode).to eq("100755") } + end + + context 'file with Chinese text' do + let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "encoding/テスト.txt") } + + it { expect(blob.name).to eq("テスト.txt") } + it { expect(blob.data).to include("これはテスト") } + it { expect(blob.size).to eq(340) } + it { expect(blob.mode).to eq("100755") } + it { expect(blob.truncated?).to be_falsey } + end + + context 'file with ISO-8859 text' do + let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::LastCommit::ID, "encoding/iso8859.txt") } + + it { expect(blob.name).to eq("iso8859.txt") } + it { expect(blob.loaded_size).to eq(4) } + it { expect(blob.size).to eq(4) } + it { expect(blob.mode).to eq("100644") } + it { expect(blob.truncated?).to be_falsey } + end + end + + describe 'mode' do + context 'file regular' do + let(:blob) do + Gitlab::Git::Blob.find( + repository, + 'fa1b1e6c004a68b7d8763b86455da9e6b23e36d6', + 'files/ruby/regex.rb' + ) + end + + it { expect(blob.name).to eq('regex.rb') } + it { expect(blob.path).to eq('files/ruby/regex.rb') } + it { expect(blob.size).to eq(1200) } + it { expect(blob.mode).to eq("100644") } + end + + context 'file binary' do + let(:blob) do + Gitlab::Git::Blob.find( + repository, + 'fa1b1e6c004a68b7d8763b86455da9e6b23e36d6', + 'files/executables/ls' + ) + end + + it { expect(blob.name).to eq('ls') } + it { expect(blob.path).to eq('files/executables/ls') } + it { expect(blob.size).to eq(110080) } + it { expect(blob.mode).to eq("100755") } + end + + context 'file symlink to regular' do + let(:blob) do + Gitlab::Git::Blob.find( + repository, + 'fa1b1e6c004a68b7d8763b86455da9e6b23e36d6', + 'files/links/ruby-style-guide.md' + ) + end + + it { expect(blob.name).to eq('ruby-style-guide.md') } + it { expect(blob.path).to eq('files/links/ruby-style-guide.md') } + it { expect(blob.size).to eq(31) } + it { expect(blob.mode).to eq("120000") } + end + + context 'file symlink to binary' do + let(:blob) do + Gitlab::Git::Blob.find( + repository, + 'fa1b1e6c004a68b7d8763b86455da9e6b23e36d6', + 'files/links/touch' + ) + end + + it { expect(blob.name).to eq('touch') } + it { expect(blob.path).to eq('files/links/touch') } + it { expect(blob.size).to eq(20) } + it { expect(blob.mode).to eq("120000") } + end + end + + describe :commit do + let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + + let(:commit_options) do + { + file: { + content: 'Lorem ipsum...', + path: 'documents/story.txt' + }, + author: { + email: 'user@example.com', + name: 'Test User', + time: Time.now + }, + committer: { + email: 'user@example.com', + name: 'Test User', + time: Time.now + }, + commit: { + message: 'Wow such commit', + branch: 'fix-mode' + } + } + end + + let(:commit_sha) { Gitlab::Git::Blob.commit(repository, commit_options) } + let(:commit) { repository.lookup(commit_sha) } + + it 'should add file with commit' do + # Commit message valid + expect(commit.message).to eq('Wow such commit') + + tree = commit.tree.to_a.find { |tree| tree[:name] == 'documents' } + + # Directory was created + expect(tree[:type]).to eq(:tree) + + # File was created + expect(repository.lookup(tree[:oid]).first[:name]).to eq('story.txt') + end + + describe "ref updating" do + it 'creates a commit but does not udate a ref' do + commit_opts = commit_options.tap{ |opts| opts[:commit][:update_ref] = false} + commit_sha = Gitlab::Git::Blob.commit(repository, commit_opts) + commit = repository.lookup(commit_sha) + + # Commit message valid + expect(commit.message).to eq('Wow such commit') + + # Does not update any related ref + expect(repository.lookup("fix-mode").oid).not_to eq(commit.oid) + expect(repository.lookup("HEAD").oid).not_to eq(commit.oid) + end + end + + describe 'reject updates' do + it 'should reject updates' do + commit_options[:file][:update] = false + commit_options[:file][:path] = 'files/executables/ls' + + expect{ commit_sha }.to raise_error('Filename already exists; update not allowed') + end + end + + describe 'file modes' do + it 'should preserve file modes with commit' do + commit_options[:file][:path] = 'files/executables/ls' + + entry = Gitlab::Git::Blob::find_entry_by_path(repository, commit.tree.oid, commit_options[:file][:path]) + expect(entry[:filemode]).to eq(0100755) + end + end + end + + describe :rename do + let(:repository) { Gitlab::Git::Repository.new(TEST_NORMAL_REPO_PATH) } + let(:rename_options) do + { + file: { + path: 'NEWCONTRIBUTING.md', + previous_path: 'CONTRIBUTING.md', + content: 'Lorem ipsum...', + update: true + }, + author: { + email: 'user@example.com', + name: 'Test User', + time: Time.now + }, + committer: { + email: 'user@example.com', + name: 'Test User', + time: Time.now + }, + commit: { + message: 'Rename readme', + branch: 'master' + } + } + end + + let(:rename_options2) do + { + file: { + content: 'Lorem ipsum...', + path: 'bin/new_executable', + previous_path: 'bin/executable', + }, + author: { + email: 'user@example.com', + name: 'Test User', + time: Time.now + }, + committer: { + email: 'user@example.com', + name: 'Test User', + time: Time.now + }, + commit: { + message: 'Updates toberenamed.txt', + branch: 'master', + update_ref: false + } + } + end + + it 'maintains file permissions when renaming' do + mode = 0o100755 + + Gitlab::Git::Blob.rename(repository, rename_options2) + + expect(repository.rugged.index.get(rename_options2[:file][:path])[:mode]).to eq(mode) + end + + it 'renames the file with commit and not change file permissions' do + ref = rename_options[:commit][:branch] + + expect(repository.rugged.index.get('CONTRIBUTING.md')).not_to be_nil + expect { Gitlab::Git::Blob.rename(repository, rename_options) }.to change { repository.commit_count(ref) }.by(1) + + expect(repository.rugged.index.get('CONTRIBUTING.md')).to be_nil + expect(repository.rugged.index.get('NEWCONTRIBUTING.md')).not_to be_nil + end + end + + describe :remove do + let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + + let(:commit_options) do + { + file: { + path: 'README.md' + }, + author: { + email: 'user@example.com', + name: 'Test User', + time: Time.now + }, + committer: { + email: 'user@example.com', + name: 'Test User', + time: Time.now + }, + commit: { + message: 'Remove readme', + branch: 'feature' + } + } + end + + let(:commit_sha) { Gitlab::Git::Blob.remove(repository, commit_options) } + let(:commit) { repository.lookup(commit_sha) } + let(:blob) { Gitlab::Git::Blob.find(repository, commit_sha, "README.md") } + + it 'should remove file with commit' do + # Commit message valid + expect(commit.message).to eq('Remove readme') + + # File was removed + expect(blob).to be_nil + end + end + + describe :lfs_pointers do + context 'file a valid lfs pointer' do + let(:blob) do + Gitlab::Git::Blob.find( + repository, + '33bcff41c232a11727ac6d660bd4b0c2ba86d63d', + 'files/lfs/image.jpg' + ) + end + + it { expect(blob.lfs_pointer?).to eq(true) } + it { expect(blob.lfs_oid).to eq("4206f951d2691c78aac4c0ce9f2b23580b2c92cdcc4336e1028742c0274938e0") } + it { expect(blob.lfs_size).to eq("19548") } + it { expect(blob.id).to eq("f4d76af13003d1106be7ac8c5a2a3d37ddf32c2a") } + it { expect(blob.name).to eq("image.jpg") } + it { expect(blob.path).to eq("files/lfs/image.jpg") } + it { expect(blob.size).to eq(130) } + it { expect(blob.mode).to eq("100644") } + end + + describe 'file an invalid lfs pointer' do + context 'with correct version header but incorrect size and oid' do + let(:blob) do + Gitlab::Git::Blob.find( + repository, + '33bcff41c232a11727ac6d660bd4b0c2ba86d63d', + 'files/lfs/archive-invalid.tar' + ) + end + + it { expect(blob.lfs_pointer?).to eq(false) } + it { expect(blob.lfs_oid).to eq(nil) } + it { expect(blob.lfs_size).to eq(nil) } + it { expect(blob.id).to eq("f8a898db217a5a85ed8b3d25b34c1df1d1094c46") } + it { expect(blob.name).to eq("archive-invalid.tar") } + it { expect(blob.path).to eq("files/lfs/archive-invalid.tar") } + it { expect(blob.size).to eq(43) } + it { expect(blob.mode).to eq("100644") } + end + + context 'with correct version header and size but incorrect size and oid' do + let(:blob) do + Gitlab::Git::Blob.find( + repository, + '33bcff41c232a11727ac6d660bd4b0c2ba86d63d', + 'files/lfs/picture-invalid.png' + ) + end + + it { expect(blob.lfs_pointer?).to eq(false) } + it { expect(blob.lfs_oid).to eq(nil) } + it { expect(blob.lfs_size).to eq("1575078") } + it { expect(blob.id).to eq("5ae35296e1f95c1ef9feda1241477ed29a448572") } + it { expect(blob.name).to eq("picture-invalid.png") } + it { expect(blob.path).to eq("files/lfs/picture-invalid.png") } + it { expect(blob.size).to eq(57) } + it { expect(blob.mode).to eq("100644") } + end + + context 'with correct version header and size but invalid size and oid' do + let(:blob) do + Gitlab::Git::Blob.find( + repository, + '33bcff41c232a11727ac6d660bd4b0c2ba86d63d', + 'files/lfs/file-invalid.zip' + ) + end + + it { expect(blob.lfs_pointer?).to eq(false) } + it { expect(blob.lfs_oid).to eq(nil) } + it { expect(blob.lfs_size).to eq(nil) } + it { expect(blob.id).to eq("d831981bd876732b85a1bcc6cc01210c9f36248f") } + it { expect(blob.name).to eq("file-invalid.zip") } + it { expect(blob.path).to eq("files/lfs/file-invalid.zip") } + it { expect(blob.size).to eq(60) } + it { expect(blob.mode).to eq("100644") } + end + end + end +end diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb new file mode 100644 index 00000000000..78234b396c5 --- /dev/null +++ b/spec/lib/gitlab/git/branch_spec.rb @@ -0,0 +1,31 @@ +require "spec_helper" + +describe Gitlab::Git::Branch, seed_helper: true do + let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + + subject { repository.branches } + + it { is_expected.to be_kind_of Array } + + describe '#size' do + subject { super().size } + it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) } + end + + describe 'first branch' do + let(:branch) { repository.branches.first } + + it { expect(branch.name).to eq(SeedRepo::Repo::BRANCHES.first) } + it { expect(branch.dereferenced_target.sha).to eq("0b4bc9a49b562e85de7cc9e834518ea6828729b9") } + end + + describe 'master branch' do + let(:branch) do + repository.branches.find { |branch| branch.name == 'master' } + end + + it { expect(branch.dereferenced_target.sha).to eq(SeedRepo::LastCommit::ID) } + end + + it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) } +end diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb new file mode 100644 index 00000000000..e1be6784c20 --- /dev/null +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -0,0 +1,408 @@ +require "spec_helper" + +describe Gitlab::Git::Commit, seed_helper: true do + let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:commit) { Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID) } + let(:rugged_commit) do + repository.rugged.lookup(SeedRepo::Commit::ID) + end + + describe "Commit info" do + before do + repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged + + @committer = { + email: 'mike@smith.com', + name: "Mike Smith", + time: Time.now + } + + @author = { + email: 'john@smith.com', + name: "John Smith", + time: Time.now + } + + @parents = [repo.head.target] + @gitlab_parents = @parents.map { |c| Gitlab::Git::Commit.decorate(c) } + @tree = @parents.first.tree + + sha = Rugged::Commit.create( + repo, + author: @author, + committer: @committer, + tree: @tree, + parents: @parents, + message: "Refactoring specs", + update_ref: "HEAD" + ) + + @raw_commit = repo.lookup(sha) + @commit = Gitlab::Git::Commit.new(@raw_commit) + end + + it { expect(@commit.short_id).to eq(@raw_commit.oid[0..10]) } + it { expect(@commit.id).to eq(@raw_commit.oid) } + it { expect(@commit.sha).to eq(@raw_commit.oid) } + it { expect(@commit.safe_message).to eq(@raw_commit.message) } + it { expect(@commit.created_at).to eq(@raw_commit.author[:time]) } + it { expect(@commit.date).to eq(@raw_commit.committer[:time]) } + it { expect(@commit.author_email).to eq(@author[:email]) } + it { expect(@commit.author_name).to eq(@author[:name]) } + it { expect(@commit.committer_name).to eq(@committer[:name]) } + it { expect(@commit.committer_email).to eq(@committer[:email]) } + it { expect(@commit.different_committer?).to be_truthy } + it { expect(@commit.parents).to eq(@gitlab_parents) } + it { expect(@commit.parent_id).to eq(@parents.first.oid) } + it { expect(@commit.no_commit_message).to eq("--no commit message") } + it { expect(@commit.tree).to eq(@tree) } + + after do + # Erase the new commit so other tests get the original repo + repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged + repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID) + end + end + + context 'Class methods' do + describe :find do + it "should return first head commit if without params" do + expect(Gitlab::Git::Commit.last(repository).id).to eq( + repository.raw.head.target.oid + ) + end + + it "should return valid commit" do + expect(Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID)).to be_valid_commit + end + + it "should return valid commit for tag" do + expect(Gitlab::Git::Commit.find(repository, 'v1.0.0').id).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') + end + + it "should return nil for non-commit ids" do + blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") + expect(Gitlab::Git::Commit.find(repository, blob.id)).to be_nil + end + + it "should return nil for parent of non-commit object" do + blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") + expect(Gitlab::Git::Commit.find(repository, "#{blob.id}^")).to be_nil + end + + it "should return nil for nonexisting ids" do + expect(Gitlab::Git::Commit.find(repository, "+123_4532530XYZ")).to be_nil + end + + context 'with broken repo' do + let(:repository) { Gitlab::Git::Repository.new(TEST_BROKEN_REPO_PATH) } + + it 'returns nil' do + expect(Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID)).to be_nil + end + end + end + + describe :last_for_path do + context 'no path' do + subject { Gitlab::Git::Commit.last_for_path(repository, 'master') } + + describe '#id' do + subject { super().id } + it { is_expected.to eq(SeedRepo::LastCommit::ID) } + end + end + + context 'path' do + subject { Gitlab::Git::Commit.last_for_path(repository, 'master', 'files/ruby') } + + describe '#id' do + subject { super().id } + it { is_expected.to eq(SeedRepo::Commit::ID) } + end + end + + context 'ref + path' do + subject { Gitlab::Git::Commit.last_for_path(repository, SeedRepo::Commit::ID, 'encoding') } + + describe '#id' do + subject { super().id } + it { is_expected.to eq(SeedRepo::BigCommit::ID) } + end + end + end + + describe "where" do + context 'path is empty string' do + subject do + commits = Gitlab::Git::Commit.where( + repo: repository, + ref: 'master', + path: '', + limit: 10 + ) + + commits.map { |c| c.id } + end + + it 'has 10 elements' do + expect(subject.size).to eq(10) + end + it { is_expected.to include(SeedRepo::EmptyCommit::ID) } + end + + context 'path is nil' do + subject do + commits = Gitlab::Git::Commit.where( + repo: repository, + ref: 'master', + path: nil, + limit: 10 + ) + + commits.map { |c| c.id } + end + + it 'has 10 elements' do + expect(subject.size).to eq(10) + end + it { is_expected.to include(SeedRepo::EmptyCommit::ID) } + end + + context 'ref is branch name' do + subject do + commits = Gitlab::Git::Commit.where( + repo: repository, + ref: 'master', + path: 'files', + limit: 3, + offset: 1 + ) + + commits.map { |c| c.id } + end + + it 'has 3 elements' do + expect(subject.size).to eq(3) + end + it { is_expected.to include("d14d6c0abdd253381df51a723d58691b2ee1ab08") } + it { is_expected.not_to include("eb49186cfa5c4338011f5f590fac11bd66c5c631") } + end + + context 'ref is commit id' do + subject do + commits = Gitlab::Git::Commit.where( + repo: repository, + ref: "874797c3a73b60d2187ed6e2fcabd289ff75171e", + path: 'files', + limit: 3, + offset: 1 + ) + + commits.map { |c| c.id } + end + + it 'has 3 elements' do + expect(subject.size).to eq(3) + end + it { is_expected.to include("2f63565e7aac07bcdadb654e253078b727143ec4") } + it { is_expected.not_to include(SeedRepo::Commit::ID) } + end + + context 'ref is tag' do + subject do + commits = Gitlab::Git::Commit.where( + repo: repository, + ref: 'v1.0.0', + path: 'files', + limit: 3, + offset: 1 + ) + + commits.map { |c| c.id } + end + + it 'has 3 elements' do + expect(subject.size).to eq(3) + end + it { is_expected.to include("874797c3a73b60d2187ed6e2fcabd289ff75171e") } + it { is_expected.not_to include(SeedRepo::Commit::ID) } + end + end + + describe :between do + subject do + commits = Gitlab::Git::Commit.between(repository, SeedRepo::Commit::PARENT_ID, SeedRepo::Commit::ID) + commits.map { |c| c.id } + end + + it 'has 1 element' do + expect(subject.size).to eq(1) + end + it { is_expected.to include(SeedRepo::Commit::ID) } + it { is_expected.not_to include(SeedRepo::FirstCommit::ID) } + end + + describe :find_all do + context 'max_count' do + subject do + commits = Gitlab::Git::Commit.find_all( + repository, + max_count: 50 + ) + + commits.map { |c| c.id } + end + + it 'has 31 elements' do + expect(subject.size).to eq(33) + end + it { is_expected.to include(SeedRepo::Commit::ID) } + it { is_expected.to include(SeedRepo::Commit::PARENT_ID) } + it { is_expected.to include(SeedRepo::FirstCommit::ID) } + end + + context 'ref + max_count + skip' do + subject do + commits = Gitlab::Git::Commit.find_all( + repository, + ref: 'master', + max_count: 50, + skip: 1 + ) + + commits.map { |c| c.id } + end + + it 'has 23 elements' do + expect(subject.size).to eq(24) + end + it { is_expected.to include(SeedRepo::Commit::ID) } + it { is_expected.to include(SeedRepo::FirstCommit::ID) } + it { is_expected.not_to include(SeedRepo::LastCommit::ID) } + end + + context 'contains feature + max_count' do + subject do + commits = Gitlab::Git::Commit.find_all( + repository, + contains: 'feature', + max_count: 7 + ) + + commits.map { |c| c.id } + end + + it 'has 7 elements' do + expect(subject.size).to eq(7) + end + + it { is_expected.not_to include(SeedRepo::Commit::PARENT_ID) } + it { is_expected.not_to include(SeedRepo::Commit::ID) } + it { is_expected.to include(SeedRepo::BigCommit::ID) } + end + end + end + + describe :init_from_rugged do + let(:gitlab_commit) { Gitlab::Git::Commit.new(rugged_commit) } + subject { gitlab_commit } + + describe '#id' do + subject { super().id } + it { is_expected.to eq(SeedRepo::Commit::ID) } + end + end + + describe :init_from_hash do + let(:commit) { Gitlab::Git::Commit.new(sample_commit_hash) } + subject { commit } + + describe '#id' do + subject { super().id } + it { is_expected.to eq(sample_commit_hash[:id])} + end + + describe '#message' do + subject { super().message } + it { is_expected.to eq(sample_commit_hash[:message])} + end + end + + describe :stats do + subject { commit.stats } + + describe '#additions' do + subject { super().additions } + it { is_expected.to eq(11) } + end + + describe '#deletions' do + subject { super().deletions } + it { is_expected.to eq(6) } + end + end + + describe :to_diff do + subject { commit.to_diff } + + it { is_expected.not_to include "From #{SeedRepo::Commit::ID}" } + it { is_expected.to include 'diff --git a/files/ruby/popen.rb b/files/ruby/popen.rb'} + end + + describe :has_zero_stats? do + it { expect(commit.has_zero_stats?).to eq(false) } + end + + describe :to_patch do + subject { commit.to_patch } + + it { is_expected.to include "From #{SeedRepo::Commit::ID}" } + it { is_expected.to include 'diff --git a/files/ruby/popen.rb b/files/ruby/popen.rb'} + end + + describe :to_hash do + let(:hash) { commit.to_hash } + subject { hash } + + it { is_expected.to be_kind_of Hash } + + describe '#keys' do + subject { super().keys.sort } + it { is_expected.to match(sample_commit_hash.keys.sort) } + end + end + + describe :diffs do + subject { commit.diffs } + + it { is_expected.to be_kind_of Gitlab::Git::DiffCollection } + it { expect(subject.count).to eq(2) } + it { expect(subject.first).to be_kind_of Gitlab::Git::Diff } + end + + describe :ref_names do + let(:commit) { Gitlab::Git::Commit.find(repository, 'master') } + subject { commit.ref_names(repository) } + + it 'has 1 element' do + expect(subject.size).to eq(1) + end + it { is_expected.to include("master") } + it { is_expected.not_to include("feature") } + end + + def sample_commit_hash + { + author_email: "dmitriy.zaporozhets@gmail.com", + author_name: "Dmitriy Zaporozhets", + authored_date: "2012-02-27 20:51:12 +0200", + committed_date: "2012-02-27 20:51:12 +0200", + committer_email: "dmitriy.zaporozhets@gmail.com", + committer_name: "Dmitriy Zaporozhets", + id: SeedRepo::Commit::ID, + message: "tree css fixes", + parent_ids: ["874797c3a73b60d2187ed6e2fcabd289ff75171e"] + } + end +end diff --git a/spec/lib/gitlab/git/compare_spec.rb b/spec/lib/gitlab/git/compare_spec.rb new file mode 100644 index 00000000000..f66b68e4218 --- /dev/null +++ b/spec/lib/gitlab/git/compare_spec.rb @@ -0,0 +1,109 @@ +require "spec_helper" + +describe Gitlab::Git::Compare, seed_helper: true do + let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:compare) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, false) } + let(:compare_straight) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, true) } + + describe :commits do + subject do + compare.commits.map(&:id) + end + + it 'has 8 elements' do + expect(subject.size).to eq(8) + end + + it { is_expected.to include(SeedRepo::Commit::PARENT_ID) } + it { is_expected.not_to include(SeedRepo::BigCommit::PARENT_ID) } + + context 'non-existing base ref' do + let(:compare) { Gitlab::Git::Compare.new(repository, 'no-such-branch', SeedRepo::Commit::ID) } + + it { is_expected.to be_empty } + end + + context 'non-existing head ref' do + let(:compare) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, '1234567890') } + + it { is_expected.to be_empty } + end + + context 'base ref is equal to head ref' do + let(:compare) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::BigCommit::ID) } + + it { is_expected.to be_empty } + end + + context 'providing nil as base ref or head ref' do + let(:compare) { Gitlab::Git::Compare.new(repository, nil, nil) } + + it { is_expected.to be_empty } + end + end + + describe :diffs do + subject do + compare.diffs.map(&:new_path) + end + + it 'has 10 elements' do + expect(subject.size).to eq(10) + end + + it { is_expected.to include('files/ruby/popen.rb') } + it { is_expected.not_to include('LICENSE') } + + context 'non-existing base ref' do + let(:compare) { Gitlab::Git::Compare.new(repository, 'no-such-branch', SeedRepo::Commit::ID) } + + it { is_expected.to be_empty } + end + + context 'non-existing head ref' do + let(:compare) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, '1234567890') } + + it { is_expected.to be_empty } + end + end + + describe :same do + subject do + compare.same + end + + it { is_expected.to eq(false) } + + context 'base ref is equal to head ref' do + let(:compare) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::BigCommit::ID) } + + it { is_expected.to eq(true) } + end + end + + describe :commits_straight do + subject do + compare_straight.commits.map(&:id) + end + + it 'has 8 elements' do + expect(subject.size).to eq(8) + end + + it { is_expected.to include(SeedRepo::Commit::PARENT_ID) } + it { is_expected.not_to include(SeedRepo::BigCommit::PARENT_ID) } + end + + describe :diffs_straight do + subject do + compare_straight.diffs.map(&:new_path) + end + + it 'has 10 elements' do + expect(subject.size).to eq(10) + end + + it { is_expected.to include('files/ruby/popen.rb') } + it { is_expected.not_to include('LICENSE') } + end +end diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb new file mode 100644 index 00000000000..4fa72c565ae --- /dev/null +++ b/spec/lib/gitlab/git/diff_collection_spec.rb @@ -0,0 +1,460 @@ +require 'spec_helper' + +describe Gitlab::Git::DiffCollection, seed_helper: true do + subject do + Gitlab::Git::DiffCollection.new( + iterator, + max_files: max_files, + max_lines: max_lines, + all_diffs: all_diffs, + no_collapse: no_collapse + ) + end + let(:iterator) { Array.new(file_count, fake_diff(line_length, line_count)) } + let(:file_count) { 0 } + let(:line_length) { 1 } + let(:line_count) { 1 } + let(:max_files) { 10 } + let(:max_lines) { 100 } + let(:all_diffs) { false } + let(:no_collapse) { true } + + describe '#to_a' do + subject { super().to_a } + it { is_expected.to be_kind_of ::Array } + end + + describe :decorate! do + let(:file_count) { 3 } + + it 'modifies the array in place' do + count = 0 + subject.decorate! { |d| !d.nil? && count += 1 } + expect(subject.to_a).to eq([1, 2, 3]) + expect(count).to eq(3) + end + + it 'avoids future iterator iterations' do + subject.decorate! { |d| d unless d.nil? } + + expect(iterator).not_to receive(:each) + + subject.overflow? + end + end + + context 'overflow handling' do + context 'adding few enough files' do + let(:file_count) { 3 } + + context 'and few enough lines' do + let(:line_count) { 10 } + + describe '#overflow?' do + subject { super().overflow? } + it { is_expected.to be_falsey } + end + + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_falsey } + end + + describe '#real_size' do + subject { super().real_size } + it { is_expected.to eq('3') } + end + it { expect(subject.size).to eq(3) } + + context 'when limiting is disabled' do + let(:all_diffs) { true } + + describe '#overflow?' do + subject { super().overflow? } + it { is_expected.to be_falsey } + end + + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_falsey } + end + + describe '#real_size' do + subject { super().real_size } + it { is_expected.to eq('3') } + end + it { expect(subject.size).to eq(3) } + end + end + + context 'and too many lines' do + let(:line_count) { 1000 } + + describe '#overflow?' do + subject { super().overflow? } + it { is_expected.to be_truthy } + end + + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_falsey } + end + + describe '#real_size' do + subject { super().real_size } + it { is_expected.to eq('0+') } + end + it { expect(subject.size).to eq(0) } + + context 'when limiting is disabled' do + let(:all_diffs) { true } + + describe '#overflow?' do + subject { super().overflow? } + it { is_expected.to be_falsey } + end + + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_falsey } + end + + describe '#real_size' do + subject { super().real_size } + it { is_expected.to eq('3') } + end + it { expect(subject.size).to eq(3) } + end + end + end + + context 'adding too many files' do + let(:file_count) { 11 } + + context 'and few enough lines' do + let(:line_count) { 1 } + + describe '#overflow?' do + subject { super().overflow? } + it { is_expected.to be_truthy } + end + + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_falsey } + end + + describe '#real_size' do + subject { super().real_size } + it { is_expected.to eq('10+') } + end + it { expect(subject.size).to eq(10) } + + context 'when limiting is disabled' do + let(:all_diffs) { true } + + describe '#overflow?' do + subject { super().overflow? } + it { is_expected.to be_falsey } + end + + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_falsey } + end + + describe '#real_size' do + subject { super().real_size } + it { is_expected.to eq('11') } + end + it { expect(subject.size).to eq(11) } + end + end + + context 'and too many lines' do + let(:line_count) { 30 } + + describe '#overflow?' do + subject { super().overflow? } + it { is_expected.to be_truthy } + end + + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_falsey } + end + + describe '#real_size' do + subject { super().real_size } + it { is_expected.to eq('3+') } + end + it { expect(subject.size).to eq(3) } + + context 'when limiting is disabled' do + let(:all_diffs) { true } + + describe '#overflow?' do + subject { super().overflow? } + it { is_expected.to be_falsey } + end + + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_falsey } + end + + describe '#real_size' do + subject { super().real_size } + it { is_expected.to eq('11') } + end + it { expect(subject.size).to eq(11) } + end + end + end + + context 'adding exactly the maximum number of files' do + let(:file_count) { 10 } + + context 'and few enough lines' do + let(:line_count) { 1 } + + describe '#overflow?' do + subject { super().overflow? } + it { is_expected.to be_falsey } + end + + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_falsey } + end + + describe '#real_size' do + subject { super().real_size } + it { is_expected.to eq('10') } + end + it { expect(subject.size).to eq(10) } + end + end + + context 'adding too many bytes' do + let(:file_count) { 10 } + let(:line_length) { 5200 } + + describe '#overflow?' do + subject { super().overflow? } + it { is_expected.to be_truthy } + end + + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_falsey } + end + + describe '#real_size' do + subject { super().real_size } + it { is_expected.to eq('9+') } + end + it { expect(subject.size).to eq(9) } + + context 'when limiting is disabled' do + let(:all_diffs) { true } + + describe '#overflow?' do + subject { super().overflow? } + it { is_expected.to be_falsey } + end + + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_falsey } + end + + describe '#real_size' do + subject { super().real_size } + it { is_expected.to eq('10') } + end + it { expect(subject.size).to eq(10) } + end + end + end + + describe 'empty collection' do + subject { Gitlab::Git::DiffCollection.new([]) } + + describe '#overflow?' do + subject { super().overflow? } + it { is_expected.to be_falsey } + end + + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_truthy } + end + + describe '#size' do + subject { super().size } + it { is_expected.to eq(0) } + end + + describe '#real_size' do + subject { super().real_size } + it { is_expected.to eq('0')} + end + end + + describe :each do + context 'when diff are too large' do + let(:collection) do + Gitlab::Git::DiffCollection.new([{ diff: 'a' * 204800 }]) + end + + it 'yields Diff instances even when they are too large' do + expect { |b| collection.each(&b) }. + to yield_with_args(an_instance_of(Gitlab::Git::Diff)) + end + + it 'prunes diffs that are too large' do + diff = nil + + collection.each do |d| + diff = d + end + + expect(diff.diff).to eq('') + end + end + + context 'when diff is quite large will collapse by default' do + let(:iterator) { [{ diff: 'a' * 20480 }] } + + context 'when no collapse is set' do + let(:no_collapse) { true } + + it 'yields Diff instances even when they are quite big' do + expect { |b| subject.each(&b) }. + to yield_with_args(an_instance_of(Gitlab::Git::Diff)) + end + + it 'does not prune diffs' do + diff = nil + + subject.each do |d| + diff = d + end + + expect(diff.diff).not_to eq('') + end + end + + context 'when no collapse is unset' do + let(:no_collapse) { false } + + it 'yields Diff instances even when they are quite big' do + expect { |b| subject.each(&b) }. + to yield_with_args(an_instance_of(Gitlab::Git::Diff)) + end + + it 'prunes diffs that are quite big' do + diff = nil + + subject.each do |d| + diff = d + end + + expect(diff.diff).to eq('') + end + + context 'when go over safe limits on files' do + let(:iterator) { [ fake_diff(1, 1) ] * 4 } + + before(:each) do + stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_files: 2, max_lines: max_lines }) + end + + it 'prunes diffs by default even little ones' do + subject.each_with_index do |d, i| + if i < 2 + expect(d.diff).not_to eq('') + else # 90 lines + expect(d.diff).to eq('') + end + end + end + end + + context 'when go over safe limits on lines' do + let(:iterator) do + [ + fake_diff(1, 45), + fake_diff(1, 45), + fake_diff(1, 20480), + fake_diff(1, 1) + ] + end + + before(:each) do + stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_files: max_files, max_lines: 80 }) + end + + it 'prunes diffs by default even little ones' do + subject.each_with_index do |d, i| + if i < 2 + expect(d.diff).not_to eq('') + else # 90 lines + expect(d.diff).to eq('') + end + end + end + end + + context 'when go over safe limits on bytes' do + let(:iterator) do + [ + fake_diff(1, 45), + fake_diff(1, 45), + fake_diff(1, 20480), + fake_diff(1, 1) + ] + end + + before(:each) do + stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_files: max_files, max_lines: 80 }) + end + + it 'prunes diffs by default even little ones' do + subject.each_with_index do |d, i| + if i < 2 + expect(d.diff).not_to eq('') + else # > 80 bytes + expect(d.diff).to eq('') + end + end + end + end + end + + context 'when limiting is disabled' do + let(:all_diffs) { true } + + it 'yields Diff instances even when they are quite big' do + expect { |b| subject.each(&b) }. + to yield_with_args(an_instance_of(Gitlab::Git::Diff)) + end + + it 'does not prune diffs' do + diff = nil + + subject.each do |d| + diff = d + end + + expect(diff.diff).not_to eq('') + end + end + end + end + + def fake_diff(line_length, line_count) + { 'diff' => "#{'a' * line_length}\n" * line_count } + end +end diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb new file mode 100644 index 00000000000..4c55532d165 --- /dev/null +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -0,0 +1,287 @@ +require "spec_helper" + +describe Gitlab::Git::Diff, seed_helper: true do + let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + + before do + @raw_diff_hash = { + diff: < "409f37c4f05865e4fb208c771485f211a22c4c2d", + "path" => "six", + "url" => "git://github.com/randx/six.git" + } + ]) + end + + it 'should handle nested submodules correctly' do + nested = submodules['nested/six'] + expect(nested['path']).to eq('nested/six') + expect(nested['url']).to eq('git://github.com/randx/six.git') + expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196') + end + + it 'should handle deeply nested submodules correctly' do + nested = submodules['deeper/nested/six'] + expect(nested['path']).to eq('deeper/nested/six') + expect(nested['url']).to eq('git://github.com/randx/six.git') + expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196') + end + + it 'should not have an entry for an invalid submodule' do + expect(submodules).not_to have_key('invalid/path') + end + + it 'should not have an entry for an uncommited submodule dir' do + submodules = repository.submodules('fix-existing-submodule-dir') + expect(submodules).not_to have_key('submodule-existing-dir') + end + + it 'should handle tags correctly' do + submodules = repository.submodules('v1.2.1') + + expect(submodules.first).to eq([ + "six", { + "id" => "409f37c4f05865e4fb208c771485f211a22c4c2d", + "path" => "six", + "url" => "git://github.com/randx/six.git" + } + ]) + end + end + + context 'where repo doesn\'t have submodules' do + let(:submodules) { repository.submodules('6d39438') } + it 'should return an empty hash' do + expect(submodules).to be_empty + end + end + end + + describe :commit_count do + it { expect(repository.commit_count("master")).to eq(25) } + it { expect(repository.commit_count("feature")).to eq(9) } + end + + describe "#reset" do + change_path = File.join(TEST_NORMAL_REPO_PATH, "CHANGELOG") + untracked_path = File.join(TEST_NORMAL_REPO_PATH, "UNTRACKED") + tracked_path = File.join(TEST_NORMAL_REPO_PATH, "files", "ruby", "popen.rb") + + change_text = "New changelog text" + untracked_text = "This file is untracked" + + reset_commit = SeedRepo::LastCommit::ID + + context "--hard" do + before(:all) do + # Modify a tracked file + File.open(change_path, "w") do |f| + f.write(change_text) + end + + # Add an untracked file to the working directory + File.open(untracked_path, "w") do |f| + f.write(untracked_text) + end + + @normal_repo = Gitlab::Git::Repository.new(TEST_NORMAL_REPO_PATH) + @normal_repo.reset("HEAD", :hard) + end + + it "should replace the working directory with the content of the index" do + File.open(change_path, "r") do |f| + expect(f.each_line.first).not_to eq(change_text) + end + + File.open(tracked_path, "r") do |f| + expect(f.each_line.to_a[8]).to include('raise RuntimeError, "System commands') + end + end + + it "should not touch untracked files" do + expect(File.exist?(untracked_path)).to be_truthy + end + + it "should move the HEAD to the correct commit" do + new_head = @normal_repo.rugged.head.target.oid + expect(new_head).to eq(reset_commit) + end + + it "should move the tip of the master branch to the correct commit" do + new_tip = @normal_repo.rugged.references["refs/heads/master"]. + target.oid + + expect(new_tip).to eq(reset_commit) + end + + after(:all) do + # Fast-forward to the original HEAD + FileUtils.rm_rf(TEST_NORMAL_REPO_PATH) + ensure_seeds + end + end + end + + describe "#checkout" do + new_branch = "foo_branch" + + context "-b" do + before(:all) do + @normal_repo = Gitlab::Git::Repository.new(TEST_NORMAL_REPO_PATH) + @normal_repo.checkout(new_branch, { b: true }, "origin/feature") + end + + it "should create a new branch" do + expect(@normal_repo.rugged.branches[new_branch]).not_to be_nil + end + + it "should move the HEAD to the correct commit" do + expect(@normal_repo.rugged.head.target.oid).to( + eq(@normal_repo.rugged.branches["origin/feature"].target.oid) + ) + end + + it "should refresh the repo's #heads collection" do + head_names = @normal_repo.heads.map { |h| h.name } + expect(head_names).to include(new_branch) + end + + after(:all) do + FileUtils.rm_rf(TEST_NORMAL_REPO_PATH) + ensure_seeds + end + end + + context "without -b" do + context "and specifying a nonexistent branch" do + it "should not do anything" do + normal_repo = Gitlab::Git::Repository.new(TEST_NORMAL_REPO_PATH) + + expect { normal_repo.checkout(new_branch) }.to raise_error(Rugged::ReferenceError) + expect(normal_repo.rugged.branches[new_branch]).to be_nil + expect(normal_repo.rugged.head.target.oid).to( + eq(normal_repo.rugged.branches["master"].target.oid) + ) + + head_names = normal_repo.heads.map { |h| h.name } + expect(head_names).not_to include(new_branch) + end + + after(:all) do + FileUtils.rm_rf(TEST_NORMAL_REPO_PATH) + ensure_seeds + end + end + + context "and with a valid branch" do + before(:all) do + @normal_repo = Gitlab::Git::Repository.new(TEST_NORMAL_REPO_PATH) + @normal_repo.rugged.branches.create("feature", "origin/feature") + @normal_repo.checkout("feature") + end + + it "should move the HEAD to the correct commit" do + expect(@normal_repo.rugged.head.target.oid).to( + eq(@normal_repo.rugged.branches["feature"].target.oid) + ) + end + + it "should update the working directory" do + File.open(File.join(TEST_NORMAL_REPO_PATH, ".gitignore"), "r") do |f| + expect(f.read.each_line.to_a).not_to include(".DS_Store\n") + end + end + + after(:all) do + FileUtils.rm_rf(TEST_NORMAL_REPO_PATH) + ensure_seeds + end + end + end + end + + describe "#delete_branch" do + before(:all) do + @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo.delete_branch("feature") + end + + it "should remove the branch from the repo" do + expect(@repo.rugged.branches["feature"]).to be_nil + end + + it "should update the repo's #heads collection" do + expect(@repo.heads).not_to include("feature") + end + + after(:all) do + FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH) + ensure_seeds + end + end + + describe "#create_branch" do + before(:all) do + @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + end + + it "should create a new branch" do + expect(@repo.create_branch('new_branch', 'master')).not_to be_nil + end + + it "should create a new branch with the right name" do + expect(@repo.create_branch('another_branch', 'master').name).to eq('another_branch') + end + + it "should fail if we create an existing branch" do + @repo.create_branch('duplicated_branch', 'master') + expect{@repo.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{@repo.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge") + end + + after(:all) do + FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH) + ensure_seeds + end + end + + describe "#remote_names" do + let(:remotes) { repository.remote_names } + + it "should have one entry: 'origin'" do + expect(remotes.size).to eq(1) + expect(remotes.first).to eq("origin") + end + end + + describe "#refs_hash" do + let(:refs) { 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(refs.values.flatten.size).to eq(expected_refs.size) + end + end + + describe "#remote_delete" do + before(:all) do + @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo.remote_delete("expendable") + end + + it "should remove the remote" do + expect(@repo.rugged.remotes).not_to include("expendable") + end + + after(:all) do + FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH) + ensure_seeds + end + end + + describe "#remote_add" do + before(:all) do + @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo.remote_add("new_remote", SeedHelper::GITLAB_URL) + end + + it "should add the remote" do + expect(@repo.rugged.remotes.each_name.to_a).to include("new_remote") + end + + after(:all) do + FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH) + ensure_seeds + end + end + + describe "#remote_update" do + before(:all) do + @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo.remote_update("expendable", url: TEST_NORMAL_REPO_PATH) + end + + it "should add the remote" do + expect(@repo.rugged.remotes["expendable"].url).to( + eq(TEST_NORMAL_REPO_PATH) + ) + end + + after(:all) do + FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH) + ensure_seeds + end + end + + describe "#log" do + commit_with_old_name = nil + commit_with_new_name = nil + rename_commit = nil + + before(:all) do + # Add new commits so that there's a renamed file in the commit history + repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged + + commit_with_old_name = new_commit_edit_old_file(repo) + rename_commit = new_commit_move_file(repo) + commit_with_new_name = new_commit_edit_new_file(repo) + end + + context "where 'follow' == true" do + options = { ref: "master", follow: true } + + context "and 'path' is a directory" do + let(:log_commits) do + repository.log(options.merge(path: "encoding")) + end + + it "should 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 "should follow renames" 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 + + context "and 'path' is a file that matches the old filename" do + let(:log_commits) do + repository.log(options.merge(path: "CHANGELOG")) + end + + it "should 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 "unknown ref" do + let(:log_commits) { repository.log(options.merge(ref: 'unknown')) } + + it "should return empty" do + 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 "should 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 "should 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 "should 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 "should return a list of commits" do + expect(log_commits.size).to eq(1) + end + end + end + + context "compare results between log_by_walk and log_by_shell" do + let(:options) { { ref: "master" } } + let(:commits_by_walk) { repository.log(options).map(&:oid) } + let(:commits_by_shell) { repository.log(options.merge({ disable_walk: true })).map(&:oid) } + + it { expect(commits_by_walk).to eq(commits_by_shell) } + + context "with limit" do + let(:options) { { ref: "master", limit: 1 } } + + it { expect(commits_by_walk).to eq(commits_by_shell) } + end + + context "with offset" do + let(:options) { { ref: "master", offset: 1 } } + + it { expect(commits_by_walk).to eq(commits_by_shell) } + end + + context "with skip_merges" do + let(:options) { { ref: "master", skip_merges: true } } + + it { expect(commits_by_walk).to eq(commits_by_shell) } + end + + context "with path" do + let(:options) { { ref: "master", path: "encoding" } } + + it { expect(commits_by_walk).to eq(commits_by_shell) } + + context "with follow" do + let(:options) { { ref: "master", path: "encoding", follow: true } } + + it { expect(commits_by_walk).to eq(commits_by_shell) } + 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 + satisfy do + commits.all? { |commit| commit.created_at >= 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 + satisfy do + commits.all? { |commit| commit.created_at <= options[:before] } + end + end + end + + after(:all) do + # Erase our commits so other tests get the original repo + repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged + repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID) + end + end + + describe "#commits_between" do + context 'two SHAs' do + let(:first_sha) { 'b0e52af38d7ea43cf41d8a6f2471351ac036d6c9' } + let(:second_sha) { '0e50ec4d3c7ce42ab74dda1d422cb2cbffe1e326' } + + it 'returns the number of commits between' do + expect(repository.commits_between(first_sha, second_sha).count).to eq(3) + end + end + + context 'SHA and master branch' do + let(:sha) { 'b0e52af38d7ea43cf41d8a6f2471351ac036d6c9' } + let(:branch) { 'master' } + + it 'returns the number of commits between a sha and a branch' do + expect(repository.commits_between(sha, branch).count).to eq(5) + end + + it 'returns the number of commits between a branch and a sha' do + expect(repository.commits_between(branch, sha).count).to eq(0) # sha is before branch + end + end + + context 'two branches' do + let(:first_branch) { 'feature' } + let(:second_branch) { 'master' } + + it 'returns the number of commits between' do + expect(repository.commits_between(first_branch, second_branch).count).to eq(17) + end + end + end + + describe '#count_commits_between' do + subject { repository.count_commits_between('feature', 'master') } + + it { is_expected.to eq(17) } + end + + describe "branch_names_contains" do + subject { repository.branch_names_contains(SeedRepo::LastCommit::ID) } + + it { is_expected.to include('master') } + it { is_expected.not_to include('feature') } + it { is_expected.not_to include('fix') } + end + + describe '#autocrlf' do + before(:all) do + @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo.rugged.config['core.autocrlf'] = true + end + + it 'return the value of the autocrlf option' do + expect(@repo.autocrlf).to be(true) + end + + after(:all) do + @repo.rugged.config.delete('core.autocrlf') + end + end + + describe '#autocrlf=' do + before(:all) do + @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo.rugged.config['core.autocrlf'] = false + end + + it 'should set the autocrlf option to the provided option' do + @repo.autocrlf = :input + + File.open(File.join(TEST_MUTABLE_REPO_PATH, '.git', 'config')) do |config_file| + expect(config_file.read).to match('autocrlf = input') + end + end + + after(:all) do + @repo.rugged.config.delete('core.autocrlf') + 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 + + it 'should reload Rugged::Repository and return master' do + expect(Rugged::Repository).to receive(:new).twice.and_call_original + + repository.find_branch('master') + branch = repository.find_branch('master', force_reload: true) + + expect(branch).to be_a_kind_of(Gitlab::Git::Branch) + expect(branch.name).to eq('master') + end + end + + describe '#branches with deleted branch' do + before(:each) do + ref = double() + allow(ref).to receive(:name) { 'bad-branch' } + allow(ref).to receive(:target) { raise Rugged::ReferenceError } + allow(repository.rugged).to receive(:branches) { [ref] } + end + + it 'should return empty branches' do + expect(repository.branches).to eq([]) + end + end + + describe '#branch_count' do + before(:each) do + valid_ref = double(:ref) + invalid_ref = double(:ref) + + allow(valid_ref).to receive_messages(name: 'master', target: double(:target)) + + allow(invalid_ref).to receive_messages(name: 'bad-branch') + allow(invalid_ref).to receive(:target) { raise Rugged::ReferenceError } + + allow(repository.rugged).to receive_messages(branches: [valid_ref, invalid_ref]) + end + + it 'returns the number of branches' do + expect(repository.branch_count).to eq(1) + end + end + + describe '#mkdir' do + let(:commit_options) do + { + author: { + email: 'user@example.com', + name: 'Test User', + time: Time.now + }, + committer: { + email: 'user@example.com', + name: 'Test User', + time: Time.now + }, + commit: { + message: 'Test message', + branch: 'refs/heads/fix', + } + } + end + + def generate_diff_for_path(path) + "diff --git a/#{path}/.gitkeep b/#{path}/.gitkeep +new file mode 100644 +index 0000000..e69de29 +--- /dev/null ++++ b/#{path}/.gitkeep\n" + end + + shared_examples 'mkdir diff check' do |path, expected_path| + it 'creates a directory' do + result = repository.mkdir(path, commit_options) + expect(result).not_to eq(nil) + + # Verify another mkdir doesn't create a directory that already exists + expect{ repository.mkdir(path, commit_options) }.to raise_error('Directory already exists') + end + end + + describe 'creates a directory in root directory' do + it_should_behave_like 'mkdir diff check', 'new_dir', 'new_dir' + end + + describe 'creates a directory in subdirectory' do + it_should_behave_like 'mkdir diff check', 'files/ruby/test', 'files/ruby/test' + end + + describe 'creates a directory in subdirectory with a slash' do + it_should_behave_like 'mkdir diff check', '/files/ruby/test2', 'files/ruby/test2' + end + + describe 'creates a directory in subdirectory with multiple slashes' do + it_should_behave_like 'mkdir diff check', '//files/ruby/test3', 'files/ruby/test3' + end + + describe 'handles relative paths' do + it_should_behave_like 'mkdir diff check', 'files/ruby/../test_relative', 'files/test_relative' + end + + describe 'creates nested directories' do + it_should_behave_like 'mkdir diff check', 'files/missing/test', 'files/missing/test' + end + + it 'does not attempt to create a directory with invalid relative path' do + expect{ repository.mkdir('../files/missing/test', commit_options) }.to raise_error('Invalid path') + end + + it 'does not attempt to overwrite a file' do + expect{ repository.mkdir('README.md', commit_options) }.to raise_error('Directory already exists as a file') + end + + it 'does not attempt to overwrite a directory' do + expect{ repository.mkdir('files', commit_options) }.to raise_error('Directory already exists') + end + end + + describe "#ls_files" do + let(:master_file_paths) { repository.ls_files("master") } + 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 "dose 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 + end + + describe "#copy_gitattributes" do + let(:attributes_path) { File.join(TEST_REPO_PATH, 'info/attributes') } + + it "raises an error with invalid ref" do + expect { repository.copy_gitattributes("invalid") }.to raise_error(Gitlab::Git::Repository::InvalidRef) + end + + context "with no .gitattrbutes" do + before(:each) do + repository.copy_gitattributes("master") + end + + it "does not have an info/attributes" do + expect(File.exist?(attributes_path)).to be_falsey + end + + after(:each) do + FileUtils.rm_rf(attributes_path) + end + end + + context "with .gitattrbutes" do + before(:each) 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 + + after(:each) do + FileUtils.rm_rf(attributes_path) + end + end + + context "with updated .gitattrbutes" do + before(:each) 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 + + after(:each) do + FileUtils.rm_rf(attributes_path) + end + end + + context "with no .gitattrbutes in HEAD but with previous info/attributes" do + before(:each) 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 + + after(:each) do + FileUtils.rm_rf(attributes_path) + end + end + end + + describe '#diffable' do + info_dir_path = attributes_path = File.join(TEST_REPO_PATH, 'info') + attributes_path = File.join(info_dir_path, 'attributes') + + before(:all) do + FileUtils.mkdir(info_dir_path) unless File.exist?(info_dir_path) + File.write(attributes_path, "*.md -diff\n") + end + + it "should return true for files which are text and do not have attributes" do + blob = Gitlab::Git::Blob.find( + repository, + '33bcff41c232a11727ac6d660bd4b0c2ba86d63d', + 'LICENSE' + ) + expect(repository.diffable?(blob)).to be_truthy + end + + it "should return false for binary files which do not have attributes" do + blob = Gitlab::Git::Blob.find( + repository, + '33bcff41c232a11727ac6d660bd4b0c2ba86d63d', + 'files/images/logo-white.png' + ) + expect(repository.diffable?(blob)).to be_falsey + end + + it "should return false for text files which have been marked as not being diffable in attributes" do + blob = Gitlab::Git::Blob.find( + repository, + '33bcff41c232a11727ac6d660bd4b0c2ba86d63d', + 'README.md' + ) + expect(repository.diffable?(blob)).to be_falsey + end + + after(:all) do + FileUtils.rm_rf(info_dir_path) + 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 + before(:all) do + @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + end + + after(:all) do + FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH) + ensure_seeds + end + + it 'returns the local branches' do + create_remote_branch('joe', 'remote_branch', 'master') + @repo.create_branch('local_branch', 'master') + + expect(@repo.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false) + expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true) + end + end + + def create_remote_branch(remote_name, branch_name, source_branch_name) + source_branch = @repo.branches.find { |branch| branch.name == source_branch_name } + rugged = @repo.rugged + 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 +end diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb new file mode 100644 index 00000000000..ad469e94735 --- /dev/null +++ b/spec/lib/gitlab/git/tag_spec.rb @@ -0,0 +1,25 @@ +require "spec_helper" + +describe Gitlab::Git::Tag, seed_helper: true do + let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + + describe 'first tag' do + let(:tag) { repository.tags.first } + + it { expect(tag.name).to eq("v1.0.0") } + it { expect(tag.target).to eq("f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8") } + it { expect(tag.dereferenced_target.sha).to eq("6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9") } + it { expect(tag.message).to eq("Release") } + end + + describe 'last tag' do + let(:tag) { repository.tags.last } + + it { expect(tag.name).to eq("v1.2.1") } + it { expect(tag.target).to eq("2ac1f24e253e08135507d0830508febaaccf02ee") } + it { expect(tag.dereferenced_target.sha).to eq("fa1b1e6c004a68b7d8763b86455da9e6b23e36d6") } + it { expect(tag.message).to eq("Version 1.2.1") } + end + + it { expect(repository.tags.size).to eq(SeedRepo::Repo::TAGS.size) } +end diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb new file mode 100644 index 00000000000..688e2a75373 --- /dev/null +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -0,0 +1,76 @@ +require "spec_helper" + +describe Gitlab::Git::Tree, seed_helper: true do + context :repo do + let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:tree) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID) } + + it { expect(tree).to be_kind_of Array } + it { expect(tree.empty?).to be_falsey } + it { expect(tree.select(&:dir?).size).to eq(2) } + it { expect(tree.select(&:file?).size).to eq(10) } + it { expect(tree.select(&:submodule?).size).to eq(2) } + + describe :dir do + let(:dir) { tree.select(&:dir?).first } + + it { expect(dir).to be_kind_of Gitlab::Git::Tree } + it { expect(dir.id).to eq('3c122d2b7830eca25235131070602575cf8b41a1') } + it { expect(dir.commit_id).to eq(SeedRepo::Commit::ID) } + it { expect(dir.name).to eq('encoding') } + it { expect(dir.path).to eq('encoding') } + + context :subdir do + let(:subdir) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID, 'files').first } + + it { expect(subdir).to be_kind_of Gitlab::Git::Tree } + it { expect(subdir.id).to eq('a1e8f8d745cc87e3a9248358d9352bb7f9a0aeba') } + it { expect(subdir.commit_id).to eq(SeedRepo::Commit::ID) } + it { expect(subdir.name).to eq('html') } + it { expect(subdir.path).to eq('files/html') } + end + + context :subdir_file do + let(:subdir_file) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID, 'files/ruby').first } + + it { expect(subdir_file).to be_kind_of Gitlab::Git::Tree } + it { expect(subdir_file.id).to eq('7e3e39ebb9b2bf433b4ad17313770fbe4051649c') } + it { expect(subdir_file.commit_id).to eq(SeedRepo::Commit::ID) } + it { expect(subdir_file.name).to eq('popen.rb') } + it { expect(subdir_file.path).to eq('files/ruby/popen.rb') } + end + end + + describe :file do + let(:file) { tree.select(&:file?).first } + + it { expect(file).to be_kind_of Gitlab::Git::Tree } + it { expect(file.id).to eq('dfaa3f97ca337e20154a98ac9d0be76ddd1fcc82') } + it { expect(file.commit_id).to eq(SeedRepo::Commit::ID) } + it { expect(file.name).to eq('.gitignore') } + end + + describe :readme do + let(:file) { tree.select(&:readme?).first } + + it { expect(file).to be_kind_of Gitlab::Git::Tree } + it { expect(file.name).to eq('README.md') } + end + + describe :contributing do + let(:file) { tree.select(&:contributing?).first } + + it { expect(file).to be_kind_of Gitlab::Git::Tree } + it { expect(file.name).to eq('CONTRIBUTING.md') } + end + + describe :submodule do + let(:submodule) { tree.select(&:submodule?).first } + + it { expect(submodule).to be_kind_of Gitlab::Git::Tree } + it { expect(submodule.id).to eq('79bceae69cb5750d6567b223597999bfa91cb3b9') } + it { expect(submodule.commit_id).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') } + it { expect(submodule.name).to eq('gitlab-shell') } + end + end +end diff --git a/spec/lib/gitlab/git/util_spec.rb b/spec/lib/gitlab/git/util_spec.rb new file mode 100644 index 00000000000..8d43b570e98 --- /dev/null +++ b/spec/lib/gitlab/git/util_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe Gitlab::Git::Util do + describe :count_lines do + [ + ["", 0], + ["foo", 1], + ["foo\n", 1], + ["foo\n\n", 2], + ].each do |string, line_count| + it "counts #{line_count} lines in #{string.inspect}" do + expect(Gitlab::Git::Util.count_lines(string)).to eq(line_count) + end + end + end +end -- cgit v1.2.1