From 63670bb7f84d1c728c32a1914ed378de4e4ac0de Mon Sep 17 00:00:00 2001 From: keen99 Date: Tue, 29 Mar 2016 21:51:31 +0000 Subject: [PATCH 01/24] existing folders and existing repos should not use the same set of instructions - pushing an existing repo into gitlab using the previous instructions would result in loss (at the gitlab remote end) of exist branches and tags. so lets push those! --- app/views/projects/empty.html.haml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 6ad7b05155a..05e73326ee4 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -44,7 +44,7 @@ git push -u origin master %fieldset - %h5 Existing folder or Git repository + %h5 Existing folder %pre.light-well :preserve cd existing_folder @@ -54,6 +54,15 @@ git commit git push -u origin master + %fieldset + %h5 Existing Git repository + %pre.light-well + :preserve + cd existing_repo + git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')} + git push -u origin --all + git push -u origin --tags + - if can? current_user, :remove_project, @project .prepend-top-20 = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right" From 4e90f1009d3b587a7d56936f4e960d222d7f7993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Sun, 3 Apr 2016 13:44:32 +0000 Subject: [PATCH 02/24] use plain shell no bashism detected here --- scripts/notify_slack.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/notify_slack.sh b/scripts/notify_slack.sh index 0a4239e132c..6b3bc563c7a 100755 --- a/scripts/notify_slack.sh +++ b/scripts/notify_slack.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # Sends Slack notification ERROR_MSG to CHANNEL # An env. variable CI_SLACK_WEBHOOK_URL needs to be set. From 2eb0beb661cf1a97e3f36ae2785bb1ad44afd5d0 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Thu, 22 Dec 2016 14:31:42 -0600 Subject: [PATCH 03/24] add natural sorting token for build names --- app/models/commit_status.rb | 6 ++++++ app/views/projects/stage/_graph.html.haml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 31cd381dcd2..897e53fc7bd 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -137,4 +137,10 @@ class CommitStatus < ActiveRecord::Base .new(self, current_user) .fabricate! end + + def natsort_name + name.split(/(\d+)/).map do |v| + v =~ /\d+/ ? v.to_i : v + end + end end diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml index d9d392fa02f..faadcfee30c 100644 --- a/app/views/projects/stage/_graph.html.haml +++ b/app/views/projects/stage/_graph.html.haml @@ -1,6 +1,6 @@ - stage = local_assigns.fetch(:stage) - statuses = stage.statuses.latest -- status_groups = statuses.sort_by(&:name).group_by(&:group_name) +- status_groups = statuses.sort_by(&:natsort_name).group_by(&:group_name) %li.stage-column .stage-name %a{ name: stage.name } From 19c7fc75a8aae79214fec0d71e5bdd54a58f5964 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Thu, 22 Dec 2016 14:31:46 -0600 Subject: [PATCH 04/24] add tests for natural sorting of build names --- .../projects/pipelines/show.html.haml_spec.rb | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb index a066ea078e6..afda286c089 100644 --- a/spec/views/projects/pipelines/show.html.haml_spec.rb +++ b/spec/views/projects/pipelines/show.html.haml_spec.rb @@ -45,6 +45,41 @@ describe 'projects/pipelines/show' do expect(rendered).to have_text('jenkins') end + it 'lists builds in the correct sort order' do + create_build('test', 1, 'karma 0 20', :created) + create_build('test', 1, 'karma 12 20', :created) + create_build('test', 1, 'karma 1 20', :created) + create_build('test', 1, 'karma 10 20', :created) + create_build('test', 1, 'karma 11 20', :created) + create_build('test', 1, 'karma 2 20', :created) + create_build('test', 1, 'test 1.10', :created) + create_build('test', 1, 'test 1.5.1', :created) + create_build('test', 1, 'test 1 a', :created) + + render + + # spaced builds order + expected_order_1 = [ + 'karma 0 20', + 'karma 1 20', + 'karma 2 20', + 'karma 10 20', + 'karma 11 20', + 'karma 12 20' + ].join(' ') + + expect(rendered).to have_text(expected_order_1) + + # decimal builds order + expected_order_2 = [ + 'test 1 a', + 'test 1.5.1', + 'test 1.10' + ].join(' ') + + expect(rendered).to have_text(expected_order_2) + end + private def create_build(stage, stage_idx, name, status) From 713e8cf2eb5f5b46a355f9d76a49076c3b459d1c Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Thu, 22 Dec 2016 14:39:39 -0600 Subject: [PATCH 05/24] add CHANGELOG.md entry for !8277 --- changelogs/unreleased/fix-build-sort-order.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/fix-build-sort-order.yml diff --git a/changelogs/unreleased/fix-build-sort-order.yml b/changelogs/unreleased/fix-build-sort-order.yml new file mode 100644 index 00000000000..a6d6371f69a --- /dev/null +++ b/changelogs/unreleased/fix-build-sort-order.yml @@ -0,0 +1,4 @@ +--- +title: Sort numbers in build names more intelligently +merge_request: 8277 +author: From 1c5a506588342bdb2a6390f14d333d41a5482f58 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 27 Dec 2016 09:59:42 -0600 Subject: [PATCH 06/24] rename sort method --- app/models/commit_status.rb | 2 +- app/views/projects/stage/_graph.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 897e53fc7bd..9547c57b2ae 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -138,7 +138,7 @@ class CommitStatus < ActiveRecord::Base .fabricate! end - def natsort_name + def sortable_name name.split(/(\d+)/).map do |v| v =~ /\d+/ ? v.to_i : v end diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml index faadcfee30c..4ee30b023ac 100644 --- a/app/views/projects/stage/_graph.html.haml +++ b/app/views/projects/stage/_graph.html.haml @@ -1,6 +1,6 @@ - stage = local_assigns.fetch(:stage) - statuses = stage.statuses.latest -- status_groups = statuses.sort_by(&:natsort_name).group_by(&:group_name) +- status_groups = statuses.sort_by(&:sortable_name).group_by(&:group_name) %li.stage-column .stage-name %a{ name: stage.name } From dc3d40ff28e4087120d48a94db5bd3d3b32f164d Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 2 Jan 2017 11:42:08 -0600 Subject: [PATCH 07/24] prefer unit test on model over view test --- spec/models/commit_status_spec.rb | 22 ++++++++++++ .../projects/pipelines/show.html.haml_spec.rb | 35 ------------------- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 701f3323c0f..daabc804d16 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -243,4 +243,26 @@ describe CommitStatus, models: true do .to be_a Gitlab::Ci::Status::Success end end + + describe '#sortable_name' do + subject { commit_status.sortable_name } + + tests = { + 'karma' => ['karma'], + 'karma 0 20' => ['karma ', 0, ' ', 20], + 'karma 10 20' => ['karma ', 10, ' ', 20], + 'karma 50:100' => ['karma ', 50, ':', 100], + 'karma 1.10' => ['karma ', 1, '.', 10], + 'karma 1.5.1' => ['karma ', 1, '.', 5, '.', 1], + 'karma 1 a' => ['karma ', 1, ' a'] + } + + tests.each do |name, sortable_name| + it "'#{name}' sorts as '#{sortable_name}'" do + commit_status.name = name + + is_expected.to eq(sortable_name) + end + end + 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 afda286c089..a066ea078e6 100644 --- a/spec/views/projects/pipelines/show.html.haml_spec.rb +++ b/spec/views/projects/pipelines/show.html.haml_spec.rb @@ -45,41 +45,6 @@ describe 'projects/pipelines/show' do expect(rendered).to have_text('jenkins') end - it 'lists builds in the correct sort order' do - create_build('test', 1, 'karma 0 20', :created) - create_build('test', 1, 'karma 12 20', :created) - create_build('test', 1, 'karma 1 20', :created) - create_build('test', 1, 'karma 10 20', :created) - create_build('test', 1, 'karma 11 20', :created) - create_build('test', 1, 'karma 2 20', :created) - create_build('test', 1, 'test 1.10', :created) - create_build('test', 1, 'test 1.5.1', :created) - create_build('test', 1, 'test 1 a', :created) - - render - - # spaced builds order - expected_order_1 = [ - 'karma 0 20', - 'karma 1 20', - 'karma 2 20', - 'karma 10 20', - 'karma 11 20', - 'karma 12 20' - ].join(' ') - - expect(rendered).to have_text(expected_order_1) - - # decimal builds order - expected_order_2 = [ - 'test 1 a', - 'test 1.5.1', - 'test 1.10' - ].join(' ') - - expect(rendered).to have_text(expected_order_2) - end - private def create_build(stage, stage_idx, name, status) From 9c27408bb75f7228a015e3975cc7bcd38f9dec30 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 11 Jan 2017 09:53:19 -0500 Subject: [PATCH 08/24] Fix broken link in docs [ci skip] --- doc/project_services/slack_slash_commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/project_services/slack_slash_commands.md b/doc/project_services/slack_slash_commands.md index b6b5c741d90..d9ff573d185 100644 --- a/doc/project_services/slack_slash_commands.md +++ b/doc/project_services/slack_slash_commands.md @@ -6,7 +6,7 @@ Slack commands give users an extra interface to perform common operations from the chat environment. This allows one to, for example, create an issue as soon as the idea was discussed in chat. For all available commands try the help subcommand, for example: `/gitlab help`, -all review the [full list of commands](../integrations/chat_commands.md). +all review the [full list of commands](../integration/chat_commands.md). ## Prerequisites From 0b5b3ec3a432842bc53c7226c6db98e484aeee50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 12 Jan 2017 17:37:14 -0500 Subject: [PATCH 09/24] Remove useless permission checks in Gitlab::Checks::ChangeAccess MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/gitlab/checks/change_access.rb | 4 ++-- spec/lib/gitlab/checks/change_access_spec.rb | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index 9c391fa92a3..273118135a9 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -30,9 +30,9 @@ module Gitlab return unless @branch_name return unless project.protected_branch?(@branch_name) - if forced_push? && user_access.cannot_do_action?(:force_push_code_to_protected_branches) + if forced_push? return "You are not allowed to force push code to a protected branch on this project." - elsif Gitlab::Git.blank_ref?(@newrev) && user_access.cannot_do_action?(:remove_protected_branches) + elsif Gitlab::Git.blank_ref?(@newrev) return "You are not allowed to delete protected branches from this project." end diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb index 39069b49978..98effecdbbc 100644 --- a/spec/lib/gitlab/checks/change_access_spec.rb +++ b/spec/lib/gitlab/checks/change_access_spec.rb @@ -56,7 +56,6 @@ describe Gitlab::Checks::ChangeAccess, lib: true do it 'returns an error if the user is not allowed to do forced pushes to protected branches' do expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true) - expect(user_access).to receive(:can_do_action?).with(:force_push_code_to_protected_branches).and_return(false) expect(subject.status).to be(false) expect(subject.message).to eq('You are not allowed to force push code to a protected branch on this project.') @@ -88,8 +87,6 @@ describe Gitlab::Checks::ChangeAccess, lib: true do end it 'returns an error if the user is not allowed to delete protected branches' do - expect(user_access).to receive(:can_do_action?).with(:remove_protected_branches).and_return(false) - expect(subject.status).to be(false) expect(subject.message).to eq('You are not allowed to delete protected branches from this project.') end From e1521b748bc12f71fa7b8f311fcefb9bd3aca8ae Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Thu, 12 Jan 2017 18:27:06 -0500 Subject: [PATCH 10/24] Mutate the attribute instead of issuing a write operation to the DB This fixes gitlab-org/gitlab-ee#1520 --- app/models/concerns/project_features_compatibility.rb | 2 +- changelogs/unreleased/bug-project-feature-compatibility.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/bug-project-feature-compatibility.yml diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb index 6d88951c713..60734bc6660 100644 --- a/app/models/concerns/project_features_compatibility.rb +++ b/app/models/concerns/project_features_compatibility.rb @@ -32,6 +32,6 @@ module ProjectFeaturesCompatibility build_project_feature unless project_feature access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED - project_feature.update_attribute(field, access_level) + project_feature.send(:write_attribute, field, access_level) end end diff --git a/changelogs/unreleased/bug-project-feature-compatibility.yml b/changelogs/unreleased/bug-project-feature-compatibility.yml new file mode 100644 index 00000000000..2124ee085e0 --- /dev/null +++ b/changelogs/unreleased/bug-project-feature-compatibility.yml @@ -0,0 +1,5 @@ +--- +title: Mutate the attribute instead of issuing a write operation to the DB in `ProjectFeaturesCompatibility` + concern. +merge_request: 8552 +author: From 892ff3a3ae640272f8712fb190242f2b1fe010a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 12 Jan 2017 13:12:50 -0500 Subject: [PATCH 11/24] Check for env[Grape::Env::GRAPE_ROUTING_ARGS] instead of endpoint.route MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `endpoint.route` is calling `env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info]` but `env[Grape::Env::GRAPE_ROUTING_ARGS]` is `nil` in the case of a 405 response Signed-off-by: Rémy Coutable --- .../26587-metrics-middleware-endpoint-is-nil.yml | 4 ++++ lib/gitlab/metrics/rack_middleware.rb | 15 +++++++++++---- spec/lib/gitlab/metrics/rack_middleware_spec.rb | 11 +++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/26587-metrics-middleware-endpoint-is-nil.yml diff --git a/changelogs/unreleased/26587-metrics-middleware-endpoint-is-nil.yml b/changelogs/unreleased/26587-metrics-middleware-endpoint-is-nil.yml new file mode 100644 index 00000000000..5891a5ef6e8 --- /dev/null +++ b/changelogs/unreleased/26587-metrics-middleware-endpoint-is-nil.yml @@ -0,0 +1,4 @@ +--- +title: Check for env[Grape::Env::GRAPE_ROUTING_ARGS] instead of endpoint.route +merge_request: 8544 +author: diff --git a/lib/gitlab/metrics/rack_middleware.rb b/lib/gitlab/metrics/rack_middleware.rb index d01d47a6a7a..47f88727fc8 100644 --- a/lib/gitlab/metrics/rack_middleware.rb +++ b/lib/gitlab/metrics/rack_middleware.rb @@ -71,10 +71,17 @@ module Gitlab def tag_endpoint(trans, env) endpoint = env[ENDPOINT_KEY] - # endpoint.route is nil in the case of a 405 response - if endpoint.route - path = endpoint_paths_cache[endpoint.route.request_method][endpoint.route.path] - trans.action = "Grape##{endpoint.route.request_method} #{path}" + begin + route = endpoint.route + rescue + # endpoint.route is calling env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info] + # but env[Grape::Env::GRAPE_ROUTING_ARGS] is nil in the case of a 405 response + # so we're rescuing exceptions and bailing out + end + + if route + path = endpoint_paths_cache[route.request_method][route.path] + trans.action = "Grape##{route.request_method} #{path}" end end diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb index 7371b578a48..fb470ea7568 100644 --- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb @@ -126,5 +126,16 @@ describe Gitlab::Metrics::RackMiddleware do expect(transaction.action).to eq('Grape#GET /projects/:id/archive') end + + it 'does not tag a transaction if route infos are missing' do + endpoint = double(:endpoint) + allow(endpoint).to receive(:route).and_raise + + env['api.endpoint'] = endpoint + + middleware.tag_endpoint(transaction, env) + + expect(transaction.action).to be_nil + end end end From a532c6040c1c150810122b172a2fad30d1753dfd Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Wed, 14 Dec 2016 00:54:50 +0500 Subject: [PATCH 12/24] Allow to use ENV variables in redis config --- .../unreleased/env-var-in-redis-config.yml | 4 ++++ doc/install/installation.md | 6 ++++++ lib/gitlab/redis.rb | 2 +- spec/fixtures/config/redis_config_with_env.yml | 2 ++ spec/lib/gitlab/redis_spec.rb | 16 +++++++++++++++- 5 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/env-var-in-redis-config.yml create mode 100644 spec/fixtures/config/redis_config_with_env.yml diff --git a/changelogs/unreleased/env-var-in-redis-config.yml b/changelogs/unreleased/env-var-in-redis-config.yml new file mode 100644 index 00000000000..561ea7f514e --- /dev/null +++ b/changelogs/unreleased/env-var-in-redis-config.yml @@ -0,0 +1,4 @@ +--- +title: Allow to use ENV variables in redis config +merge_request: 8073 +author: Semyon Pupkov diff --git a/doc/install/installation.md b/doc/install/installation.md index 9dba03b1924..9cebed34b7e 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -601,6 +601,12 @@ If you want to connect the Redis server via socket, then use the "unix:" URL sch production: url: unix:/path/to/redis/socket +Also you can use environment variables in the `config/resque.yml` file: + + # example + production: + url: <%= ENV.fetch('GITLAB_REDIS_URL') %> + ### Custom SSH Connection If you are running SSH on a non-standard port, you must change the GitLab user's SSH config. diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb index 9226da2d6b1..9384102acec 100644 --- a/lib/gitlab/redis.rb +++ b/lib/gitlab/redis.rb @@ -42,7 +42,7 @@ module Gitlab return @_raw_config if defined?(@_raw_config) begin - @_raw_config = File.read(CONFIG_FILE).freeze + @_raw_config = ERB.new(File.read(CONFIG_FILE)).result.freeze rescue Errno::ENOENT @_raw_config = false end diff --git a/spec/fixtures/config/redis_config_with_env.yml b/spec/fixtures/config/redis_config_with_env.yml new file mode 100644 index 00000000000..f5860f37e47 --- /dev/null +++ b/spec/fixtures/config/redis_config_with_env.yml @@ -0,0 +1,2 @@ +test: + url: <%= ENV['TEST_GITLAB_REDIS_URL'] %> diff --git a/spec/lib/gitlab/redis_spec.rb b/spec/lib/gitlab/redis_spec.rb index e5406fb2d33..917c5c46db1 100644 --- a/spec/lib/gitlab/redis_spec.rb +++ b/spec/lib/gitlab/redis_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Redis do - let(:redis_config) { Rails.root.join('config', 'resque.yml').to_s } + include StubENV before(:each) { clear_raw_config } after(:each) { clear_raw_config } @@ -72,6 +72,20 @@ describe Gitlab::Redis do expect(url2).not_to end_with('foobar') end + + context 'when yml file with env variable' do + let(:redis_config) { Rails.root.join('spec/fixtures/config/redis_config_with_env.yml') } + + before do + stub_env('TEST_GITLAB_REDIS_URL', 'redis://redishost:6379') + end + + it 'reads redis url from env variable' do + stub_const("#{described_class}::CONFIG_FILE", redis_config) + + expect(described_class.url).to eq 'redis://redishost:6379' + end + end end describe '._raw_config' do From 1f546e4d724d77db0a384fb36e17243bf75381f5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 13 Jan 2017 13:07:32 +0100 Subject: [PATCH 13/24] Update commit entity to point to valid commit page --- app/serializers/commit_entity.rb | 8 ++++---- spec/serializers/commit_entity_spec.rb | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb index 49f4db36295..31763955f97 100644 --- a/app/serializers/commit_entity.rb +++ b/app/serializers/commit_entity.rb @@ -8,16 +8,16 @@ class CommitEntity < API::Entities::RepoCommit end expose :commit_url do |commit| - namespace_project_tree_url( + namespace_project_commit_url( request.project.namespace, request.project, - id: commit.id) + commit) end expose :commit_path do |commit| - namespace_project_tree_path( + namespace_project_commit_path( request.project.namespace, request.project, - id: commit.id) + commit) end end diff --git a/spec/serializers/commit_entity_spec.rb b/spec/serializers/commit_entity_spec.rb index a8662e81d20..0333d73b5b5 100644 --- a/spec/serializers/commit_entity_spec.rb +++ b/spec/serializers/commit_entity_spec.rb @@ -33,10 +33,12 @@ describe CommitEntity do it 'contains path to commit' do expect(subject).to include(:commit_path) + expect(subject[:commit_path]).to include "commit/#{commit.id}" end it 'contains URL to commit' do expect(subject).to include(:commit_url) + expect(subject[:commit_path]).to include "commit/#{commit.id}" end it 'needs to receive project in the request' do From 46a5a8e2b1a86452bc65e72eae6a4322a38d2930 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 13 Jan 2017 09:27:16 -0500 Subject: [PATCH 14/24] Fixed spacing of labels in issuable row on milestone#show --- app/assets/stylesheets/pages/milestone.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index e284b7269ce..686b64cdd24 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -109,6 +109,10 @@ .avatar { float: none; } + + > a:not(:last-of-type) { + margin-right: 5px; + } } } From 0433790958e3ce701dd46f697f421f2db69fba10 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 13 Jan 2017 15:34:14 +0100 Subject: [PATCH 15/24] Merge build specs into file that has valid location --- spec/models/build_spec.rb | 1332 ---------------------------------- spec/models/ci/build_spec.rb | 1279 +++++++++++++++++++++++++++++++- 2 files changed, 1276 insertions(+), 1335 deletions(-) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index cd3b6d51545..64c751e791f 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -1,1338 +1,6 @@ require 'spec_helper' describe Ci::Build, models: true do - let(:project) { create(:project) } - let(:pipeline) do - create(:ci_pipeline, project: project, - sha: project.commit.id, - ref: project.default_branch, - status: 'success') - end - let(:build) { create(:ci_build, pipeline: pipeline) } - - it { is_expected.to validate_presence_of :ref } - - it { is_expected.to respond_to :trace_html } - - describe '#first_pending' do - let!(:first) { create(:ci_build, pipeline: pipeline, status: 'pending', created_at: Date.yesterday) } - let!(:second) { create(:ci_build, pipeline: pipeline, status: 'pending') } - subject { Ci::Build.first_pending } - - it { is_expected.to be_a(Ci::Build) } - it('returns with the first pending build') { is_expected.to eq(first) } - end - - describe '#create_from' do - before do - build.status = 'success' - build.save - end - let(:create_from_build) { Ci::Build.create_from build } - - it 'exists a pending task' do - expect(Ci::Build.pending.count(:all)).to eq 0 - create_from_build - expect(Ci::Build.pending.count(:all)).to be > 0 - end - end - - describe '#failed_but_allowed?' do - subject { build.failed_but_allowed? } - - context 'when build is not allowed to fail' do - before do - build.allow_failure = false - end - - context 'and build.status is success' do - before do - build.status = 'success' - end - - it { is_expected.to be_falsey } - end - - context 'and build.status is failed' do - before do - build.status = 'failed' - end - - it { is_expected.to be_falsey } - end - end - - context 'when build is allowed to fail' do - before do - build.allow_failure = true - end - - context 'and build.status is success' do - before do - build.status = 'success' - end - - it { is_expected.to be_falsey } - end - - context 'and build.status is failed' do - before do - build.status = 'failed' - end - - it { is_expected.to be_truthy } - end - end - end - - describe '#persisted_environment' do - before do - @environment = create(:environment, project: project, name: "foo-#{project.default_branch}") - end - - subject { build.persisted_environment } - - context 'referenced literally' do - let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-#{project.default_branch}") } - - it { is_expected.to eq(@environment) } - end - - context 'referenced with a variable' do - let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-$CI_BUILD_REF_NAME") } - - it { is_expected.to eq(@environment) } - end - end - - describe '#trace' do - it { expect(build.trace).to be_nil } - - context 'when build.trace contains text' do - let(:text) { 'example output' } - before do - build.trace = text - end - - it { expect(build.trace).to eq(text) } - end - - context 'when build.trace hides runners token' do - let(:token) { 'my_secret_token' } - - before do - build.update(trace: token) - build.project.update(runners_token: token) - end - - it { expect(build.trace).not_to include(token) } - it { expect(build.raw_trace).to include(token) } - end - - context 'when build.trace hides build token' do - let(:token) { 'my_secret_token' } - - before do - build.update(trace: token) - build.update(token: token) - end - - it { expect(build.trace).not_to include(token) } - it { expect(build.raw_trace).to include(token) } - end - end - - describe '#raw_trace' do - subject { build.raw_trace } - - context 'when build.trace hides runners token' do - let(:token) { 'my_secret_token' } - - before do - build.project.update(runners_token: token) - build.update(trace: token) - end - - it { is_expected.not_to include(token) } - end - - context 'when build.trace hides build token' do - let(:token) { 'my_secret_token' } - - before do - build.update(token: token) - build.update(trace: token) - end - - it { is_expected.not_to include(token) } - end - end - - context '#append_trace' do - subject { build.trace_html } - - context 'when build.trace hides runners token' do - let(:token) { 'my_secret_token' } - - before do - build.project.update(runners_token: token) - build.append_trace(token, 0) - end - - it { is_expected.not_to include(token) } - end - - context 'when build.trace hides build token' do - let(:token) { 'my_secret_token' } - - before do - build.update(token: token) - build.append_trace(token, 0) - end - - it { is_expected.not_to include(token) } - end - end - - # TODO: build timeout - # describe :timeout do - # subject { build.timeout } - # - # it { is_expected.to eq(pipeline.project.timeout) } - # end - - describe '#options' do - let(:options) do - { - image: "ruby:2.1", - services: [ - "postgres" - ] - } - end - - subject { build.options } - it { is_expected.to eq(options) } - end - - # TODO: allow_git_fetch - # describe :allow_git_fetch do - # subject { build.allow_git_fetch } - # - # it { is_expected.to eq(project.allow_git_fetch) } - # end - - describe '#project' do - subject { build.project } - - it { is_expected.to eq(pipeline.project) } - end - - describe '#project_id' do - subject { build.project_id } - - it { is_expected.to eq(pipeline.project_id) } - end - - describe '#project_name' do - subject { build.project_name } - - it { is_expected.to eq(project.name) } - end - - describe '#extract_coverage' do - context 'valid content & regex' do - subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') } - - it { is_expected.to eq(98.29) } - end - - context 'valid content & bad regex' do - subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', 'very covered') } - - it { is_expected.to be_nil } - end - - context 'no coverage content & regex' do - subject { build.extract_coverage('No coverage for today :sad:', '\(\d+.\d+\%\) covered') } - - it { is_expected.to be_nil } - end - - context 'multiple results in content & regex' do - subject { build.extract_coverage(' (98.39%) covered. (98.29%) covered', '\(\d+.\d+\%\) covered') } - - it { is_expected.to eq(98.29) } - end - - context 'using a regex capture' do - subject { build.extract_coverage('TOTAL 9926 3489 65%', 'TOTAL\s+\d+\s+\d+\s+(\d{1,3}\%)') } - - it { is_expected.to eq(65) } - end - end - - describe '#ref_slug' do - { - 'master' => 'master', - '1-foo' => '1-foo', - 'fix/1-foo' => 'fix-1-foo', - 'fix-1-foo' => 'fix-1-foo', - 'a' * 63 => 'a' * 63, - 'a' * 64 => 'a' * 63, - 'FOO' => 'foo', - }.each do |ref, slug| - it "transforms #{ref} to #{slug}" do - build.ref = ref - - expect(build.ref_slug).to eq(slug) - end - end - end - - describe '#variables' do - let(:container_registry_enabled) { false } - let(:predefined_variables) do - [ - { key: 'CI', value: 'true', public: true }, - { key: 'GITLAB_CI', value: 'true', public: true }, - { key: 'CI_BUILD_ID', value: build.id.to_s, public: true }, - { key: 'CI_BUILD_TOKEN', value: build.token, public: false }, - { key: 'CI_BUILD_REF', value: build.sha, public: true }, - { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true }, - { key: 'CI_BUILD_REF_NAME', value: 'master', public: true }, - { key: 'CI_BUILD_REF_SLUG', value: 'master', public: true }, - { key: 'CI_BUILD_NAME', value: 'test', public: true }, - { key: 'CI_BUILD_STAGE', value: 'test', public: true }, - { key: 'CI_SERVER_NAME', value: 'GitLab', public: true }, - { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true }, - { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }, - { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true }, - { key: 'CI_PROJECT_NAME', value: project.path, public: true }, - { key: 'CI_PROJECT_PATH', value: project.path_with_namespace, public: true }, - { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.path, public: true }, - { key: 'CI_PROJECT_URL', value: project.web_url, public: true }, - { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true } - ] - end - - before do - stub_container_registry_config(enabled: container_registry_enabled, host_port: 'registry.example.com') - end - - subject { build.variables } - - context 'returns variables' do - before do - build.yaml_variables = [] - end - - it { is_expected.to eq(predefined_variables) } - end - - context 'when build has user' do - let(:user) { create(:user, username: 'starter') } - let(:user_variables) do - [ - { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, - { key: 'GITLAB_USER_EMAIL', value: user.email, public: true } - ] - end - - before do - build.update_attributes(user: user) - end - - it { user_variables.each { |v| is_expected.to include(v) } } - end - - context 'when build has an environment' do - before do - build.update(environment: 'production') - create(:environment, project: build.project, name: 'production', slug: 'prod-slug') - end - - let(:environment_variables) do - [ - { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true }, - { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true } - ] - end - - it { environment_variables.each { |v| is_expected.to include(v) } } - end - - context 'when build started manually' do - before do - build.update_attributes(when: :manual) - end - - let(:manual_variable) do - { key: 'CI_BUILD_MANUAL', value: 'true', public: true } - end - - it { is_expected.to include(manual_variable) } - end - - context 'when build is for tag' do - let(:tag_variable) do - { key: 'CI_BUILD_TAG', value: 'master', public: true } - end - - before do - build.update_attributes(tag: true) - end - - it { is_expected.to include(tag_variable) } - end - - context 'when secure variable is defined' do - let(:secure_variable) do - { key: 'SECRET_KEY', value: 'secret_value', public: false } - end - - before do - build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value') - end - - it { is_expected.to include(secure_variable) } - end - - context 'when build is for triggers' do - let(:trigger) { create(:ci_trigger, project: project) } - let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) } - let(:user_trigger_variable) do - { key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false } - end - let(:predefined_trigger_variable) do - { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } - end - - before do - build.trigger_request = trigger_request - end - - it { is_expected.to include(user_trigger_variable) } - it { is_expected.to include(predefined_trigger_variable) } - end - - context 'when yaml_variables are undefined' do - before do - build.yaml_variables = nil - end - - context 'use from gitlab-ci.yml' do - before do - stub_ci_pipeline_yaml_file(config) - end - - context 'when config is not found' do - let(:config) { nil } - - it { is_expected.to eq(predefined_variables) } - end - - context 'when config does not have a questioned job' do - let(:config) do - YAML.dump({ - test_other: { - script: 'Hello World' - } - }) - end - - it { is_expected.to eq(predefined_variables) } - end - - context 'when config has variables' do - let(:config) do - YAML.dump({ - test: { - script: 'Hello World', - variables: { - KEY: 'value' - } - } - }) - end - let(:variables) do - [{ key: 'KEY', value: 'value', public: true }] - end - - it { is_expected.to eq(predefined_variables + variables) } - end - end - end - - context 'when container registry is enabled' do - let(:container_registry_enabled) { true } - let(:ci_registry) do - { key: 'CI_REGISTRY', value: 'registry.example.com', public: true } - end - let(:ci_registry_image) do - { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_repository_url, public: true } - end - - context 'and is disabled for project' do - before do - project.update(container_registry_enabled: false) - end - - it { is_expected.to include(ci_registry) } - it { is_expected.not_to include(ci_registry_image) } - end - - context 'and is enabled for project' do - before do - project.update(container_registry_enabled: true) - end - - it { is_expected.to include(ci_registry) } - it { is_expected.to include(ci_registry_image) } - end - end - - context 'when runner is assigned to build' do - let(:runner) { create(:ci_runner, description: 'description', tag_list: ['docker', 'linux']) } - - before do - build.update(runner: runner) - end - - it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) } - it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) } - it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) } - end - - context 'when build is for a deployment' do - let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false } } - - before do - build.environment = 'production' - allow(project).to receive(:deployment_variables).and_return([deployment_variable]) - end - - it { is_expected.to include(deployment_variable) } - end - - context 'returns variables in valid order' do - before do - allow(build).to receive(:predefined_variables) { ['predefined'] } - allow(project).to receive(:predefined_variables) { ['project'] } - allow(pipeline).to receive(:predefined_variables) { ['pipeline'] } - allow(build).to receive(:yaml_variables) { ['yaml'] } - allow(project).to receive(:secret_variables) { ['secret'] } - end - - it { is_expected.to eq(%w[predefined project pipeline yaml secret]) } - end - end - - describe '#has_tags?' do - context 'when build has tags' do - subject { create(:ci_build, tag_list: ['tag']) } - it { is_expected.to have_tags } - end - - context 'when build does not have tags' do - subject { create(:ci_build, tag_list: []) } - it { is_expected.not_to have_tags } - end - end - - describe '#any_runners_online?' do - subject { build.any_runners_online? } - - context 'when no runners' do - it { is_expected.to be_falsey } - end - - context 'when there are runners' do - let(:runner) { create(:ci_runner) } - - before do - build.project.runners << runner - runner.update_attributes(contacted_at: 1.second.ago) - end - - it { is_expected.to be_truthy } - - it 'that is inactive' do - runner.update_attributes(active: false) - is_expected.to be_falsey - end - - it 'that is not online' do - runner.update_attributes(contacted_at: nil) - is_expected.to be_falsey - end - - it 'that cannot handle build' do - expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false) - is_expected.to be_falsey - end - end - end - - describe '#stuck?' do - subject { build.stuck? } - - context "when commit_status.status is pending" do - before do - build.status = 'pending' - end - - it { is_expected.to be_truthy } - - context "and there are specific runner" do - let(:runner) { create(:ci_runner, contacted_at: 1.second.ago) } - - before do - build.project.runners << runner - runner.save - end - - it { is_expected.to be_falsey } - end - end - - %w[success failed canceled running].each do |state| - context "when commit_status.status is #{state}" do - before do - build.status = state - end - - it { is_expected.to be_falsey } - end - end - end - - describe '#artifacts?' do - subject { build.artifacts? } - - context 'artifacts archive does not exist' do - before do - build.update_attributes(artifacts_file: nil) - end - - it { is_expected.to be_falsy } - end - - context 'artifacts archive exists' do - let(:build) { create(:ci_build, :artifacts) } - it { is_expected.to be_truthy } - - context 'is expired' do - before { build.update(artifacts_expire_at: Time.now - 7.days) } - it { is_expected.to be_falsy } - end - - context 'is not expired' do - before { build.update(artifacts_expire_at: Time.now + 7.days) } - it { is_expected.to be_truthy } - end - end - end - - describe '#artifacts_expired?' do - subject { build.artifacts_expired? } - - context 'is expired' do - before { build.update(artifacts_expire_at: Time.now - 7.days) } - - it { is_expected.to be_truthy } - end - - context 'is not expired' do - before { build.update(artifacts_expire_at: Time.now + 7.days) } - - it { is_expected.to be_falsey } - end - end - - describe '#artifacts_metadata?' do - subject { build.artifacts_metadata? } - context 'artifacts metadata does not exist' do - it { is_expected.to be_falsy } - end - - context 'artifacts archive is a zip file and metadata exists' do - let(:build) { create(:ci_build, :artifacts) } - it { is_expected.to be_truthy } - end - end - describe '#repo_url' do - let(:build) { create(:ci_build) } - let(:project) { build.project } - - subject { build.repo_url } - - it { is_expected.to be_a(String) } - it { is_expected.to end_with(".git") } - it { is_expected.to start_with(project.web_url[0..6]) } - it { is_expected.to include(build.token) } - it { is_expected.to include('gitlab-ci-token') } - it { is_expected.to include(project.web_url[7..-1]) } - end - - describe '#artifacts_expire_in' do - subject { build.artifacts_expire_in } - it { is_expected.to be_nil } - - context 'when artifacts_expire_at is specified' do - let(:expire_at) { Time.now + 7.days } - - before { build.artifacts_expire_at = expire_at } - - it { is_expected.to be_within(5).of(expire_at - Time.now) } - end - end - - describe '#artifacts_expire_in=' do - subject { build.artifacts_expire_in } - - it 'when assigning valid duration' do - build.artifacts_expire_in = '7 days' - - is_expected.to be_within(10).of(7.days.to_i) - end - - it 'when assigning invalid duration' do - expect { build.artifacts_expire_in = '7 elephants' }.to raise_error(ChronicDuration::DurationParseError) - is_expected.to be_nil - end - - it 'when resseting value' do - build.artifacts_expire_in = nil - - is_expected.to be_nil - end - end - - describe '#keep_artifacts!' do - let(:build) { create(:ci_build, artifacts_expire_at: Time.now + 7.days) } - - it 'to reset expire_at' do - build.keep_artifacts! - - expect(build.artifacts_expire_at).to be_nil - end - end - - describe '#depends_on_builds' do - let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') } - let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') } - let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, stage: 'test') } - let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, stage: 'deploy') } - - it 'expects to have no dependents if this is first build' do - expect(build.depends_on_builds).to be_empty - end - - it 'expects to have one dependent if this is test' do - expect(rspec_test.depends_on_builds.map(&:id)).to contain_exactly(build.id) - end - - it 'expects to have all builds from build and test stage if this is last' do - expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, rspec_test.id, rubocop_test.id) - end - - it 'expects to have retried builds instead the original ones' do - retried_rspec = Ci::Build.retry(rspec_test) - expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id) - end - end - - def create_mr(build, pipeline, factory: :merge_request, created_at: Time.now) - create(factory, source_project_id: pipeline.gl_project_id, - target_project_id: pipeline.gl_project_id, - source_branch: build.ref, - created_at: created_at) - end - - describe '#merge_request' do - context 'when a MR has a reference to the pipeline' do - before do - @merge_request = create_mr(build, pipeline, factory: :merge_request) - - commits = [double(id: pipeline.sha)] - allow(@merge_request).to receive(:commits).and_return(commits) - allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request]) - end - - it 'returns the single associated MR' do - expect(build.merge_request.id).to eq(@merge_request.id) - end - end - - context 'when there is not a MR referencing the pipeline' do - it 'returns nil' do - expect(build.merge_request).to be_nil - end - end - - context 'when more than one MR have a reference to the pipeline' do - before do - @merge_request = create_mr(build, pipeline, factory: :merge_request) - @merge_request.close! - @merge_request2 = create_mr(build, pipeline, factory: :merge_request) - - commits = [double(id: pipeline.sha)] - allow(@merge_request).to receive(:commits).and_return(commits) - allow(@merge_request2).to receive(:commits).and_return(commits) - allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request, @merge_request2]) - end - - it 'returns the first MR' do - expect(build.merge_request.id).to eq(@merge_request.id) - end - end - - context 'when a Build is created after the MR' do - before do - @merge_request = create_mr(build, pipeline, factory: :merge_request_with_diffs) - pipeline2 = create(:ci_pipeline, project: project) - @build2 = create(:ci_build, pipeline: pipeline2) - - allow(@merge_request).to receive(:commits_sha). - and_return([pipeline.sha, pipeline2.sha]) - allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request]) - end - - it 'returns the current MR' do - expect(@build2.merge_request.id).to eq(@merge_request.id) - end - end - end - - describe 'build erasable' do - shared_examples 'erasable' do - it 'removes artifact file' do - expect(build.artifacts_file.exists?).to be_falsy - end - - it 'removes artifact metadata file' do - expect(build.artifacts_metadata.exists?).to be_falsy - end - - it 'erases build trace in trace file' do - expect(build.trace).to be_empty - end - - it 'sets erased to true' do - expect(build.erased?).to be true - end - - it 'sets erase date' do - expect(build.erased_at).not_to be_falsy - end - end - - context 'build is not erasable' do - let!(:build) { create(:ci_build) } - - describe '#erase' do - subject { build.erase } - - it { is_expected.to be false } - end - - describe '#erasable?' do - subject { build.erasable? } - it { is_expected.to eq false } - end - end - - context 'build is erasable' do - let!(:build) { create(:ci_build, :trace, :success, :artifacts) } - - describe '#erase' do - before do - build.erase(erased_by: user) - end - - context 'erased by user' do - let!(:user) { create(:user, username: 'eraser') } - - include_examples 'erasable' - - it 'records user who erased a build' do - expect(build.erased_by).to eq user - end - end - - context 'erased by system' do - let(:user) { nil } - - include_examples 'erasable' - - it 'does not set user who erased a build' do - expect(build.erased_by).to be_nil - end - end - end - - describe '#erasable?' do - subject { build.erasable? } - it { is_expected.to be_truthy } - end - - describe '#erased?' do - let!(:build) { create(:ci_build, :trace, :success, :artifacts) } - subject { build.erased? } - - context 'build has not been erased' do - it { is_expected.to be_falsey } - end - - context 'build has been erased' do - before do - build.erase - end - - it { is_expected.to be_truthy } - end - end - - context 'metadata and build trace are not available' do - let!(:build) { create(:ci_build, :success, :artifacts) } - - before do - build.remove_artifacts_metadata! - end - - describe '#erase' do - it 'does not raise error' do - expect { build.erase }.not_to raise_error - end - end - end - end - end - - describe '#commit' do - it 'returns commit pipeline has been created for' do - expect(build.commit).to eq project.commit - end - end - - describe '#when' do - subject { build.when } - - context 'when `when` is undefined' do - before do - build.when = nil - end - - context 'use from gitlab-ci.yml' do - before do - stub_ci_pipeline_yaml_file(config) - end - - context 'when config is not found' do - let(:config) { nil } - - it { is_expected.to eq('on_success') } - end - - context 'when config does not have a questioned job' do - let(:config) do - YAML.dump({ - test_other: { - script: 'Hello World' - } - }) - end - - it { is_expected.to eq('on_success') } - end - - context 'when config has `when`' do - let(:config) do - YAML.dump({ - test: { - script: 'Hello World', - when: 'always' - } - }) - end - - it { is_expected.to eq('always') } - end - end - end - end - - describe '#cancelable?' do - subject { build } - - context 'when build is cancelable' do - context 'when build is pending' do - it { is_expected.to be_cancelable } - end - - context 'when build is running' do - before do - build.run! - end - - it { is_expected.to be_cancelable } - end - end - - context 'when build is not cancelable' do - context 'when build is successful' do - before do - build.success! - end - - it { is_expected.not_to be_cancelable } - end - - context 'when build is failed' do - before do - build.drop! - end - - it { is_expected.not_to be_cancelable } - end - end - end - - describe '#retryable?' do - subject { build } - - context 'when build is retryable' do - context 'when build is successful' do - before do - build.success! - end - - it { is_expected.to be_retryable } - end - - context 'when build is failed' do - before do - build.drop! - end - - it { is_expected.to be_retryable } - end - - context 'when build is canceled' do - before do - build.cancel! - end - - it { is_expected.to be_retryable } - end - end - - context 'when build is not retryable' do - context 'when build is running' do - before do - build.run! - end - - it { is_expected.not_to be_retryable } - end - - context 'when build is skipped' do - before do - build.skip! - end - - it { is_expected.not_to be_retryable } - end - end - end - - describe '#manual?' do - before do - build.update(when: value) - end - - subject { build.manual? } - - context 'when is set to manual' do - let(:value) { 'manual' } - - it { is_expected.to be_truthy } - end - - context 'when set to something else' do - let(:value) { 'something else' } - - it { is_expected.to be_falsey } - end - end - - describe '#other_actions' do - let(:build) { create(:ci_build, :manual, pipeline: pipeline) } - let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') } - - subject { build.other_actions } - - it 'returns other actions' do - is_expected.to contain_exactly(other_build) - end - - context 'when build is retried' do - let!(:new_build) { Ci::Build.retry(build) } - - it 'does not return any of them' do - is_expected.not_to include(build, new_build) - end - end - - context 'when other build is retried' do - let!(:retried_build) { Ci::Build.retry(other_build) } - - it 'returns a retried build' do - is_expected.to contain_exactly(retried_build) - end - end - end - - describe '#play' do - let(:build) { create(:ci_build, :manual, pipeline: pipeline) } - - subject { build.play } - - it 'enqueues a build' do - is_expected.to be_pending - is_expected.to eq(build) - end - - context 'for successful build' do - before do - build.update(status: 'success') - end - - it 'creates a new build' do - is_expected.to be_pending - is_expected.not_to eq(build) - end - end - end - - describe '#when' do - subject { build.when } - - context 'when `when` is undefined' do - before do - build.when = nil - end - - context 'use from gitlab-ci.yml' do - before do - stub_ci_pipeline_yaml_file(config) - end - - context 'when config is not found' do - let(:config) { nil } - - it { is_expected.to eq('on_success') } - end - - context 'when config does not have a questioned job' do - let(:config) do - YAML.dump({ - test_other: { - script: 'Hello World' - } - }) - end - - it { is_expected.to eq('on_success') } - end - - context 'when config has when' do - let(:config) do - YAML.dump({ - test: { - script: 'Hello World', - when: 'always' - } - }) - end - - it { is_expected.to eq('always') } - end - end - end - end - - describe '#retryable?' do - context 'when build is running' do - before { build.run! } - - it 'returns false' do - expect(build).not_to be_retryable - end - end - - context 'when build is finished' do - before do - build.success! - end - - it 'returns true' do - expect(build).to be_retryable - end - end - end - - describe '#has_environment?' do - subject { build.has_environment? } - - context 'when environment is defined' do - before do - build.update(environment: 'review') - end - - it { is_expected.to be_truthy } - end - - context 'when environment is not defined' do - before do - build.update(environment: nil) - end - - it { is_expected.to be_falsey } - end - end - - describe '#starts_environment?' do - subject { build.starts_environment? } - - context 'when environment is defined' do - before do - build.update(environment: 'review') - end - - context 'no action is defined' do - it { is_expected.to be_truthy } - end - - context 'and start action is defined' do - before do - build.update(options: { environment: { action: 'start' } } ) - end - - it { is_expected.to be_truthy } - end - end - - context 'when environment is not defined' do - before do - build.update(environment: nil) - end - - it { is_expected.to be_falsey } - end - end - - describe '#stops_environment?' do - subject { build.stops_environment? } - - context 'when environment is defined' do - before do - build.update(environment: 'review') - end - - context 'no action is defined' do - it { is_expected.to be_falsey } - end - - context 'and stop action is defined' do - before do - build.update(options: { environment: { action: 'stop' } } ) - end - - it { is_expected.to be_truthy } - end - end - - context 'when environment is not defined' do - before do - build.update(environment: nil) - end - - it { is_expected.to be_falsey } - end - end - - describe '#last_deployment' do - subject { build.last_deployment } - - context 'when multiple deployments are created' do - let!(:deployment1) { create(:deployment, deployable: build) } - let!(:deployment2) { create(:deployment, deployable: build) } - - it 'returns the latest one' do - is_expected.to eq(deployment2) - end - end - end - - describe '#outdated_deployment?' do - subject { build.outdated_deployment? } - - context 'when build succeeded' do - let(:build) { create(:ci_build, :success) } - let!(:deployment) { create(:deployment, deployable: build) } - - context 'current deployment is latest' do - it { is_expected.to be_falsey } - end - - context 'current deployment is not latest on environment' do - let!(:deployment2) { create(:deployment, environment: deployment.environment) } - - it { is_expected.to be_truthy } - end - end - - context 'when build failed' do - let(:build) { create(:ci_build, :failed) } - - it { is_expected.to be_falsey } - end - end - - describe '#expanded_environment_name' do - subject { build.expanded_environment_name } - - context 'when environment uses $CI_BUILD_REF_NAME' do - let(:build) do - create(:ci_build, - ref: 'master', - environment: 'review/$CI_BUILD_REF_NAME') - end - - it { is_expected.to eq('review/master') } - end - - context 'when environment uses yaml_variables containing symbol keys' do - let(:build) do - create(:ci_build, - yaml_variables: [{ key: :APP_HOST, value: 'host' }], - environment: 'review/$APP_HOST') - end - - it { is_expected.to eq('review/host') } - end - end - - describe '#detailed_status' do - let(:user) { create(:user) } - - it 'returns a detailed status' do - expect(build.detailed_status(user)) - .to be_a Gitlab::Ci::Status::Build::Cancelable - end - end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 7e1d1126b97..9e4c13bde71 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1,14 +1,963 @@ require 'spec_helper' -describe Ci::Build, models: true do - let(:build) { create(:ci_build) } +describe Ci::Build, :models do + let(:project) { create(:project) } + let(:build) { create(:ci_build, pipeline: pipeline) } let(:test_trace) { 'This is a test' } + let(:pipeline) do + create(:ci_pipeline, project: project, + sha: project.commit.id, + ref: project.default_branch, + status: 'success') + end + it { is_expected.to belong_to(:runner) } it { is_expected.to belong_to(:trigger_request) } it { is_expected.to belong_to(:erased_by) } - it { is_expected.to have_many(:deployments) } + it { is_expected.to validate_presence_of :ref } + it { is_expected.to respond_to :trace_html } + + describe '#any_runners_online?' do + subject { build.any_runners_online? } + + context 'when no runners' do + it { is_expected.to be_falsey } + end + + context 'when there are runners' do + let(:runner) { create(:ci_runner) } + + before do + build.project.runners << runner + runner.update_attributes(contacted_at: 1.second.ago) + end + + it { is_expected.to be_truthy } + + it 'that is inactive' do + runner.update_attributes(active: false) + is_expected.to be_falsey + end + + it 'that is not online' do + runner.update_attributes(contacted_at: nil) + is_expected.to be_falsey + end + + it 'that cannot handle build' do + expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false) + is_expected.to be_falsey + end + end + end + + describe '#append_trace' do + subject { build.trace_html } + + context 'when build.trace hides runners token' do + let(:token) { 'my_secret_token' } + + before do + build.project.update(runners_token: token) + build.append_trace(token, 0) + end + + it { is_expected.not_to include(token) } + end + + context 'when build.trace hides build token' do + let(:token) { 'my_secret_token' } + + before do + build.update(token: token) + build.append_trace(token, 0) + end + + it { is_expected.not_to include(token) } + end + end + + describe '#artifacts?' do + subject { build.artifacts? } + + context 'artifacts archive does not exist' do + before do + build.update_attributes(artifacts_file: nil) + end + + it { is_expected.to be_falsy } + end + + context 'artifacts archive exists' do + let(:build) { create(:ci_build, :artifacts) } + it { is_expected.to be_truthy } + + context 'is expired' do + before { build.update(artifacts_expire_at: Time.now - 7.days) } + it { is_expected.to be_falsy } + end + + context 'is not expired' do + before { build.update(artifacts_expire_at: Time.now + 7.days) } + it { is_expected.to be_truthy } + end + end + end + + describe '#artifacts_expired?' do + subject { build.artifacts_expired? } + + context 'is expired' do + before { build.update(artifacts_expire_at: Time.now - 7.days) } + + it { is_expected.to be_truthy } + end + + context 'is not expired' do + before { build.update(artifacts_expire_at: Time.now + 7.days) } + + it { is_expected.to be_falsey } + end + end + + describe '#artifacts_metadata?' do + subject { build.artifacts_metadata? } + context 'artifacts metadata does not exist' do + it { is_expected.to be_falsy } + end + + context 'artifacts archive is a zip file and metadata exists' do + let(:build) { create(:ci_build, :artifacts) } + it { is_expected.to be_truthy } + end + end + + describe '#artifacts_expire_in' do + subject { build.artifacts_expire_in } + it { is_expected.to be_nil } + + context 'when artifacts_expire_at is specified' do + let(:expire_at) { Time.now + 7.days } + + before { build.artifacts_expire_at = expire_at } + + it { is_expected.to be_within(5).of(expire_at - Time.now) } + end + end + + describe '#artifacts_expire_in=' do + subject { build.artifacts_expire_in } + + it 'when assigning valid duration' do + build.artifacts_expire_in = '7 days' + + is_expected.to be_within(10).of(7.days.to_i) + end + + it 'when assigning invalid duration' do + expect { build.artifacts_expire_in = '7 elephants' }.to raise_error(ChronicDuration::DurationParseError) + is_expected.to be_nil + end + + it 'when resseting value' do + build.artifacts_expire_in = nil + + is_expected.to be_nil + end + end + + describe '#commit' do + it 'returns commit pipeline has been created for' do + expect(build.commit).to eq project.commit + end + end + + describe '#create_from' do + before do + build.status = 'success' + build.save + end + let(:create_from_build) { Ci::Build.create_from build } + + it 'exists a pending task' do + expect(Ci::Build.pending.count(:all)).to eq 0 + create_from_build + expect(Ci::Build.pending.count(:all)).to be > 0 + end + end + + describe '#depends_on_builds' do + let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') } + let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') } + let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, stage: 'test') } + let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, stage: 'deploy') } + + it 'expects to have no dependents if this is first build' do + expect(build.depends_on_builds).to be_empty + end + + it 'expects to have one dependent if this is test' do + expect(rspec_test.depends_on_builds.map(&:id)).to contain_exactly(build.id) + end + + it 'expects to have all builds from build and test stage if this is last' do + expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, rspec_test.id, rubocop_test.id) + end + + it 'expects to have retried builds instead the original ones' do + retried_rspec = Ci::Build.retry(rspec_test) + expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id) + end + end + + describe '#detailed_status' do + let(:user) { create(:user) } + + it 'returns a detailed status' do + expect(build.detailed_status(user)) + .to be_a Gitlab::Ci::Status::Build::Cancelable + end + end + + describe 'deployment' do + describe '#last_deployment' do + subject { build.last_deployment } + + context 'when multiple deployments are created' do + let!(:deployment1) { create(:deployment, deployable: build) } + let!(:deployment2) { create(:deployment, deployable: build) } + + it 'returns the latest one' do + is_expected.to eq(deployment2) + end + end + end + + describe '#outdated_deployment?' do + subject { build.outdated_deployment? } + + context 'when build succeeded' do + let(:build) { create(:ci_build, :success) } + let!(:deployment) { create(:deployment, deployable: build) } + + context 'current deployment is latest' do + it { is_expected.to be_falsey } + end + + context 'current deployment is not latest on environment' do + let!(:deployment2) { create(:deployment, environment: deployment.environment) } + + it { is_expected.to be_truthy } + end + end + + context 'when build failed' do + let(:build) { create(:ci_build, :failed) } + + it { is_expected.to be_falsey } + end + end + end + + describe 'environment' do + describe '#has_environment?' do + subject { build.has_environment? } + + context 'when environment is defined' do + before do + build.update(environment: 'review') + end + + it { is_expected.to be_truthy } + end + + context 'when environment is not defined' do + before do + build.update(environment: nil) + end + + it { is_expected.to be_falsey } + end + end + + describe '#expanded_environment_name' do + subject { build.expanded_environment_name } + + context 'when environment uses $CI_BUILD_REF_NAME' do + let(:build) do + create(:ci_build, + ref: 'master', + environment: 'review/$CI_BUILD_REF_NAME') + end + + it { is_expected.to eq('review/master') } + end + + context 'when environment uses yaml_variables containing symbol keys' do + let(:build) do + create(:ci_build, + yaml_variables: [{ key: :APP_HOST, value: 'host' }], + environment: 'review/$APP_HOST') + end + + it { is_expected.to eq('review/host') } + end + end + + describe '#starts_environment?' do + subject { build.starts_environment? } + + context 'when environment is defined' do + before do + build.update(environment: 'review') + end + + context 'no action is defined' do + it { is_expected.to be_truthy } + end + + context 'and start action is defined' do + before do + build.update(options: { environment: { action: 'start' } } ) + end + + it { is_expected.to be_truthy } + end + end + + context 'when environment is not defined' do + before do + build.update(environment: nil) + end + + it { is_expected.to be_falsey } + end + end + + describe '#stops_environment?' do + subject { build.stops_environment? } + + context 'when environment is defined' do + before do + build.update(environment: 'review') + end + + context 'no action is defined' do + it { is_expected.to be_falsey } + end + + context 'and stop action is defined' do + before do + build.update(options: { environment: { action: 'stop' } } ) + end + + it { is_expected.to be_truthy } + end + end + + context 'when environment is not defined' do + before do + build.update(environment: nil) + end + + it { is_expected.to be_falsey } + end + end + end + + describe 'erasable build' do + shared_examples 'erasable' do + it 'removes artifact file' do + expect(build.artifacts_file.exists?).to be_falsy + end + + it 'removes artifact metadata file' do + expect(build.artifacts_metadata.exists?).to be_falsy + end + + it 'erases build trace in trace file' do + expect(build.trace).to be_empty + end + + it 'sets erased to true' do + expect(build.erased?).to be true + end + + it 'sets erase date' do + expect(build.erased_at).not_to be_falsy + end + end + + context 'build is not erasable' do + let!(:build) { create(:ci_build) } + + describe '#erase' do + subject { build.erase } + + it { is_expected.to be false } + end + + describe '#erasable?' do + subject { build.erasable? } + it { is_expected.to eq false } + end + end + + context 'build is erasable' do + let!(:build) { create(:ci_build, :trace, :success, :artifacts) } + + describe '#erase' do + before do + build.erase(erased_by: user) + end + + context 'erased by user' do + let!(:user) { create(:user, username: 'eraser') } + + include_examples 'erasable' + + it 'records user who erased a build' do + expect(build.erased_by).to eq user + end + end + + context 'erased by system' do + let(:user) { nil } + + include_examples 'erasable' + + it 'does not set user who erased a build' do + expect(build.erased_by).to be_nil + end + end + end + + describe '#erasable?' do + subject { build.erasable? } + it { is_expected.to be_truthy } + end + + describe '#erased?' do + let!(:build) { create(:ci_build, :trace, :success, :artifacts) } + subject { build.erased? } + + context 'build has not been erased' do + it { is_expected.to be_falsey } + end + + context 'build has been erased' do + before do + build.erase + end + + it { is_expected.to be_truthy } + end + end + + context 'metadata and build trace are not available' do + let!(:build) { create(:ci_build, :success, :artifacts) } + + before do + build.remove_artifacts_metadata! + end + + describe '#erase' do + it 'does not raise error' do + expect { build.erase }.not_to raise_error + end + end + end + end + end + + describe '#extract_coverage' do + context 'valid content & regex' do + subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') } + + it { is_expected.to eq(98.29) } + end + + context 'valid content & bad regex' do + subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', 'very covered') } + + it { is_expected.to be_nil } + end + + context 'no coverage content & regex' do + subject { build.extract_coverage('No coverage for today :sad:', '\(\d+.\d+\%\) covered') } + + it { is_expected.to be_nil } + end + + context 'multiple results in content & regex' do + subject { build.extract_coverage(' (98.39%) covered. (98.29%) covered', '\(\d+.\d+\%\) covered') } + + it { is_expected.to eq(98.29) } + end + + context 'using a regex capture' do + subject { build.extract_coverage('TOTAL 9926 3489 65%', 'TOTAL\s+\d+\s+\d+\s+(\d{1,3}\%)') } + + it { is_expected.to eq(65) } + end + end + + describe '#first_pending' do + let!(:first) { create(:ci_build, pipeline: pipeline, status: 'pending', created_at: Date.yesterday) } + let!(:second) { create(:ci_build, pipeline: pipeline, status: 'pending') } + subject { Ci::Build.first_pending } + + it { is_expected.to be_a(Ci::Build) } + it('returns with the first pending build') { is_expected.to eq(first) } + end + + describe '#failed_but_allowed?' do + subject { build.failed_but_allowed? } + + context 'when build is not allowed to fail' do + before do + build.allow_failure = false + end + + context 'and build.status is success' do + before do + build.status = 'success' + end + + it { is_expected.to be_falsey } + end + + context 'and build.status is failed' do + before do + build.status = 'failed' + end + + it { is_expected.to be_falsey } + end + end + + context 'when build is allowed to fail' do + before do + build.allow_failure = true + end + + context 'and build.status is success' do + before do + build.status = 'success' + end + + it { is_expected.to be_falsey } + end + + context 'and build.status is failed' do + before do + build.status = 'failed' + end + + it { is_expected.to be_truthy } + end + end + end + + describe 'flags' do + describe '#cancelable?' do + subject { build } + + context 'when build is cancelable' do + context 'when build is pending' do + it { is_expected.to be_cancelable } + end + + context 'when build is running' do + before do + build.run! + end + + it { is_expected.to be_cancelable } + end + end + + context 'when build is not cancelable' do + context 'when build is successful' do + before do + build.success! + end + + it { is_expected.not_to be_cancelable } + end + + context 'when build is failed' do + before do + build.drop! + end + + it { is_expected.not_to be_cancelable } + end + end + end + + describe '#retryable?' do + subject { build } + + context 'when build is retryable' do + context 'when build is successful' do + before do + build.success! + end + + it { is_expected.to be_retryable } + end + + context 'when build is failed' do + before do + build.drop! + end + + it { is_expected.to be_retryable } + end + + context 'when build is canceled' do + before do + build.cancel! + end + + it { is_expected.to be_retryable } + end + end + + context 'when build is not retryable' do + context 'when build is running' do + before do + build.run! + end + + it { is_expected.not_to be_retryable } + end + + context 'when build is skipped' do + before do + build.skip! + end + + it { is_expected.not_to be_retryable } + end + end + end + + describe '#manual?' do + before do + build.update(when: value) + end + + subject { build.manual? } + + context 'when is set to manual' do + let(:value) { 'manual' } + + it { is_expected.to be_truthy } + end + + context 'when set to something else' do + let(:value) { 'something else' } + + it { is_expected.to be_falsey } + end + end + end + + describe '#has_tags?' do + context 'when build has tags' do + subject { create(:ci_build, tag_list: ['tag']) } + it { is_expected.to have_tags } + end + + context 'when build does not have tags' do + subject { create(:ci_build, tag_list: []) } + it { is_expected.not_to have_tags } + end + end + + describe '#keep_artifacts!' do + let(:build) { create(:ci_build, artifacts_expire_at: Time.now + 7.days) } + + it 'to reset expire_at' do + build.keep_artifacts! + + expect(build.artifacts_expire_at).to be_nil + end + end + + describe '#merge_request' do + def create_mr(build, pipeline, factory: :merge_request, created_at: Time.now) + create(factory, source_project_id: pipeline.gl_project_id, + target_project_id: pipeline.gl_project_id, + source_branch: build.ref, + created_at: created_at) + end + + context 'when a MR has a reference to the pipeline' do + before do + @merge_request = create_mr(build, pipeline, factory: :merge_request) + + commits = [double(id: pipeline.sha)] + allow(@merge_request).to receive(:commits).and_return(commits) + allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request]) + end + + it 'returns the single associated MR' do + expect(build.merge_request.id).to eq(@merge_request.id) + end + end + + context 'when there is not a MR referencing the pipeline' do + it 'returns nil' do + expect(build.merge_request).to be_nil + end + end + + context 'when more than one MR have a reference to the pipeline' do + before do + @merge_request = create_mr(build, pipeline, factory: :merge_request) + @merge_request.close! + @merge_request2 = create_mr(build, pipeline, factory: :merge_request) + + commits = [double(id: pipeline.sha)] + allow(@merge_request).to receive(:commits).and_return(commits) + allow(@merge_request2).to receive(:commits).and_return(commits) + allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request, @merge_request2]) + end + + it 'returns the first MR' do + expect(build.merge_request.id).to eq(@merge_request.id) + end + end + + context 'when a Build is created after the MR' do + before do + @merge_request = create_mr(build, pipeline, factory: :merge_request_with_diffs) + pipeline2 = create(:ci_pipeline, project: project) + @build2 = create(:ci_build, pipeline: pipeline2) + + allow(@merge_request).to receive(:commits_sha). + and_return([pipeline.sha, pipeline2.sha]) + allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request]) + end + + it 'returns the current MR' do + expect(@build2.merge_request.id).to eq(@merge_request.id) + end + end + end + + describe '#options' do + let(:options) do + { + image: "ruby:2.1", + services: [ + "postgres" + ] + } + end + + it 'contains options' do + expect(build.options).to eq(options) + end + end + + describe '#other_actions' do + let(:build) { create(:ci_build, :manual, pipeline: pipeline) } + let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') } + + subject { build.other_actions } + + it 'returns other actions' do + is_expected.to contain_exactly(other_build) + end + + context 'when build is retried' do + let!(:new_build) { Ci::Build.retry(build) } + + it 'does not return any of them' do + is_expected.not_to include(build, new_build) + end + end + + context 'when other build is retried' do + let!(:retried_build) { Ci::Build.retry(other_build) } + + it 'returns a retried build' do + is_expected.to contain_exactly(retried_build) + end + end + end + + describe '#persisted_environment' do + before do + @environment = create(:environment, project: project, name: "foo-#{project.default_branch}") + end + + subject { build.persisted_environment } + + context 'referenced literally' do + let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-#{project.default_branch}") } + + it { is_expected.to eq(@environment) } + end + + context 'referenced with a variable' do + let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-$CI_BUILD_REF_NAME") } + + it { is_expected.to eq(@environment) } + end + end + + describe '#play' do + let(:build) { create(:ci_build, :manual, pipeline: pipeline) } + + subject { build.play } + + it 'enqueues a build' do + is_expected.to be_pending + is_expected.to eq(build) + end + + context 'for successful build' do + before do + build.update(status: 'success') + end + + it 'creates a new build' do + is_expected.to be_pending + is_expected.not_to eq(build) + end + end + end + + describe 'project settings' do + describe '#timeout' do + it 'returns project timeout configuration' do + expect(build.timeout).to eq(project.build_timeout) + end + end + + + describe '#allow_git_fetch' do + it 'return project allow_git_fetch configuration' do + expect(build.allow_git_fetch).to eq(project.build_allow_git_fetch) + end + end + end + + describe '#project' do + subject { build.project } + + it { is_expected.to eq(pipeline.project) } + end + + describe '#project_id' do + subject { build.project_id } + + it { is_expected.to eq(pipeline.project_id) } + end + + describe '#project_name' do + subject { build.project_name } + + it { is_expected.to eq(project.name) } + end + + describe '#raw_trace' do + subject { build.raw_trace } + + context 'when build.trace hides runners token' do + let(:token) { 'my_secret_token' } + + before do + build.project.update(runners_token: token) + build.update(trace: token) + end + + it { is_expected.not_to include(token) } + end + + context 'when build.trace hides build token' do + let(:token) { 'my_secret_token' } + + before do + build.update(token: token) + build.update(trace: token) + end + + it { is_expected.not_to include(token) } + end + end + + describe '#ref_slug' do + { + 'master' => 'master', + '1-foo' => '1-foo', + 'fix/1-foo' => 'fix-1-foo', + 'fix-1-foo' => 'fix-1-foo', + 'a' * 63 => 'a' * 63, + 'a' * 64 => 'a' * 63, + 'FOO' => 'foo', + }.each do |ref, slug| + it "transforms #{ref} to #{slug}" do + build.ref = ref + + expect(build.ref_slug).to eq(slug) + end + end + end + + describe '#repo_url' do + let(:build) { create(:ci_build) } + let(:project) { build.project } + + subject { build.repo_url } + + it { is_expected.to be_a(String) } + it { is_expected.to end_with(".git") } + it { is_expected.to start_with(project.web_url[0..6]) } + it { is_expected.to include(build.token) } + it { is_expected.to include('gitlab-ci-token') } + it { is_expected.to include(project.web_url[7..-1]) } + end + + describe '#stuck?' do + subject { build.stuck? } + + context "when commit_status.status is pending" do + before do + build.status = 'pending' + end + + it { is_expected.to be_truthy } + + context "and there are specific runner" do + let(:runner) { create(:ci_runner, contacted_at: 1.second.ago) } + + before do + build.project.runners << runner + runner.save + end + + it { is_expected.to be_falsey } + end + end + + %w[success failed canceled running].each do |state| + context "when commit_status.status is #{state}" do + before do + build.status = state + end + + it { is_expected.to be_falsey } + end + end + end describe '#trace' do it 'obfuscates project runners token' do @@ -24,6 +973,45 @@ describe Ci::Build, models: true do expect(build.trace).to eq(test_trace) end + + context 'when build does not have trace' do + it 'is is empty' do + expect(build.trace).to be_nil + end + end + + context 'when trace contains text' do + let(:text) { 'example output' } + before do + build.trace = text + end + + it { expect(build.trace).to eq(text) } + end + + context 'when trace hides runners token' do + let(:token) { 'my_secret_token' } + + before do + build.update(trace: token) + build.project.update(runners_token: token) + end + + it { expect(build.trace).not_to include(token) } + it { expect(build.raw_trace).to include(token) } + end + + context 'when build.trace hides build token' do + let(:token) { 'my_secret_token' } + + before do + build.update(trace: token) + build.update(token: token) + end + + it { expect(build.trace).not_to include(token) } + it { expect(build.raw_trace).to include(token) } + end end describe '#has_trace_file?' do @@ -111,4 +1099,289 @@ describe Ci::Build, models: true do build.destroy end end + + describe '#when' do + subject { build.when } + + context 'when `when` is undefined' do + before do + build.when = nil + end + + context 'use from gitlab-ci.yml' do + before do + stub_ci_pipeline_yaml_file(config) + end + + context 'when config is not found' do + let(:config) { nil } + + it { is_expected.to eq('on_success') } + end + + context 'when config does not have a questioned job' do + let(:config) do + YAML.dump({ + test_other: { + script: 'Hello World' + } + }) + end + + it { is_expected.to eq('on_success') } + end + + context 'when config has `when`' do + let(:config) do + YAML.dump({ + test: { + script: 'Hello World', + when: 'always' + } + }) + end + + it { is_expected.to eq('always') } + end + end + end + end + + describe '#variables' do + let(:container_registry_enabled) { false } + let(:predefined_variables) do + [ + { key: 'CI', value: 'true', public: true }, + { key: 'GITLAB_CI', value: 'true', public: true }, + { key: 'CI_BUILD_ID', value: build.id.to_s, public: true }, + { key: 'CI_BUILD_TOKEN', value: build.token, public: false }, + { key: 'CI_BUILD_REF', value: build.sha, public: true }, + { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true }, + { key: 'CI_BUILD_REF_NAME', value: 'master', public: true }, + { key: 'CI_BUILD_REF_SLUG', value: 'master', public: true }, + { key: 'CI_BUILD_NAME', value: 'test', public: true }, + { key: 'CI_BUILD_STAGE', value: 'test', public: true }, + { key: 'CI_SERVER_NAME', value: 'GitLab', public: true }, + { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true }, + { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }, + { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true }, + { key: 'CI_PROJECT_NAME', value: project.path, public: true }, + { key: 'CI_PROJECT_PATH', value: project.path_with_namespace, public: true }, + { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.path, public: true }, + { key: 'CI_PROJECT_URL', value: project.web_url, public: true }, + { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true } + ] + end + + before do + stub_container_registry_config(enabled: container_registry_enabled, host_port: 'registry.example.com') + end + + subject { build.variables } + + context 'returns variables' do + before do + build.yaml_variables = [] + end + + it { is_expected.to eq(predefined_variables) } + end + + context 'when build has user' do + let(:user) { create(:user, username: 'starter') } + let(:user_variables) do + [ + { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, + { key: 'GITLAB_USER_EMAIL', value: user.email, public: true } + ] + end + + before do + build.update_attributes(user: user) + end + + it { user_variables.each { |v| is_expected.to include(v) } } + end + + context 'when build has an environment' do + before do + build.update(environment: 'production') + create(:environment, project: build.project, name: 'production', slug: 'prod-slug') + end + + let(:environment_variables) do + [ + { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true }, + { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true } + ] + end + + it { environment_variables.each { |v| is_expected.to include(v) } } + end + + context 'when build started manually' do + before do + build.update_attributes(when: :manual) + end + + let(:manual_variable) do + { key: 'CI_BUILD_MANUAL', value: 'true', public: true } + end + + it { is_expected.to include(manual_variable) } + end + + context 'when build is for tag' do + let(:tag_variable) do + { key: 'CI_BUILD_TAG', value: 'master', public: true } + end + + before do + build.update_attributes(tag: true) + end + + it { is_expected.to include(tag_variable) } + end + + context 'when secure variable is defined' do + let(:secure_variable) do + { key: 'SECRET_KEY', value: 'secret_value', public: false } + end + + before do + build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value') + end + + it { is_expected.to include(secure_variable) } + end + + context 'when build is for triggers' do + let(:trigger) { create(:ci_trigger, project: project) } + let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) } + let(:user_trigger_variable) do + { key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false } + end + let(:predefined_trigger_variable) do + { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } + end + + before do + build.trigger_request = trigger_request + end + + it { is_expected.to include(user_trigger_variable) } + it { is_expected.to include(predefined_trigger_variable) } + end + + context 'when yaml_variables are undefined' do + before do + build.yaml_variables = nil + end + + context 'use from gitlab-ci.yml' do + before do + stub_ci_pipeline_yaml_file(config) + end + + context 'when config is not found' do + let(:config) { nil } + + it { is_expected.to eq(predefined_variables) } + end + + context 'when config does not have a questioned job' do + let(:config) do + YAML.dump({ + test_other: { + script: 'Hello World' + } + }) + end + + it { is_expected.to eq(predefined_variables) } + end + + context 'when config has variables' do + let(:config) do + YAML.dump({ + test: { + script: 'Hello World', + variables: { + KEY: 'value' + } + } + }) + end + let(:variables) do + [{ key: 'KEY', value: 'value', public: true }] + end + + it { is_expected.to eq(predefined_variables + variables) } + end + end + end + + context 'when container registry is enabled' do + let(:container_registry_enabled) { true } + let(:ci_registry) do + { key: 'CI_REGISTRY', value: 'registry.example.com', public: true } + end + let(:ci_registry_image) do + { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_repository_url, public: true } + end + + context 'and is disabled for project' do + before do + project.update(container_registry_enabled: false) + end + + it { is_expected.to include(ci_registry) } + it { is_expected.not_to include(ci_registry_image) } + end + + context 'and is enabled for project' do + before do + project.update(container_registry_enabled: true) + end + + it { is_expected.to include(ci_registry) } + it { is_expected.to include(ci_registry_image) } + end + end + + context 'when runner is assigned to build' do + let(:runner) { create(:ci_runner, description: 'description', tag_list: ['docker', 'linux']) } + + before do + build.update(runner: runner) + end + + it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) } + it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) } + it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) } + end + + context 'when build is for a deployment' do + let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false } } + + before do + build.environment = 'production' + allow(project).to receive(:deployment_variables).and_return([deployment_variable]) + end + + it { is_expected.to include(deployment_variable) } + end + + context 'returns variables in valid order' do + before do + allow(build).to receive(:predefined_variables) { ['predefined'] } + allow(project).to receive(:predefined_variables) { ['project'] } + allow(pipeline).to receive(:predefined_variables) { ['pipeline'] } + allow(build).to receive(:yaml_variables) { ['yaml'] } + allow(project).to receive(:secret_variables) { ['secret'] } + end + + it { is_expected.to eq(%w[predefined project pipeline yaml secret]) } + end + end end From 908443868721e0ea0940086fe39fd433feae2891 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 13 Jan 2017 15:35:33 +0100 Subject: [PATCH 16/24] Remove empty build spec file from invalid location --- spec/models/build_spec.rb | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 spec/models/build_spec.rb diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb deleted file mode 100644 index 64c751e791f..00000000000 --- a/spec/models/build_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -require 'spec_helper' - -describe Ci::Build, models: true do - - -end From c47e1f97fdc1328461e965471f5aab100e337285 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 13 Jan 2017 16:47:00 +0100 Subject: [PATCH 17/24] Add Changelog for commit links fix on pipelines page --- changelogs/unreleased/fix-serialized-commit-path.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/fix-serialized-commit-path.yml diff --git a/changelogs/unreleased/fix-serialized-commit-path.yml b/changelogs/unreleased/fix-serialized-commit-path.yml new file mode 100644 index 00000000000..4e4df503874 --- /dev/null +++ b/changelogs/unreleased/fix-serialized-commit-path.yml @@ -0,0 +1,4 @@ +--- +title: Fix links to commits pages on pipelines list page +merge_request: 8558 +author: From d69ec1269a0fa321d10504e906724f49eaab4a7b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 13 Jan 2017 16:53:00 +0100 Subject: [PATCH 18/24] Fix Rubocop offense in build specs --- spec/models/ci/build_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 9e4c13bde71..af0f6a31eda 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -842,7 +842,6 @@ describe Ci::Build, :models do end end - describe '#allow_git_fetch' do it 'return project allow_git_fetch configuration' do expect(build.allow_git_fetch).to eq(project.build_allow_git_fetch) From 45485947e255c11ca6a45bddf0ba22e9f7dfdea5 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Thu, 22 Dec 2016 20:20:12 +0000 Subject: [PATCH 19/24] Improve disabled state select Add tests --- .../mattermosts/_team_selection.html.haml | 11 +-- .../services/mattermost_slash_command_spec.rb | 85 ++++++++++++++++++- 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml index 24e86b8497f..a80f9aa4c4a 100644 --- a/app/views/projects/mattermosts/_team_selection.html.haml +++ b/app/views/projects/mattermosts/_team_selection.html.haml @@ -7,20 +7,21 @@ %p = @teams.one? ? 'The team' : 'Select the team' where the slash commands will be used in - - selected_id = @teams.keys.first if @teams.one? + - selected_id = @teams.one? ? @teams.keys.first : 0 - options = mattermost_teams_options(@teams) - options = options_for_select(options, selected_id) - = f.select(:team_id, options, {}, { class: 'form-control', selected: "#{selected_id}" }) + = f.select(:team_id, options, {}, { class: 'form-control', disabled: @teams.one?, selected: selected_id }) + = f.hidden_field(:team_id, value: selected_id) if @teams.one? .help-block - if @teams.one? - This is the only team where you are an administrator. + This is the only available team. - else - The list shows teams where you are administrator - To create a team, ask your Mattermost system administrator. + The list shows all available teams. To create a team, = link_to "#{Gitlab.config.mattermost.host}/create_team" do use Mattermost's interface = icon('external-link') + or ask your Mattermost system administrator. %hr %h4 Command trigger word %p Choose the word that will trigger commands diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb index 8de827447ff..86a07b2c679 100644 --- a/spec/features/projects/services/mattermost_slash_command_spec.rb +++ b/spec/features/projects/services/mattermost_slash_command_spec.rb @@ -33,10 +33,89 @@ feature 'Setup Mattermost slash commands', feature: true do expect(value).to eq(token) end - describe 'mattermost service is enabled' do - it 'shows the add to mattermost button' do - expect(page).to have_link 'Add to Mattermost' + it 'shows the add to mattermost button' do + expect(page).to have_link('Add to Mattermost') + end + + it 'shows an explanation if user is a member of no teams' do + stub_teams(count: 0) + + click_link 'Add to Mattermost' + + expect(page).to have_content('You aren’t a member of any team on the Mattermost instance') + expect(page).to have_link('join a team', href: "#{Gitlab.config.mattermost.host}/select_team") + end + + it 'shows an explanation if user is a member of 1 team' do + stub_teams(count: 1) + + click_link 'Add to Mattermost' + + expect(page).to have_content('The team where the slash commands will be used in') + expect(page).to have_content('This is the only available team.') + end + + it 'shows a disabled prefilled select if user is a member of 1 team' do + teams = stub_teams(count: 1) + + click_link 'Add to Mattermost' + + team_name = teams.first[1]['display_name'] + select_element = find('select#mattermost_team_id') + selected_option = select_element.find('option[selected]') + + expect(select_element['disabled']).to be(true) + expect(selected_option).to have_content(team_name.to_s) + end + + it 'has a hidden input for the prefilled value if user is a member of 1 team' do + teams = stub_teams(count: 1) + + click_link 'Add to Mattermost' + + expect(find('input#mattermost_team_id', visible: false).value).to eq(teams.first[0].to_s) + end + + it 'shows an explanation user is a member of multiple teams' do + stub_teams(count: 2) + + click_link 'Add to Mattermost' + + expect(page).to have_content('Select the team where the slash commands will be used in') + expect(page).to have_content('The list shows all available teams.') + end + + it 'shows a select with team options user is a member of multiple teams' do + stub_teams(count: 2) + + click_link 'Add to Mattermost' + + select_element = find('select#mattermost_team_id') + selected_option = select_element.find('option[selected]') + + expect(select_element['disabled']).to be(false) + expect(selected_option).to have_content('Select team...') + # The 'Select team...' placeholder is item `0`. + expect(select_element.all('option').count).to eq(3) + end + + def stub_teams(count: 0) + teams = create_teams(count) + + allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { teams } + + teams + end + + def create_teams(count = 0) + teams = {} + + count.times do |i| + i += 1 + teams[i] = { id: i, display_name: i } end + + teams end describe 'mattermost service is not enabled' do From c5a373c634a0547a204c35fec8fd2c4fe4acbca8 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 13 Jan 2017 12:59:32 -0500 Subject: [PATCH 20/24] fix pagination component handling different header styles from different server proxies --- .../vue_pipelines_index/store.js.es6 | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 6b34839b030..1982142853a 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -3,14 +3,24 @@ /*= require vue_realtime_listener/index.js */ ((gl) => { - const pageValues = headers => ({ - perPage: +headers['X-Per-Page'], - page: +headers['X-Page'], - total: +headers['X-Total'], - totalPages: +headers['X-Total-Pages'], - nextPage: +headers['X-Next-Page'], - previousPage: +headers['X-Prev-Page'], - }); + const pageValues = (headers) => { + const normalizedHeaders = {}; + + Object.keys(headers).forEach((e) => { + normalizedHeaders[e.toUpperCase()] = headers[e]; + }); + + const paginationInfo = { + perPage: +normalizedHeaders['X-PER-PAGE'], + page: +normalizedHeaders['X-PAGE'], + total: +normalizedHeaders['X-TOTAL'], + totalPages: +normalizedHeaders['X-TOTAL-PAGES'], + nextPage: +normalizedHeaders['X-NEXT-PAGE'], + previousPage: +normalizedHeaders['X-PREV-PAGE'], + }; + + return paginationInfo; + }; gl.PipelineStore = class { fetchDataLoop(Vue, pageNum, url, apiScope) { From 0bc48797c8559a1f9c042b74e2faadc820fc7257 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 13 Jan 2017 14:54:25 -0500 Subject: [PATCH 21/24] revise sortable_name test formatting --- spec/models/commit_status_spec.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index daabc804d16..64ea607eb95 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -245,8 +245,6 @@ describe CommitStatus, models: true do end describe '#sortable_name' do - subject { commit_status.sortable_name } - tests = { 'karma' => ['karma'], 'karma 0 20' => ['karma ', 0, ' ', 20], @@ -260,8 +258,7 @@ describe CommitStatus, models: true do tests.each do |name, sortable_name| it "'#{name}' sorts as '#{sortable_name}'" do commit_status.name = name - - is_expected.to eq(sortable_name) + expect(commit_status.sortable_name).to eq(sortable_name) end end end From ebc1d5b39d02ddf3d5a94e968084ca67a59603e2 Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 15 Jan 2017 00:11:12 -0500 Subject: [PATCH 22/24] change how pagination component is loaded --- app/assets/javascripts/vue_pipelines_index/index.js.es6 | 1 + app/views/projects/pipelines/index.html.haml | 1 - config/application.rb | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 9dfbedd73ab..edd01f17a97 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -1,5 +1,6 @@ /* global Vue, VueResource, gl */ /*= require vue_common_component/commit */ +/*= require vue_pagination/index */ /*= require vue-resource /*= require boards/vue_resource_interceptor */ /*= require ./status.js.es6 */ diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index abea6932567..df36279ed75 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -64,5 +64,4 @@ .vue-pipelines-index -= page_specific_javascript_tag('vue_pagination/index.js') = page_specific_javascript_tag('vue_pipelines_index/index.js') diff --git a/config/application.rb b/config/application.rb index aa52b0cd512..8ce549cebf6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -111,7 +111,6 @@ module Gitlab config.assets.precompile << "lib/*.js" config.assets.precompile << "u2f.js" config.assets.precompile << "vue_pipelines_index/index.js" - config.assets.precompile << "vue_pagination/index.js" config.assets.precompile << "vendor/assets/fonts/*" # Version of your assets, change this if you want to expire all your assets From de460f7e4d06bec7bd6138c2b4edc956e9df4471 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Sun, 15 Jan 2017 12:08:29 +0500 Subject: [PATCH 23/24] Use string for class_name option for lazy autoload class Passing constant into this option is deprecated in Rails 5.2 https://github.com/rails/rails/commit/8312a0d22212798864f142b5a94805e0baa6c562 --- app/models/forked_project_link.rb | 4 ++-- app/models/project.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb index 9803bae0bee..36cf7ad6a28 100644 --- a/app/models/forked_project_link.rb +++ b/app/models/forked_project_link.rb @@ -1,4 +1,4 @@ class ForkedProjectLink < ActiveRecord::Base - belongs_to :forked_to_project, class_name: Project - belongs_to :forked_from_project, class_name: Project + belongs_to :forked_to_project, class_name: 'Project' + belongs_to :forked_from_project, class_name: 'Project' end diff --git a/app/models/project.rb b/app/models/project.rb index c22386c84e9..e85d3d3bc6c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -122,7 +122,7 @@ class Project < ActiveRecord::Base # Merge Requests for target project should be removed with it has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id' # Merge requests from source project should be kept when source project was removed - has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest + has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: 'MergeRequest' has_many :issues, dependent: :destroy has_many :labels, dependent: :destroy, class_name: 'ProjectLabel' has_many :services, dependent: :destroy From ad977e8bb257db6ddfae022795a94a4ea95f9be3 Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Sun, 15 Jan 2017 19:48:35 +1100 Subject: [PATCH 24/24] Allow to use + symbol in filenames --- .../unreleased/allow_plus_sign_for_snippets.yml | 4 ++++ lib/gitlab/regex.rb | 4 ++-- spec/features/snippets/create_snippet_spec.rb | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/allow_plus_sign_for_snippets.yml diff --git a/changelogs/unreleased/allow_plus_sign_for_snippets.yml b/changelogs/unreleased/allow_plus_sign_for_snippets.yml new file mode 100644 index 00000000000..62d9dd74d07 --- /dev/null +++ b/changelogs/unreleased/allow_plus_sign_for_snippets.yml @@ -0,0 +1,4 @@ +--- +title: Allow to use + symbol in filenames +merge_request: 6644 +author: blackst0ne diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 9e0b0e5ea98..a3fa7c1331a 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -61,11 +61,11 @@ module Gitlab end def file_name_regex - @file_name_regex ||= /\A[[[:alnum:]]_\-\.\@]*\z/.freeze + @file_name_regex ||= /\A[[[:alnum:]]_\-\.\@\+]*\z/.freeze end def file_name_regex_message - "can contain only letters, digits, '_', '-', '@' and '.'." + "can contain only letters, digits, '_', '-', '@', '+' and '.'." end def file_path_regex diff --git a/spec/features/snippets/create_snippet_spec.rb b/spec/features/snippets/create_snippet_spec.rb index cb95e7828db..5470276bf06 100644 --- a/spec/features/snippets/create_snippet_spec.rb +++ b/spec/features/snippets/create_snippet_spec.rb @@ -17,4 +17,18 @@ feature 'Create Snippet', feature: true do expect(page).to have_content('My Snippet Title') expect(page).to have_content('Hello World!') end + + scenario 'Authenticated user creates a snippet with + in filename' do + fill_in 'personal_snippet_title', with: 'My Snippet Title' + page.within('.file-editor') do + find(:xpath, "//input[@id='personal_snippet_file_name']").set 'snippet+file+name' + find(:xpath, "//input[@id='personal_snippet_content']").set 'Hello World!' + end + + click_button 'Create snippet' + + expect(page).to have_content('My Snippet Title') + expect(page).to have_content('snippet+file+name') + expect(page).to have_content('Hello World!') + end end