summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-01-05 18:40:35 +0000
committerFilipa Lacerda <filipa@gitlab.com>2017-01-05 18:40:35 +0000
commit8181f58d4407ee273cdf5792aa810d2b008f63d6 (patch)
treefeb96cdecf106abc4d0272641577191393360cf1 /spec
parente4da8b11a2222a4ef12025e08b250a5c78dfa2c9 (diff)
parentcd85baf5e363847ee1cca2e154e5fb47484e0fdf (diff)
downloadgitlab-ce-8181f58d4407ee273cdf5792aa810d2b008f63d6.tar.gz
Merge branch 'master' into auto-pipelines-vue
* master: (35 commits) Adds back removed class in merge request pipelines table Fix dropdown icon alignment Simplify HTML of mini pipeline graph and dropdown Creates individual html for dropdown Adds simplified CSS for the new dropdown Removes old CSS Improves dropdown item in Chrome, Firefox and Safari Use SCSS variables for colors. Fix scss linter errors Adds animation when the stage is hovered. Adds back tooltip on dropdown toggle Fixes broken tests additional css changes to get more into direction of mockups Adds CHANGELOG entry Removes unneeded `window` declaration Decreases font-size on login page Ensure internal Gitlab::Git references use the namespace Absorb gitlab_git Fix review comments. Add spec for note edit and fix one commented spec. Review fixes. Use gl.utils.isInViewport and improve gl.utils.animateToElement. Make sure elements share the same scope Hide edit warning element when form reverted. Remove unnecessary styling came from merge conflicts. Fix single note edit form specs. Separate edit form in Changes and Discussions tab. Fix warning styling for responsive design. Fix task list for single edit note widget changes. Fix notes spec. ...
Diffstat (limited to 'spec')
-rw-r--r--spec/features/admin/admin_deploy_keys_spec.rb37
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb40
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb16
-rw-r--r--spec/javascripts/lib/utils/custom_event_polyfill_spec.js.es643
-rw-r--r--spec/javascripts/notes_spec.js1
-rw-r--r--spec/javascripts/zen_mode_spec.js14
-rw-r--r--spec/lib/gitlab/git/attributes_spec.rb150
-rw-r--r--spec/lib/gitlab/git/blame_spec.rb66
-rw-r--r--spec/lib/gitlab/git/blob_snippet_spec.rb19
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb489
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb31
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb408
-rw-r--r--spec/lib/gitlab/git/compare_spec.rb109
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb460
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb287
-rw-r--r--spec/lib/gitlab/git/encoding_helper_spec.rb84
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb1184
-rw-r--r--spec/lib/gitlab/git/tag_spec.rb25
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb76
-rw-r--r--spec/lib/gitlab/git/util_spec.rb16
-rw-r--r--spec/requests/api/commits_spec.rb10
-rw-r--r--spec/requests/api/groups_spec.rb67
-rw-r--r--spec/requests/api/services_spec.rb93
-rw-r--r--spec/support/matchers/be_valid_commit.rb8
-rw-r--r--spec/support/seed_helper.rb112
-rw-r--r--spec/support/seed_repo.rb143
-rw-r--r--spec/views/projects/pipelines/show.html.haml_spec.rb2
27 files changed, 3878 insertions, 112 deletions
diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb
index 8bf68480785..7ce6cce0a5c 100644
--- a/spec/features/admin/admin_deploy_keys_spec.rb
+++ b/spec/features/admin/admin_deploy_keys_spec.rb
@@ -15,15 +15,36 @@ RSpec.describe 'admin deploy keys', type: :feature do
expect(page).to have_content(another_deploy_key.title)
end
- it 'creates new deploy key' do
- visit admin_deploy_keys_path
+ describe 'create new deploy key' do
+ before do
+ visit admin_deploy_keys_path
+ click_link 'New Deploy Key'
+ end
+
+ it 'creates new deploy key' do
+ fill_deploy_key
+ click_button 'Create'
+
+ expect_renders_new_key
+ end
+
+ it 'creates new deploy key with write access' do
+ fill_deploy_key
+ check "deploy_key_can_push"
+ click_button "Create"
+
+ expect_renders_new_key
+ expect(page).to have_content('Yes')
+ end
- click_link 'New Deploy Key'
- fill_in 'deploy_key_title', with: 'laptop'
- fill_in 'deploy_key_key', with: 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop'
- click_button 'Create'
+ def expect_renders_new_key
+ expect(current_path).to eq admin_deploy_keys_path
+ expect(page).to have_content('laptop')
+ end
- expect(current_path).to eq admin_deploy_keys_path
- expect(page).to have_content('laptop')
+ def fill_deploy_key
+ fill_in 'deploy_key_title', with: 'laptop'
+ fill_in 'deploy_key_key', with: 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop'
+ end
end
end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 9fffbb43e87..b785b2f7704 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -70,16 +70,15 @@ describe 'Comments', feature: true do
end
describe 'when editing a note', js: true do
- it 'contains the hidden edit form' do
- page.within("#note_#{note.id}") do
- is_expected.to have_css('.note-edit-form', visible: false)
- end
+ it 'there should be a hidden edit form' do
+ is_expected.to have_css('.note-edit-form:not(.mr-note-edit-form)', visible: false, count: 1)
+ is_expected.to have_css('.note-edit-form.mr-note-edit-form', visible: false, count: 1)
end
describe 'editing the note' do
before do
find('.note').hover
- find(".js-note-edit").click
+ find('.js-note-edit').click
end
it 'shows the note edit form and hide the note body' do
@@ -90,14 +89,29 @@ describe 'Comments', feature: true do
end
end
- # TODO: fix after 7.7 release
- # it "should reset the edit note form textarea with the original content of the note if cancelled" do
- # within(".current-note-edit-form") do
- # fill_in "note[note]", with: "Some new content"
- # find(".btn-cancel").click
- # expect(find(".js-note-text", visible: false).text).to eq note.note
- # end
- # end
+ it 'should reset the edit note form textarea with the original content of the note if cancelled' do
+ within('.current-note-edit-form') do
+ fill_in 'note[note]', with: 'Some new content'
+ find('.btn-cancel').click
+ expect(find('.js-note-text', visible: false).text).to eq ''
+ end
+ end
+
+ it 'allows using markdown buttons after saving a note and then trying to edit it again' do
+ page.within('.current-note-edit-form') do
+ fill_in 'note[note]', with: 'This is the new content'
+ find('.btn-save').click
+ end
+
+ find('.note').hover
+ find('.js-note-edit').click
+
+ page.within('.current-note-edit-form') do
+ expect(find('#note_note').value).to eq('This is the new content')
+ find('.js-md:first-child').click
+ expect(find('#note_note').value).to eq('This is the new content****')
+ end
+ end
it 'appends the edited at time to the note' do
page.within('.current-note-edit-form') do
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 97b91e74c85..a4d776c98ef 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -279,8 +279,15 @@ describe 'Pipelines', :feature, :js do
context 'for valid commit' do
before { fill_in('pipeline[ref]', with: 'master') }
- context 'with gitlab-ci.yml' do
- before { stub_ci_pipeline_to_return_yaml_file }
+ expect(page).to have_selector('.js-mini-pipeline-graph')
+ expect(page).to have_selector(".js-builds-dropdown-button[data-stage-endpoint='#{endpoint}']")
+ end
+
+ context 'when clicking a graph stage' do
+ it 'should open a dropdown' do
+ find('.js-builds-dropdown-button').trigger('click')
+
+ wait_for_ajax
it 'creates a new pipeline' do
expect { click_on 'Create pipeline' }
@@ -291,7 +298,10 @@ describe 'Pipelines', :feature, :js do
context 'without gitlab-ci.yml' do
before { click_on 'Create pipeline' }
- it { expect(page).to have_content('Missing .gitlab-ci.yml file') }
+ wait_for_ajax
+
+ find('a.js-ci-action-icon').trigger('click')
+ expect(page).not_to have_content('Cancel running')
end
end
diff --git a/spec/javascripts/lib/utils/custom_event_polyfill_spec.js.es6 b/spec/javascripts/lib/utils/custom_event_polyfill_spec.js.es6
deleted file mode 100644
index 3645dd70c55..00000000000
--- a/spec/javascripts/lib/utils/custom_event_polyfill_spec.js.es6
+++ /dev/null
@@ -1,43 +0,0 @@
-//= require lib/utils/custom_event_polyfill
-
-describe('Custom Event Polyfill', () => {
- it('should be defined', () => {
- expect(window.CustomEvent).toBeDefined();
- });
-
- it('should create a `CustomEvent` instance', () => {
- const e = new window.CustomEvent('foo');
-
- expect(e.type).toEqual('foo');
- expect(e.bubbles).toBe(false);
- expect(e.cancelable).toBe(false);
- expect(e.detail).toBeFalsy();
- });
-
- it('should create a `CustomEvent` instance with a `details` object', () => {
- const e = new window.CustomEvent('bar', { detail: { foo: 'bar' } });
-
- expect(e.type).toEqual('bar');
- expect(e.bubbles).toBe(false);
- expect(e.cancelable).toBe(false);
- expect(e.detail.foo).toEqual('bar');
- });
-
- it('should create a `CustomEvent` instance with a `bubbles` boolean', () => {
- const e = new window.CustomEvent('bar', { bubbles: true });
-
- expect(e.type).toEqual('bar');
- expect(e.bubbles).toBe(true);
- expect(e.cancelable).toBe(false);
- expect(e.detail).toBeFalsy();
- });
-
- it('should create a `CustomEvent` instance with a `cancelable` boolean', () => {
- const e = new window.CustomEvent('bar', { cancelable: true });
-
- expect(e.type).toEqual('bar');
- expect(e.bubbles).toBe(false);
- expect(e.cancelable).toBe(true);
- expect(e.detail).toBeFalsy();
- });
-});
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index d3bfa7730fa..bb13af7ac0c 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -19,6 +19,7 @@
fixture.load(commentsTemplate);
gl.utils.disableButtonIfEmptyField = _.noop;
window.project_uploads_path = 'http://test.host/uploads';
+ $('body').data('page', 'projects:issues:show');
});
describe('task lists', function() {
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index d80ce5a7f7e..5b4d007c8f7 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -32,9 +32,9 @@
return expect(Mousetrap.pause).toHaveBeenCalled();
});
return it('removes textarea styling', function() {
- $('textarea').attr('style', 'height: 400px');
+ $('.notes-form textarea').attr('style', 'height: 400px');
enterZen();
- return expect('textarea').not.toHaveAttr('style');
+ return expect($('.notes-form textarea')).not.toHaveAttr('style');
});
});
describe('in use', function() {
@@ -43,7 +43,7 @@
});
return it('exits on Escape', function() {
escapeKeydown();
- return expect($('.zen-backdrop')).not.toHaveClass('fullscreen');
+ return expect($('.notes-form .zen-backdrop')).not.toHaveClass('fullscreen');
});
});
return describe('on exit', function() {
@@ -64,15 +64,15 @@
});
enterZen = function() {
- return $('.js-zen-enter').click();
+ return $('.notes-form .js-zen-enter').click();
};
- exitZen = function() { // Ohmmmmmmm
- return $('.js-zen-leave').click();
+ exitZen = function() {
+ return $('.notes-form .js-zen-leave').click();
};
escapeKeydown = function() {
- return $('textarea').trigger($.Event('keydown', {
+ return $('.notes-form textarea').trigger($.Event('keydown', {
keyCode: 27
}));
};
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: <<EOT.gsub(/^ {8}/, "").sub(/\n$/, ""),
+ --- a/.gitmodules
+ +++ b/.gitmodules
+ @@ -4,3 +4,6 @@
+ [submodule "gitlab-shell"]
+ \tpath = gitlab-shell
+ \turl = https://github.com/gitlabhq/gitlab-shell.git
+ +[submodule "gitlab-grack"]
+ + path = gitlab-grack
+ + url = https://gitlab.com/gitlab-org/gitlab-grack.git
+
+EOT
+ new_path: ".gitmodules",
+ old_path: ".gitmodules",
+ a_mode: '100644',
+ b_mode: '100644',
+ new_file: false,
+ renamed_file: false,
+ deleted_file: false,
+ too_large: false
+ }
+
+ @rugged_diff = repository.rugged.diff("5937ac0a7beb003549fc5fd26fc247adbce4a52e^", "5937ac0a7beb003549fc5fd26fc247adbce4a52e", paths:
+ [".gitmodules"]).patches.first
+ end
+
+ describe '.new' do
+ context 'using a Hash' do
+ context 'with a small diff' do
+ let(:diff) { described_class.new(@raw_diff_hash) }
+
+ it 'initializes the diff' do
+ expect(diff.to_hash).to eq(@raw_diff_hash)
+ end
+
+ it 'does not prune the diff' do
+ expect(diff).not_to be_too_large
+ end
+ end
+
+ context 'using a diff that is too large' do
+ it 'prunes the diff' do
+ diff = described_class.new(diff: 'a' * 204800)
+
+ expect(diff.diff).to be_empty
+ expect(diff).to be_too_large
+ end
+ end
+ end
+
+ context 'using a Rugged::Patch' do
+ context 'with a small diff' do
+ let(:diff) { described_class.new(@rugged_diff) }
+
+ it 'initializes the diff' do
+ expect(diff.to_hash).to eq(@raw_diff_hash.merge(too_large: nil))
+ end
+
+ it 'does not prune the diff' do
+ expect(diff).not_to be_too_large
+ end
+ end
+
+ context 'using a diff that is too large' do
+ it 'prunes the diff' do
+ expect_any_instance_of(String).to receive(:bytesize).
+ and_return(1024 * 1024 * 1024)
+
+ diff = described_class.new(@rugged_diff)
+
+ expect(diff.diff).to be_empty
+ expect(diff).to be_too_large
+ end
+ end
+
+ context 'using a collapsable diff that is too large' do
+ before do
+ # The patch total size is 200, with lines between 21 and 54.
+ # This is a quick-and-dirty way to test this. Ideally, a new patch is
+ # added to the test repo with a size that falls between the real limits.
+ stub_const("#{described_class}::DIFF_SIZE_LIMIT", 150)
+ stub_const("#{described_class}::DIFF_COLLAPSE_LIMIT", 100)
+ end
+
+ it 'prunes the diff as a large diff instead of as a collapsed diff' do
+ diff = described_class.new(@rugged_diff, collapse: true)
+
+ expect(diff.diff).to be_empty
+ expect(diff).to be_too_large
+ expect(diff).not_to be_collapsed
+ end
+ end
+
+ context 'using a large binary diff' do
+ it 'does not prune the diff' do
+ expect_any_instance_of(Rugged::Diff::Delta).to receive(:binary?).
+ and_return(true)
+
+ diff = described_class.new(@rugged_diff)
+
+ expect(diff.diff).not_to be_empty
+ end
+ end
+ end
+ end
+
+ describe 'straight diffs' do
+ let(:options) { { straight: true } }
+ let(:diffs) { described_class.between(repository, 'feature', 'master', options) }
+
+ it 'has the correct size' do
+ expect(diffs.size).to eq(24)
+ end
+
+ context 'diff' do
+ it 'is an instance of Diff' do
+ expect(diffs.first).to be_kind_of(described_class)
+ end
+
+ it 'has the correct new_path' do
+ expect(diffs.first.new_path).to eq('.DS_Store')
+ end
+
+ it 'has the correct diff' do
+ expect(diffs.first.diff).to include('Binary files /dev/null and b/.DS_Store differ')
+ end
+ end
+ end
+
+ describe '.between' do
+ let(:diffs) { described_class.between(repository, 'feature', 'master') }
+ subject { diffs }
+
+ it { is_expected.to be_kind_of Gitlab::Git::DiffCollection }
+
+ describe '#size' do
+ subject { super().size }
+
+ it { is_expected.to eq(1) }
+ end
+
+ context 'diff' do
+ subject { diffs.first }
+
+ it { is_expected.to be_kind_of described_class }
+
+ describe '#new_path' do
+ subject { super().new_path }
+
+ it { is_expected.to eq('files/ruby/feature.rb') }
+ end
+
+ describe '#diff' do
+ subject { super().diff }
+
+ it { is_expected.to include '+class Feature' }
+ end
+ end
+ end
+
+ describe '.filter_diff_options' do
+ let(:options) { { max_size: 100, invalid_opt: true } }
+
+ context "without default options" do
+ let(:filtered_options) { described_class.filter_diff_options(options) }
+
+ it "should filter invalid options" do
+ expect(filtered_options).not_to have_key(:invalid_opt)
+ end
+ end
+
+ context "with default options" do
+ let(:filtered_options) do
+ default_options = { max_size: 5, bad_opt: 1, ignore_whitespace: true }
+ described_class.filter_diff_options(options, default_options)
+ end
+
+ it "should filter invalid options" do
+ expect(filtered_options).not_to have_key(:invalid_opt)
+ expect(filtered_options).not_to have_key(:bad_opt)
+ end
+
+ it "should merge with default options" do
+ expect(filtered_options).to have_key(:ignore_whitespace)
+ end
+
+ it "should override default options" do
+ expect(filtered_options).to have_key(:max_size)
+ expect(filtered_options[:max_size]).to eq(100)
+ end
+ end
+ end
+
+ describe '#submodule?' do
+ before do
+ commit = repository.lookup('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
+ @diffs = commit.parents[0].diff(commit).patches
+ end
+
+ it { expect(described_class.new(@diffs[0]).submodule?).to eq(false) }
+ it { expect(described_class.new(@diffs[1]).submodule?).to eq(true) }
+ end
+
+ describe '#line_count' do
+ it 'returns the correct number of lines' do
+ diff = described_class.new(@rugged_diff)
+
+ expect(diff.line_count).to eq(9)
+ end
+ end
+
+ describe '#too_large?' do
+ it 'returns true for a diff that is too large' do
+ diff = described_class.new(diff: 'a' * 204800)
+
+ expect(diff.too_large?).to eq(true)
+ end
+
+ it 'returns false for a diff that is small enough' do
+ diff = described_class.new(diff: 'a')
+
+ expect(diff.too_large?).to eq(false)
+ end
+
+ it 'returns true for a diff that was explicitly marked as being too large' do
+ diff = described_class.new(diff: 'a')
+
+ diff.prune_large_diff!
+
+ expect(diff.too_large?).to eq(true)
+ end
+ end
+
+ describe '#collapsed?' do
+ it 'returns false by default even on quite big diff' do
+ diff = described_class.new(diff: 'a' * 20480)
+
+ expect(diff).not_to be_collapsed
+ end
+
+ it 'returns false by default for a diff that is small enough' do
+ diff = described_class.new(diff: 'a')
+
+ expect(diff).not_to be_collapsed
+ end
+
+ it 'returns true for a diff that was explicitly marked as being collapsed' do
+ diff = described_class.new(diff: 'a')
+
+ diff.prune_collapsed_diff!
+
+ expect(diff).to be_collapsed
+ end
+ end
+
+ describe '#collapsible?' do
+ it 'returns true for a diff that is quite large' do
+ diff = described_class.new(diff: 'a' * 20480)
+
+ expect(diff).to be_collapsible
+ end
+
+ it 'returns false for a diff that is small enough' do
+ diff = described_class.new(diff: 'a')
+
+ expect(diff).not_to be_collapsible
+ end
+ end
+
+ describe '#prune_collapsed_diff!' do
+ it 'prunes the diff' do
+ diff = described_class.new(diff: "foo\nbar")
+
+ diff.prune_collapsed_diff!
+
+ expect(diff.diff).to eq('')
+ expect(diff.line_count).to eq(0)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/encoding_helper_spec.rb b/spec/lib/gitlab/git/encoding_helper_spec.rb
new file mode 100644
index 00000000000..83311536893
--- /dev/null
+++ b/spec/lib/gitlab/git/encoding_helper_spec.rb
@@ -0,0 +1,84 @@
+require "spec_helper"
+
+describe Gitlab::Git::EncodingHelper do
+ let(:ext_class) { Class.new { extend Gitlab::Git::EncodingHelper } }
+ let(:binary_string) { File.join(SEED_REPOSITORY_PATH, 'gitlab_logo.png') }
+
+ describe '#encode!' do
+ [
+ [
+ 'leaves ascii only string as is',
+ 'ascii only string',
+ 'ascii only string'
+ ],
+ [
+ 'leaves valid utf8 string as is',
+ 'multibyte string №∑∉',
+ 'multibyte string №∑∉'
+ ],
+ [
+ 'removes invalid bytes from ASCII-8bit encoded multibyte string. This can occur when a git diff match line truncates in the middle of a multibyte character. This occurs after the second word in this example. The test string is as short as we can get while still triggering the error condition when not looking at `detect[:confidence]`.',
+ "mu ns\xC3\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ".force_encoding('ASCII-8BIT'),
+ "mu ns\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ",
+ ],
+ ].each do |description, test_string, xpect|
+ it description do
+ expect(ext_class.encode!(test_string)).to eq(xpect)
+ end
+ end
+
+ it 'leaves binary string as is' do
+ expect(ext_class.encode!(binary_string)).to eq(binary_string)
+ end
+ end
+
+ describe '#encode_utf8' do
+ [
+ [
+ "encodes valid utf8 encoded string to utf8",
+ "λ, λ, λ".encode("UTF-8"),
+ "λ, λ, λ".encode("UTF-8"),
+ ],
+ [
+ "encodes valid ASCII-8BIT encoded string to utf8",
+ "ascii only".encode("ASCII-8BIT"),
+ "ascii only".encode("UTF-8"),
+ ],
+ [
+ "encodes valid ISO-8859-1 encoded string to utf8",
+ "Rüby ist eine Programmiersprache. Wir verlängern den text damit ICU die Sprache erkennen kann.".encode("ISO-8859-1", "UTF-8"),
+ "Rüby ist eine Programmiersprache. Wir verlängern den text damit ICU die Sprache erkennen kann.".encode("UTF-8"),
+ ],
+ ].each do |description, test_string, xpect|
+ it description do
+ r = ext_class.encode_utf8(test_string.force_encoding('UTF-8'))
+ expect(r).to eq(xpect)
+ expect(r.encoding.name).to eq('UTF-8')
+ end
+ end
+ end
+
+ describe '#clean' do
+ [
+ [
+ 'leaves ascii only string as is',
+ 'ascii only string',
+ 'ascii only string'
+ ],
+ [
+ 'leaves valid utf8 string as is',
+ 'multibyte string №∑∉',
+ 'multibyte string №∑∉'
+ ],
+ [
+ 'removes invalid bytes from ASCII-8bit encoded multibyte string.',
+ "Lorem ipsum\xC3\n dolor sit amet, xy\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg".force_encoding('ASCII-8BIT'),
+ "Lorem ipsum\n dolor sit amet, xyàyùabcdùefg",
+ ],
+ ].each do |description, test_string, xpect|
+ it description do
+ expect(ext_class.encode!(test_string)).to eq(xpect)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
new file mode 100644
index 00000000000..2a915bf426f
--- /dev/null
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -0,0 +1,1184 @@
+require "spec_helper"
+
+describe Gitlab::Git::Repository, seed_helper: true do
+ include Gitlab::Git::EncodingHelper
+
+ let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) }
+
+ describe "Respond to" do
+ subject { repository }
+
+ it { is_expected.to respond_to(:raw) }
+ it { is_expected.to respond_to(:rugged) }
+ it { is_expected.to respond_to(:root_ref) }
+ it { is_expected.to respond_to(:tags) }
+ end
+
+ describe "#discover_default_branch" do
+ let(:master) { 'master' }
+ let(:feature) { 'feature' }
+ let(:feature2) { 'feature2' }
+
+ it "returns 'master' when master exists" do
+ expect(repository).to receive(:branch_names).at_least(:once).and_return([feature, master])
+ expect(repository.discover_default_branch).to eq('master')
+ end
+
+ it "returns non-master when master exists but default branch is set to something else" do
+ File.write(File.join(repository.path, 'HEAD'), 'ref: refs/heads/feature')
+ expect(repository).to receive(:branch_names).at_least(:once).and_return([feature, master])
+ expect(repository.discover_default_branch).to eq('feature')
+ File.write(File.join(repository.path, 'HEAD'), 'ref: refs/heads/master')
+ end
+
+ it "returns a non-master branch when only one exists" do
+ expect(repository).to receive(:branch_names).at_least(:once).and_return([feature])
+ expect(repository.discover_default_branch).to eq('feature')
+ end
+
+ it "returns a non-master branch when more than one exists and master does not" do
+ expect(repository).to receive(:branch_names).at_least(:once).and_return([feature, feature2])
+ expect(repository.discover_default_branch).to eq('feature')
+ end
+
+ it "returns nil when no branch exists" do
+ expect(repository).to receive(:branch_names).at_least(:once).and_return([])
+ expect(repository.discover_default_branch).to be_nil
+ end
+ end
+
+ describe :branch_names do
+ subject { repository.branch_names }
+
+ it 'has SeedRepo::Repo::BRANCHES.size elements' do
+ expect(subject.size).to eq(SeedRepo::Repo::BRANCHES.size)
+ end
+ it { is_expected.to include("master") }
+ it { is_expected.not_to include("branch-from-space") }
+ end
+
+ describe :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
+
+ 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") }
+ end
+
+ shared_examples 'archive check' do |extenstion|
+ it { expect(metadata['ArchivePath']).to match(/tmp\/gitlab-git-test.git\/gitlab-git-test-master-#{SeedRepo::LastCommit::ID}/) }
+ it { expect(metadata['ArchivePath']).to end_with extenstion }
+ end
+
+ describe :archive do
+ let(:metadata) { repository.archive_metadata('master', '/tmp') }
+
+ it_should_behave_like 'archive check', '.tar.gz'
+ end
+
+ describe :archive_zip do
+ let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip') }
+
+ it_should_behave_like 'archive check', '.zip'
+ end
+
+ describe :archive_bz2 do
+ let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2') }
+
+ it_should_behave_like 'archive check', '.tar.bz2'
+ end
+
+ describe :archive_fallback do
+ let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup') }
+
+ it_should_behave_like 'archive check', '.tar.gz'
+ end
+
+ describe :size do
+ subject { repository.size }
+
+ it { is_expected.to be < 2 }
+ end
+
+ describe :has_commits? do
+ it { expect(repository.has_commits?).to be_truthy }
+ end
+
+ describe :empty? do
+ it { expect(repository.empty?).to be_falsey }
+ end
+
+ describe :bare? do
+ it { expect(repository.bare?).to be_truthy }
+ end
+
+ describe :heads do
+ let(:heads) { repository.heads }
+ subject { heads }
+
+ it { is_expected.to be_kind_of Array }
+
+ describe '#size' do
+ subject { super().size }
+ it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) }
+ end
+
+ context :head do
+ subject { heads.first }
+
+ describe '#name' do
+ subject { super().name }
+ it { is_expected.to eq("feature") }
+ end
+
+ context :commit do
+ subject { heads.first.dereferenced_target.sha }
+
+ it { is_expected.to eq("0b4bc9a49b562e85de7cc9e834518ea6828729b9") }
+ end
+ end
+ 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 :search_files do
+ let(:results) { repository.search_files('rails', 'master') }
+ subject { results }
+
+ it { is_expected.to be_kind_of Array }
+
+ describe '#first' do
+ subject { super().first }
+ it { is_expected.to be_kind_of Gitlab::Git::BlobSnippet }
+ end
+
+ context 'blob result' do
+ subject { results.first }
+
+ describe '#ref' do
+ subject { super().ref }
+ it { is_expected.to eq('master') }
+ end
+
+ describe '#filename' do
+ subject { super().filename }
+ it { is_expected.to eq('CHANGELOG') }
+ end
+
+ describe '#startline' do
+ subject { super().startline }
+ it { is_expected.to eq(35) }
+ end
+
+ describe '#data' do
+ subject { super().data }
+ it { is_expected.to include "Ability to filter by multiple labels" }
+ end
+ end
+ end
+
+ context :submodules do
+ let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) }
+
+ context 'where repo has submodules' do
+ let(:submodules) { repository.submodules('master') }
+ let(:submodule) { submodules.first }
+
+ it { expect(submodules).to be_kind_of Hash }
+ it { expect(submodules.empty?).to be_falsey }
+
+ it 'should have valid data' do
+ expect(submodule).to eq([
+ "six", {
+ "id" => "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
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 964cded917c..7f8ea5251f0 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -146,6 +146,16 @@ describe API::Commits, api: true do
expect(response).to have_http_status(400)
end
+
+ context 'with project path in URL' do
+ let(:url) { "/projects/#{project.namespace.path}%2F#{project.path}/repository/commits" }
+
+ it 'a new file in project repo' do
+ post api(url, user), valid_c_params
+
+ expect(response).to have_http_status(201)
+ end
+ end
end
context :delete do
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 0e8d6faea27..e355d5e28bc 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -23,6 +23,7 @@ describe API::Groups, api: true do
context "when unauthenticated" do
it "returns authentication error" do
get api("/groups")
+
expect(response).to have_http_status(401)
end
end
@@ -30,6 +31,7 @@ describe API::Groups, api: true do
context "when authenticated as user" do
it "normal user: returns an array of groups of user1" do
get api("/groups", user1)
+
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
@@ -48,6 +50,7 @@ describe API::Groups, api: true do
context "when authenticated as admin" do
it "admin: returns an array of all groups" do
get api("/groups", admin)
+
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
@@ -94,6 +97,7 @@ describe API::Groups, api: true do
it "returns all groups you have access to" do
public_group = create :group, :public
+
get api("/groups", user1), all_available: true
expect(response).to have_http_status(200)
@@ -140,6 +144,7 @@ describe API::Groups, api: true do
context 'when unauthenticated' do
it 'returns authentication error' do
get api('/groups/owned')
+
expect(response).to have_http_status(401)
end
end
@@ -147,6 +152,7 @@ describe API::Groups, api: true do
context 'when authenticated as group owner' do
it 'returns an array of groups the user owns' do
get api('/groups/owned', user2)
+
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(group2.name)
@@ -179,6 +185,7 @@ describe API::Groups, api: true do
it "does not return a non existing group" do
get api("/groups/1328", user1)
+
expect(response).to have_http_status(404)
end
@@ -192,12 +199,14 @@ describe API::Groups, api: true do
context "when authenticated as admin" do
it "returns any existing group" do
get api("/groups/#{group2.id}", admin)
+
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(group2.name)
end
it "does not return a non existing group" do
get api("/groups/1328", admin)
+
expect(response).to have_http_status(404)
end
end
@@ -205,12 +214,14 @@ describe API::Groups, api: true do
context 'when using group path in URL' do
it 'returns any existing group' do
get api("/groups/#{group1.path}", admin)
+
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(group1.name)
end
it 'does not return a non existing group' do
get api('/groups/unknown', admin)
+
expect(response).to have_http_status(404)
end
@@ -302,6 +313,7 @@ describe API::Groups, api: true do
it "does not return a non existing group" do
get api("/groups/1328/projects", user1)
+
expect(response).to have_http_status(404)
end
@@ -325,6 +337,7 @@ describe API::Groups, api: true do
context "when authenticated as admin" do
it "should return any existing group" do
get api("/groups/#{group2.id}/projects", admin)
+
expect(response).to have_http_status(200)
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(project2.name)
@@ -332,6 +345,7 @@ describe API::Groups, api: true do
it "should not return a non existing group" do
get api("/groups/1328/projects", admin)
+
expect(response).to have_http_status(404)
end
end
@@ -347,6 +361,7 @@ describe API::Groups, api: true do
it 'does not return a non existing group' do
get api('/groups/unknown/projects', admin)
+
expect(response).to have_http_status(404)
end
@@ -362,6 +377,7 @@ describe API::Groups, api: true do
context "when authenticated as user without group permissions" do
it "does not create group" do
post api("/groups", user1), attributes_for(:group)
+
expect(response).to have_http_status(403)
end
end
@@ -371,6 +387,7 @@ describe API::Groups, api: true do
group = attributes_for(:group, { request_access_enabled: false })
post api("/groups", user3), group
+
expect(response).to have_http_status(201)
expect(json_response["name"]).to eq(group[:name])
@@ -380,17 +397,20 @@ describe API::Groups, api: true do
it "does not create group, duplicate" do
post api("/groups", user3), { name: 'Duplicate Test', path: group2.path }
+
expect(response).to have_http_status(400)
expect(response.message).to eq("Bad Request")
end
it "returns 400 bad request error if name not given" do
post api("/groups", user3), { path: group2.path }
+
expect(response).to have_http_status(400)
end
it "returns 400 bad request error if path not given" do
post api("/groups", user3), { name: 'test' }
+
expect(response).to have_http_status(400)
end
end
@@ -400,18 +420,22 @@ describe API::Groups, api: true do
context "when authenticated as user" do
it "removes group" do
delete api("/groups/#{group1.id}", user1)
+
expect(response).to have_http_status(200)
end
it "does not remove a group if not an owner" do
user4 = create(:user)
group1.add_master(user4)
+
delete api("/groups/#{group1.id}", user3)
+
expect(response).to have_http_status(403)
end
it "does not remove a non existing group" do
delete api("/groups/1328", user1)
+
expect(response).to have_http_status(404)
end
@@ -425,11 +449,13 @@ describe API::Groups, api: true do
context "when authenticated as admin" do
it "removes any existing group" do
delete api("/groups/#{group2.id}", admin)
+
expect(response).to have_http_status(200)
end
it "does not remove a non existing group" do
delete api("/groups/1328", admin)
+
expect(response).to have_http_status(404)
end
end
@@ -437,15 +463,17 @@ describe API::Groups, api: true do
describe "POST /groups/:id/projects/:project_id" do
let(:project) { create(:project) }
+ let(:project_path) { "#{project.namespace.path}%2F#{project.path}" }
+
before(:each) do
allow_any_instance_of(Projects::TransferService).
to receive(:execute).and_return(true)
- allow(Project).to receive(:find).and_return(project)
end
context "when authenticated as user" do
it "does not transfer project to group" do
post api("/groups/#{group1.id}/projects/#{project.id}", user2)
+
expect(response).to have_http_status(403)
end
end
@@ -453,8 +481,45 @@ describe API::Groups, api: true do
context "when authenticated as admin" do
it "transfers project to group" do
post api("/groups/#{group1.id}/projects/#{project.id}", admin)
+
expect(response).to have_http_status(201)
end
+
+ context 'when using project path in URL' do
+ context 'with a valid project path' do
+ it "transfers project to group" do
+ post api("/groups/#{group1.id}/projects/#{project_path}", admin)
+
+ expect(response).to have_http_status(201)
+ end
+ end
+
+ context 'with a non-existent project path' do
+ it "does not transfer project to group" do
+ post api("/groups/#{group1.id}/projects/nogroup%2Fnoproject", admin)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when using a group path in URL' do
+ context 'with a valid group path' do
+ it "transfers project to group" do
+ post api("/groups/#{group1.path}/projects/#{project_path}", admin)
+
+ expect(response).to have_http_status(201)
+ end
+ end
+
+ context 'with a non-existent group path' do
+ it "does not transfer project to group" do
+ post api("/groups/noexist/projects/#{project_path}", admin)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
end
end
end
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 668e39f9dba..39c9e0505d1 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -6,7 +6,7 @@ describe API::Services, api: true do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:user2) { create(:user) }
- let(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
+ let(:project) {create(:empty_project, creator_id: user.id, namespace: user.namespace) }
Service.available_services_names.each do |service|
describe "PUT /projects/:id/services/#{service.dasherize}" do
@@ -92,57 +92,78 @@ describe API::Services, api: true do
describe 'POST /projects/:id/services/:slug/trigger' do
let!(:project) { create(:empty_project) }
- let(:service_name) { 'mattermost_slash_commands' }
- context 'no service is available' do
- it 'returns a not found message' do
- post api("/projects/#{project.id}/services/idonotexist/trigger")
+ describe 'Mattermost Service' do
+ let(:service_name) { 'mattermost_slash_commands' }
- expect(response).to have_http_status(404)
- expect(json_response["error"]).to eq("404 Not Found")
+ context 'no service is available' do
+ it 'returns a not found message' do
+ post api("/projects/#{project.id}/services/idonotexist/trigger")
+
+ expect(response).to have_http_status(404)
+ expect(json_response["error"]).to eq("404 Not Found")
+ end
end
- end
- context 'the service exists' do
- let(:params) { { token: 'token' } }
+ context 'the service exists' do
+ let(:params) { { token: 'token' } }
- context 'the service is not active' do
- let!(:inactive_service) do
- project.create_mattermost_slash_commands_service(
- active: false,
- properties: { token: 'token' }
- )
- end
+ context 'the service is not active' do
+ before do
+ project.create_mattermost_slash_commands_service(
+ active: false,
+ properties: params
+ )
+ end
- it 'when the service is inactive' do
- post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger"), params
+ it 'when the service is inactive' do
+ post api("/projects/#{project.id}/services/#{service_name}/trigger"), params
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(404)
+ end
end
- end
- context 'the service is active' do
- let!(:active_service) do
- project.create_mattermost_slash_commands_service(
- active: true,
- properties: { token: 'token' }
- )
+ context 'the service is active' do
+ before do
+ project.create_mattermost_slash_commands_service(
+ active: true,
+ properties: params
+ )
+ end
+
+ it 'returns status 200' do
+ post api("/projects/#{project.id}/services/#{service_name}/trigger"), params
+
+ expect(response).to have_http_status(200)
+ end
end
- it 'returns status 200' do
- post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger"), params
+ context 'when the project can not be found' do
+ it 'returns a generic 404' do
+ post api("/projects/404/services/#{service_name}/trigger"), params
- expect(response).to have_http_status(200)
+ expect(response).to have_http_status(404)
+ expect(json_response["message"]).to eq("404 Service Not Found")
+ end
end
end
+ end
- context 'when the project can not be found' do
- it 'returns a generic 404' do
- post api("/projects/404/services/mattermost_slash_commands/trigger"), params
+ describe 'Slack Service' do
+ let(:service_name) { 'slack_slash_commands' }
- expect(response).to have_http_status(404)
- expect(json_response["message"]).to eq("404 Service Not Found")
- end
+ before do
+ project.create_slack_slash_commands_service(
+ active: true,
+ properties: { token: 'token' }
+ )
+ end
+
+ it 'returns status 200' do
+ post api("/projects/#{project.id}/services/#{service_name}/trigger"), token: 'token', text: 'help'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['response_type']).to eq("ephemeral")
end
end
end
diff --git a/spec/support/matchers/be_valid_commit.rb b/spec/support/matchers/be_valid_commit.rb
new file mode 100644
index 00000000000..3696e4d5f03
--- /dev/null
+++ b/spec/support/matchers/be_valid_commit.rb
@@ -0,0 +1,8 @@
+RSpec::Matchers.define :be_valid_commit do
+ match do |actual|
+ actual &&
+ actual.id == SeedRepo::Commit::ID &&
+ actual.message == SeedRepo::Commit::MESSAGE &&
+ actual.author_name == SeedRepo::Commit::AUTHOR_FULL_NAME
+ end
+end
diff --git a/spec/support/seed_helper.rb b/spec/support/seed_helper.rb
new file mode 100644
index 00000000000..3f8398a31e3
--- /dev/null
+++ b/spec/support/seed_helper.rb
@@ -0,0 +1,112 @@
+# This file is specific to specs in spec/lib/gitlab/git/
+
+SEED_REPOSITORY_PATH = File.expand_path('../../tmp/repositories', __dir__)
+TEST_REPO_PATH = File.join(SEED_REPOSITORY_PATH, 'gitlab-git-test.git')
+TEST_NORMAL_REPO_PATH = File.join(SEED_REPOSITORY_PATH, "not-bare-repo.git")
+TEST_MUTABLE_REPO_PATH = File.join(SEED_REPOSITORY_PATH, "mutable-repo.git")
+TEST_BROKEN_REPO_PATH = File.join(SEED_REPOSITORY_PATH, "broken-repo.git")
+
+module SeedHelper
+ GITLAB_URL = "https://gitlab.com/gitlab-org/gitlab-git-test.git"
+
+ def ensure_seeds
+ if File.exist?(SEED_REPOSITORY_PATH)
+ FileUtils.rm_r(SEED_REPOSITORY_PATH)
+ end
+
+ FileUtils.mkdir_p(SEED_REPOSITORY_PATH)
+
+ create_bare_seeds
+ create_normal_seeds
+ create_mutable_seeds
+ create_broken_seeds
+ create_git_attributes
+ create_invalid_git_attributes
+ end
+
+ def create_bare_seeds
+ system(git_env, *%W(git clone --bare #{GITLAB_URL}),
+ chdir: SEED_REPOSITORY_PATH,
+ out: '/dev/null',
+ err: '/dev/null')
+ end
+
+ def create_normal_seeds
+ system(git_env, *%W(git clone #{TEST_REPO_PATH} #{TEST_NORMAL_REPO_PATH}),
+ out: '/dev/null',
+ err: '/dev/null')
+ end
+
+ def create_mutable_seeds
+ system(git_env, *%W(git clone #{TEST_REPO_PATH} #{TEST_MUTABLE_REPO_PATH}),
+ out: '/dev/null',
+ err: '/dev/null')
+
+ system(git_env, *%w(git branch -t feature origin/feature),
+ chdir: TEST_MUTABLE_REPO_PATH, out: '/dev/null', err: '/dev/null')
+
+ system(git_env, *%W(git remote add expendable #{GITLAB_URL}),
+ chdir: TEST_MUTABLE_REPO_PATH, out: '/dev/null', err: '/dev/null')
+ end
+
+ def create_broken_seeds
+ system(git_env, *%W(git clone --bare #{TEST_REPO_PATH} #{TEST_BROKEN_REPO_PATH}),
+ out: '/dev/null',
+ err: '/dev/null')
+
+ refs_path = File.join(TEST_BROKEN_REPO_PATH, 'refs')
+
+ FileUtils.rm_r(refs_path)
+ end
+
+ def create_git_attributes
+ dir = File.join(SEED_REPOSITORY_PATH, 'with-git-attributes.git', 'info')
+
+ FileUtils.mkdir_p(dir)
+
+ File.open(File.join(dir, 'attributes'), 'w') do |handle|
+ handle.write <<-EOF.strip
+# This is a comment, it should be ignored.
+
+*.txt text
+*.jpg -text
+*.sh eol=lf gitlab-language=shell
+*.haml.* gitlab-language=haml
+foo/bar.* foo
+*.cgi key=value?p1=v1&p2=v2
+/*.png gitlab-language=png
+*.binary binary
+
+# This uses a tab instead of spaces to ensure the parser also supports this.
+*.md\tgitlab-language=markdown
+bla/bla.txt
+ EOF
+ end
+ end
+
+ def create_invalid_git_attributes
+ dir = File.join(SEED_REPOSITORY_PATH, 'with-invalid-git-attributes.git', 'info')
+
+ FileUtils.mkdir_p(dir)
+
+ enc = Encoding::UTF_16
+
+ File.open(File.join(dir, 'attributes'), 'w', encoding: enc) do |handle|
+ handle.write('# hello'.encode(enc))
+ end
+ end
+
+ # Prevent developer git configurations from being persisted to test
+ # repositories
+ def git_env
+ { 'GIT_TEMPLATE_DIR' => '' }
+ end
+end
+
+RSpec.configure do |config|
+ config.include SeedHelper, :seed_helper
+
+ config.before(:all, :seed_helper) do
+ ensure_seeds
+ end
+end
diff --git a/spec/support/seed_repo.rb b/spec/support/seed_repo.rb
new file mode 100644
index 00000000000..9f2cd7c67c5
--- /dev/null
+++ b/spec/support/seed_repo.rb
@@ -0,0 +1,143 @@
+# Seed repo:
+# 0e50ec4d3c7ce42ab74dda1d422cb2cbffe1e326 Merge branch 'lfs_pointers' into 'master'
+# 33bcff41c232a11727ac6d660bd4b0c2ba86d63d Add valid and invalid lfs pointers
+# 732401c65e924df81435deb12891ef570167d2e2 Update year in license file
+# b0e52af38d7ea43cf41d8a6f2471351ac036d6c9 Empty commit
+# 40f4a7a617393735a95a0bb67b08385bc1e7c66d Add ISO-8859-encoded file
+# 66028349a123e695b589e09a36634d976edcc5e8 Merge branch 'add-comments-to-gitmodules' into 'master'
+# de5714f34c4e34f1d50b9a61a2e6c9132fe2b5fd Add comments to the end of .gitmodules to test parsing
+# fa1b1e6c004a68b7d8763b86455da9e6b23e36d6 Merge branch 'add-files' into 'master'
+# eb49186cfa5c4338011f5f590fac11bd66c5c631 Add submodules nested deeper than the root
+# 18d9c205d0d22fdf62bc2f899443b83aafbf941f Add executables and links files
+# 5937ac0a7beb003549fc5fd26fc247adbce4a52e Add submodule from gitlab.com
+# 570e7b2abdd848b95f2f578043fc23bd6f6fd24d Change some files
+# 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 More submodules
+# d14d6c0abdd253381df51a723d58691b2ee1ab08 Remove ds_store files
+# c1acaa58bbcbc3eafe538cb8274ba387047b69f8 Ignore DS files
+# ae73cb07c9eeaf35924a10f713b364d32b2dd34f Binary file added
+# 874797c3a73b60d2187ed6e2fcabd289ff75171e Ruby files modified
+# 2f63565e7aac07bcdadb654e253078b727143ec4 Modified image
+# 33f3729a45c02fc67d00adb1b8bca394b0e761d9 Image added
+# 913c66a37b4a45b9769037c55c2d238bd0942d2e Files, encoding and much more
+# cfe32cf61b73a0d5e9f13e774abde7ff789b1660 Add submodule
+# 6d394385cf567f80a8fd85055db1ab4c5295806f Added contributing guide
+# 1a0b36b3cdad1d2ee32457c102a8c0b7056fa863 Initial commit
+
+module SeedRepo
+ module BigCommit
+ ID = "913c66a37b4a45b9769037c55c2d238bd0942d2e"
+ PARENT_ID = "cfe32cf61b73a0d5e9f13e774abde7ff789b1660"
+ MESSAGE = "Files, encoding and much more"
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
+ FILES_COUNT = 2
+ end
+
+ module Commit
+ ID = "570e7b2abdd848b95f2f578043fc23bd6f6fd24d"
+ PARENT_ID = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
+ MESSAGE = "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n"
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
+ FILES = ["files/ruby/popen.rb", "files/ruby/regex.rb"]
+ FILES_COUNT = 2
+ C_FILE_PATH = "files/ruby"
+ C_FILES = ["popen.rb", "regex.rb", "version_info.rb"]
+ BLOB_FILE = %{%h3= @key.title\n%hr\n%pre= @key.key\n.actions\n = link_to 'Remove', @key, :confirm => 'Are you sure?', :method => :delete, :class => \"btn danger delete-key\"\n\n\n}
+ BLOB_FILE_PATH = "app/views/keys/show.html.haml"
+ end
+
+ module EmptyCommit
+ ID = "b0e52af38d7ea43cf41d8a6f2471351ac036d6c9"
+ PARENT_ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d"
+ MESSAGE = "Empty commit"
+ AUTHOR_FULL_NAME = "Rémy Coutable"
+ FILES = []
+ FILES_COUNT = FILES.count
+ end
+
+ module EncodingCommit
+ ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d"
+ PARENT_ID = "66028349a123e695b589e09a36634d976edcc5e8"
+ MESSAGE = "Add ISO-8859-encoded file"
+ AUTHOR_FULL_NAME = "Stan Hu"
+ FILES = ["encoding/iso8859.txt"]
+ FILES_COUNT = FILES.count
+ end
+
+ module FirstCommit
+ ID = "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863"
+ PARENT_ID = nil
+ MESSAGE = "Initial commit"
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
+ FILES = ["LICENSE", ".gitignore", "README.md"]
+ FILES_COUNT = 3
+ end
+
+ module LastCommit
+ ID = "4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6"
+ PARENT_ID = "0e1b353b348f8477bdbec1ef47087171c5032cd9"
+ MESSAGE = "Merge branch 'master' into 'master'"
+ AUTHOR_FULL_NAME = "Stan Hu"
+ FILES = ["bin/executable"]
+ FILES_COUNT = FILES.count
+ end
+
+ module Repo
+ HEAD = "master"
+ BRANCHES = %w[
+ feature
+ fix
+ fix-blob-path
+ fix-existing-submodule-dir
+ fix-mode
+ gitattributes
+ gitattributes-updated
+ master
+ merge-test
+ ]
+ TAGS = %w[v1.0.0 v1.1.0 v1.2.0 v1.2.1]
+ end
+
+ module RubyBlob
+ ID = "7e3e39ebb9b2bf433b4ad17313770fbe4051649c"
+ NAME = "popen.rb"
+ CONTENT = <<-eos
+require 'fileutils'
+require 'open3'
+
+module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ raise RuntimeError, "System commands must be given as an array of strings"
+ end
+
+ path ||= Dir.pwd
+
+ vars = {
+ "PWD" => path
+ }
+
+ options = {
+ chdir: path
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
+ end
+end
+ eos
+ end
+end
diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb
index a066ea078e6..c101f6f164d 100644
--- a/spec/views/projects/pipelines/show.html.haml_spec.rb
+++ b/spec/views/projects/pipelines/show.html.haml_spec.rb
@@ -29,7 +29,7 @@ describe 'projects/pipelines/show' do
render
expect(rendered).to have_css('.js-pipeline-graph')
- expect(rendered).to have_css('.grouped-pipeline-dropdown')
+ expect(rendered).to have_css('.js-grouped-pipeline-dropdown')
# stages
expect(rendered).to have_text('Build')