Commit 2a62f47b authored by Fatih Acet's avatar Fatih Acet

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into revert-c676283b-existing

parents 1956bd5e c9396bc7
......@@ -453,6 +453,10 @@ Style/VariableName:
EnforcedStyle: snake_case
Enabled: true
# Use the configured style when numbering variables.
Style/VariableNumber:
Enabled: false
# Use when x then ... for one-line cases.
Style/WhenThen:
Enabled: true
......
This diff is collapsed.
Please view this file on the master branch, on stable branches it's out of date.
v 8.13.0 (unreleased)
- Update runner version only when updating contacted_at
- Add link from system note to compare with previous version
- Use gitlab-shell v3.6.2 (GIT TRACE logging)
- Fix centering of custom header logos (Ashley Dumaine)
......@@ -15,12 +16,15 @@ v 8.13.0 (unreleased)
- Simplify Mentionable concern instance methods
- Fix permission for setting an issue's due date
- Expose expires_at field when sharing project on API
- Fix VueJS template tags being rendered in code comments
- Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell)
- Allow the Koding integration to be configured through the API
- Added soft wrap button to repository file/blob editor
- Add word-wrap to issue title on issue and milestone boards (ClemMakesApps)
- Fix todos page mobile viewport layout (ClemMakesApps)
- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
- Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska)
- Fix that manual jobs would no longer block jobs in the next stage. !6604
- Add configurable email subject suffix (Fu Xu)
- Use a ConnectionPool for Rails.cache on Sidekiq servers
- Replace `alias_method_chain` with `Module#prepend`
......@@ -34,6 +38,7 @@ v 8.13.0 (unreleased)
- Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska)
- Fix Long commit messages overflow viewport in file tree
- Revert avoid touching file system on Build#artifacts?
- Stop using a Redis lease when updating the project activity timestamp whenever a new event is created
- Add broadcast messages and alerts below sub-nav
- Better empty state for Groups view
- Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe)
......@@ -43,6 +48,7 @@ v 8.13.0 (unreleased)
- Optimize GitHub importing for speed and memory
- API: expose pipeline data in builds API (!6502, Guilherme Salazar)
- Notify the Merger about merge after successful build (Dimitris Karakasilis)
- Reduce queries needed to find users using their SSH keys when pushing commits
- Fix broken repository 500 errors in project list
- Close todos when accepting merge requests via the API !6486 (tonygambone)
- Changed Slack service user referencing from full name to username (Sebastian Poxhofer)
......@@ -50,6 +56,8 @@ v 8.13.0 (unreleased)
v 8.12.4 (unreleased)
- Fix type mismatch bug when closing Jira issue
- Skip wiki creation when GitHub project has wiki enabled
- Fix failed project deletion when feature visibility set to private
- Fix issues importing services via Import/Export
- Restrict failed login attempts for users with 2FA enabled
- Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. (lukehowell)
......@@ -57,7 +65,7 @@ v 8.12.4 (unreleased)
v 8.12.3
- Update Gitlab Shell to support low IO priority for storage moves
v 8.12.2 (unreleased)
v 8.12.2
- Fix Import/Export not recognising correctly the imported services.
- Fix snippets pagination
- Fix "Create project" button layout when visibility options are restricted
......
......@@ -295,7 +295,7 @@ group :development, :test do
gem 'spring-commands-spinach', '~> 1.1.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.42.0', require: false
gem 'rubocop', '~> 0.43.0', require: false
gem 'rubocop-rspec', '~> 1.5.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
gem 'haml_lint', '~> 0.18.2', require: false
......
......@@ -487,7 +487,7 @@ GEM
orm_adapter (0.5.0)
paranoia (2.1.4)
activerecord (~> 4.0)
parser (2.3.1.2)
parser (2.3.1.4)
ast (~> 2.2)
pg (0.18.4)
pkg-config (1.1.7)
......@@ -620,7 +620,7 @@ GEM
rspec-retry (0.4.5)
rspec-core
rspec-support (3.5.0)
rubocop (0.42.0)
rubocop (0.43.0)
parser (>= 2.3.1.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
......@@ -938,7 +938,7 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.5.0)
rspec-retry (~> 0.4.5)
rubocop (~> 0.42.0)
rubocop (~> 0.43.0)
rubocop-rspec (~> 1.5.0)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2)
......
......@@ -51,6 +51,7 @@
-webkit-flex-direction: column;
flex-direction: column;
margin-left: 10px;
min-width: 55px;
}
.todo-item {
......@@ -120,6 +121,14 @@
}
}
@media (max-width: $screen-sm-max) {
.todos-filters {
.dropdown-menu-toggle {
width: 135px;
}
}
}
@media (max-width: $screen-xs-max) {
.todo {
.avatar {
......@@ -141,4 +150,14 @@
padding-left: 10px;
}
}
.todos-filters {
.row-content-block {
padding-bottom: 50px;
}
.dropdown-menu-toggle {
width: 100%;
}
}
}
......@@ -196,7 +196,7 @@ module Ci
end
def has_warnings?
builds.latest.ignored.any?
builds.latest.failed_but_allowed.any?
end
def config_processor
......
......@@ -2,7 +2,7 @@ module Ci
class Runner < ActiveRecord::Base
extend Ci::Model
LAST_CONTACT_TIME = 2.hours.ago
LAST_CONTACT_TIME = 1.hour.ago
AVAILABLE_SCOPES = %w[specific shared active paused online]
FORM_EDITABLE = %i[description tag_list active run_untagged locked]
......
......@@ -24,7 +24,22 @@ class CommitStatus < ActiveRecord::Base
scope :retried, -> { where.not(id: latest) }
scope :ordered, -> { order(:name) }
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
scope :failed_but_allowed, -> do
where(allow_failure: true, status: [:failed, :canceled])
end
scope :exclude_ignored, -> do
quoted_when = connection.quote_column_name('when')
# We want to ignore failed_but_allowed jobs
where("allow_failure = ? OR status IN (?)",
false, all_state_names - [:failed, :canceled]).
# We want to ignore skipped manual jobs
where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped').
# We want to ignore skipped on_failure
where("#{quoted_when} <> ? OR status <> ?", 'on_failure', 'skipped')
end
scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) }
scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) }
......@@ -111,7 +126,7 @@ class CommitStatus < ActiveRecord::Base
end
end
def ignored?
def failed_but_allowed?
allow_failure? && (failed? || canceled?)
end
......
......@@ -8,32 +8,32 @@ module HasStatus
class_methods do
def status_sql
scope = all
scope = if respond_to?(:exclude_ignored)
exclude_ignored
else
all
end
builds = scope.select('count(*)').to_sql
created = scope.created.select('count(*)').to_sql
success = scope.success.select('count(*)').to_sql
ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored)
ignored ||= '0'
pending = scope.pending.select('count(*)').to_sql
running = scope.running.select('count(*)').to_sql
canceled = scope.canceled.select('count(*)').to_sql
skipped = scope.skipped.select('count(*)').to_sql
canceled = scope.canceled.select('count(*)').to_sql
deduce_status = "(CASE
"(CASE
WHEN (#{builds})=(#{success}) THEN 'success'
WHEN (#{builds})=(#{created}) THEN 'created'
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending'
WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled'
WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running'
ELSE 'failed'
END)"
deduce_status
end
def status
all.pluck(self.status_sql).first
all.pluck(status_sql).first
end
def started_at
......@@ -43,6 +43,10 @@ module HasStatus
def finished_at
all.maximum(:finished_at)
end
def all_state_names
state_machines.values.flat_map(&:states).flat_map { |s| s.map(&:name) }
end
end
included do
......
......@@ -328,13 +328,15 @@ class Event < ActiveRecord::Base
def reset_project_activity
return unless project
# Don't even bother obtaining a lock if the last update happened less than
# 60 minutes ago.
# Don't bother updating if we know the project was updated recently.
return if recent_update?
return unless try_obtain_lease
project.update_column(:last_activity_at, created_at)
# At this point it's possible for multiple threads/processes to try to
# update the project. Only one query should actually perform the update,
# hence we add the extra WHERE clause for last_activity_at.
Project.unscoped.where(id: project_id).
where('last_activity_at > ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago).
update_all(last_activity_at: created_at)
end
private
......@@ -342,11 +344,4 @@ class Event < ActiveRecord::Base
def recent_update?
project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago
end
def try_obtain_lease
Gitlab::ExclusiveLease.
new("project:update_last_activity_at:#{project.id}",
timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i).
try_obtain
end
end
......@@ -20,7 +20,10 @@ class ProjectFeature < ActiveRecord::Base
FEATURES = %i(issues merge_requests wiki snippets builds)
belongs_to :project
# Default scopes force us to unscope here since a service may need to check
# permissions for a project in pending_delete
# http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to
belongs_to :project, -> { unscope(where: :pending_delete) }
default_value_for :builds_access_level, value: ENABLED, allows_nil: false
default_value_for :issues_access_level, value: ENABLED, allows_nil: false
......
......@@ -279,6 +279,11 @@ class User < ActiveRecord::Base
find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i)
end
# Returns a user for the given SSH key.
def find_by_ssh_key_id(key_id)
find_by(id: Key.unscoped.select(:user_id).where(id: key_id))
end
def build_user(attrs = {})
User.new(attrs)
end
......
......@@ -7,6 +7,8 @@ module Projects
def execute
forked_from_project_id = params.delete(:forked_from_project_id)
import_data = params.delete(:import_data)
@skip_wiki = params.delete(:skip_wiki)
@project = Project.new(params)
# Make sure that the user is allowed to use the specified visibility level
......@@ -80,7 +82,7 @@ module Projects
log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
unless @project.gitlab_project_import?
@project.create_wiki if @project.feature_available?(:wiki, current_user)
@project.create_wiki unless skip_wiki?
@project.build_missing_services
@project.create_labels
......@@ -94,6 +96,10 @@ module Projects
end
end
def skip_wiki?
!@project.feature_available?(:wiki, current_user) || @skip_wiki
end
def save_project_and_import_data(import_data)
Project.transaction do
@project.create_or_update_import_data(data: import_data[:data], credentials: import_data[:credentials]) if import_data
......
......@@ -12,7 +12,7 @@ revisions (to reduce disk space and increase performance) and removing
unreachable objects which may have been created from prior invocations of
`git add`.
You can find this option under your **[Project] > Settings**.
You can find this option under your **[Project] > Edit Project**.
---
......
......@@ -221,7 +221,7 @@ time.
*Note: The following commands are run without root privileges. You should be
able to run docker with your regular user account.*
First start with creating a file named `build script`:
First start with creating a file named `build_script`:
```bash
cat <<EOF > build_script
......
......@@ -25,7 +25,7 @@ module Banzai
return if customized?(whitelist[:transformers])
# Allow code highlighting
whitelist[:attributes]['pre'] = %w(class)
whitelist[:attributes]['pre'] = %w(class v-pre)
whitelist[:attributes]['span'] = %w(class)
# Allow table alignment
......
......@@ -30,7 +30,7 @@ module Banzai
# users can still access an issue/comment/etc.
end
highlighted = %(<pre class="#{css_classes}"><code>#{code}</code></pre>)
highlighted = %(<pre class="#{css_classes}" v-pre="true"><code>#{code}</code></pre>)
# Extracted to a method to measure it
replace_parent_pre_element(node, highlighted)
......
......@@ -12,10 +12,9 @@ module Ci
# POST /builds/register
post "register" do
authenticate_runner!
update_runner_last_contact(save: false)
update_runner_info
required_attributes! [:token]
not_found! unless current_runner.active?
update_runner_info
build = Ci::RegisterBuildService.new.execute(current_runner)
......@@ -41,10 +40,11 @@ module Ci
# PUT /builds/:id
put ":id" do
authenticate_runner!
update_runner_last_contact
build = Ci::Build.where(runner_id: current_runner.id).running.find(params[:id])
forbidden!('Build has been erased!') if build.erased?
update_runner_info
build.update_attributes(trace: params[:trace]) if params[:trace]
Gitlab::Metrics.add_event(:update_build,
......
......@@ -3,7 +3,7 @@ module Ci
module Helpers
BUILD_TOKEN_HEADER = "HTTP_BUILD_TOKEN"
BUILD_TOKEN_PARAM = :token
UPDATE_RUNNER_EVERY = 40 * 60
UPDATE_RUNNER_EVERY = 10 * 60
def authenticate_runners!
forbidden! unless runner_registration_token_valid?
......@@ -30,14 +30,22 @@ module Ci
token && (build.valid_token?(token) || build.project.valid_runners_token?(token))
end
def update_runner_last_contact(save: true)
# Use a random threshold to prevent beating DB updates
# it generates a distribution between: [40m, 80m]
def update_runner_info
return unless update_runner?
current_runner.contacted_at = Time.now
current_runner.assign_attributes(get_runner_version_from_params)
current_runner.save if current_runner.changed?
end
def update_runner?
# Use a random threshold to prevent beating DB updates.
# It generates a distribution between [40m, 80m].
#
contacted_at_max_age = UPDATE_RUNNER_EVERY + Random.rand(UPDATE_RUNNER_EVERY)
if current_runner.contacted_at.nil? || Time.now - current_runner.contacted_at >= contacted_at_max_age
current_runner.contacted_at = Time.now
current_runner.save if current_runner.changed? && save
end
current_runner.contacted_at.nil? ||
(Time.now - current_runner.contacted_at) >= contacted_at_max_age
end
def build_not_found!
......@@ -57,11 +65,6 @@ module Ci
attributes_for_keys(["name", "version", "revision", "platform", "architecture"], params["info"])
end
def update_runner_info
current_runner.assign_attributes(get_runner_version_from_params)
current_runner.save if current_runner.changed?
end
def max_artifacts_size
current_application_settings.max_artifacts_size.megabytes.to_i
end
......
......@@ -170,10 +170,9 @@ module Gitlab
end
def import_wiki
unless project.wiki_enabled?
unless project.wiki.repository_exists?
wiki = WikiFormatter.new(project)
gitlab_shell.import_repository(project.repository_storage_path, wiki.path_with_namespace, wiki.import_url)
project.project.update_attribute(:wiki_access_level, ProjectFeature::ENABLED)
end
rescue Gitlab::Shell::Error => e
# GitHub error message when the wiki repo has not been created,
......
module Gitlab
module GithubImport
class ProjectCreator
attr_reader :repo, :namespace, :current_user, :session_data
attr_reader :repo, :name, :namespace, :current_user, :session_data
def initialize(repo, name, namespace, current_user, session_data)
@repo = repo
......@@ -12,24 +12,37 @@ module Gitlab
end
def execute
project = ::Projects::CreateService.new(
::Projects::CreateService.new(
current_user,
name: @name,
path: @name,
name: name,
path: name,
description: repo.description,
namespace_id: namespace.id,
visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility,
visibility_level: visibility_level,
import_type: "github",
import_source: repo.full_name,
import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@")
import_url: import_url,
skip_wiki: skip_wiki
).execute
end
private
# If repo has wiki we'll import it later
if repo.has_wiki? && project
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
end
def import_url
repo.clone_url.sub('https://', "https://#{session_data[:github_access_token]}@")
end
def visibility_level
repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility
end
project
#
# If the GitHub project repository has wiki, we should not create the
# default wiki. Otherwise the GitHub importer will fail because the wiki
# repository already exist.
#
def skip_wiki
repo.has_wiki?
end
end
end
......
......@@ -5,19 +5,61 @@ module Gitlab
def identify(identifier, project, newrev)
if identifier.blank?
# Local push from gitlab
email = project.commit(newrev).author_email rescue nil
User.find_by(email: email) if email
identify_using_commit(project, newrev)
elsif identifier =~ /\Auser-\d+\Z/
# git push over http
user_id = identifier.gsub("user-", "")
User.find_by(id: user_id)
identify_using_user(identifier)
elsif identifier =~ /\Akey-\d+\Z/
# git push over ssh
key_id = identifier.gsub("key-", "")
Key.find_by(id: key_id).try(:user)
identify_using_ssh_key(identifier)
end
end
# Tries to identify a user based on a commit SHA.
def identify_using_commit(project, ref)
commit = project.commit(ref)
return if !commit || !commit.author_email
email = commit.author_email
identify_with_cache(:email, email) do
User.find_by(email: email)
end
end
# Tries to identify a user based on a user identifier (e.g. "user-123").
def identify_using_user(identifier)
user_id = identifier.gsub("user-", "")
identify_with_cache(:user, user_id) do
User.find_by(id: user_id)
end
end
# Tries to identify a user based on an SSH key identifier (e.g. "key-123").
def identify_using_ssh_key(identifier)
key_id = identifier.gsub("key-", "")
identify_with_cache(:ssh_key, key_id) do
User.find_by_ssh_key_id(key_id)
end
end
def identify_with_cache(category, key)
if identification_cache[category].key?(key)
identification_cache[category][key]
else
identification_cache[category][key] = yield
end
end
def identification_cache
@identification_cache ||= {
email: {},
user: {},
ssh_key: {}
}
end
end
end
......@@ -240,6 +240,18 @@ describe 'Comments', feature: true do
is_expected.to have_css('.notes_holder .note', count: 1)
is_expected.to have_button('Reply...')
end
it 'adds code to discussion' do
click_button 'Reply...'
page.within(first('.js-discussion-note-form')) do
fill_in 'note[note]', with: '```{{ test }}```'
click_button('Comment')
end
expect(page).to have_content('{{ test }}')
end
end
end
end
......
......@@ -6,21 +6,21 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
context "when no language is specified" do
it "highlights as plaintext" do
result = filter('<pre><code>def fun end</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>def fun end</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" v-pre="true"><code>def fun end</code></pre>')
end
end
context "when a valid language is specified" do
it "highlights as that language" do
result = filter('<pre><code class="ruby">def fun end</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" v-pre="true"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>')
end
end
context "when an invalid language is specified" do
it "highlights as plaintext" do
result = filter('<pre><code class="gnuplot">This is a test</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>This is a test</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" v-pre="true"><code>This is a test</code></pre>')
end
end
......@@ -31,7 +31,7 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
it "highlights as plaintext" do
result = filter('<pre><code class="ruby">This is a test</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight"><code>This is a test</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight" v-pre="true"><code>This is a test</code></pre>')
end
end
end
......@@ -33,7 +33,7 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do
expect(project.import_data.credentials).to eq(user: 'asdffg', password: nil)
end
context 'when Github project is private' do
context 'when GitHub project is private' do
it 'sets project visibility to private' do
repo.private = true
......@@ -43,7 +43,7 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do
end
end
context 'when Github project is public' do
context 'when GitHub project is public' do
before do
allow_any_instance_of(ApplicationSetting).to receive(:default_project_visibility).and_return(Gitlab::VisibilityLevel::INTERNAL)
end
......@@ -56,5 +56,25 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
end
context 'when GitHub project has wiki' do
it 'does not create the wiki repository' do
allow(repo).to receive(:has_wiki?).and_return(true)
project = service.execute
expect(project.wiki.repository_exists?).to eq false
end
end
context 'when GitHub project does not have wiki' do
it 'creates the wiki repository' do
allow(repo).to receive(:has_wiki?).and_return(false)
project = service.execute
expect(project.wiki.repository_exists?).to eq true
end
end
end
end
require 'spec_helper'
describe Gitlab::Identifier do
let(:identifier) do
Class.new { include Gitlab::Identifier }.new
end
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:key) { create(:key, user: user) }
describe '#identify' do
context 'without an identifier' do
it 'identifies the user using a commit' do
expect(identifier).to receive(:identify_using_commit).
with(project, '123')
identifier.identify('', project, '123')
end
end
context 'with a user identifier' do
it 'identifies the user using a user ID' do
expect(identifier).to receive(:identify_using_user).
with("user-#{user.id}")
identifier.identify("user-#{user.id}", project, '123')
end
end
context 'with an SSH key identifier' do
it 'identifies the user using an SSH key ID' do
expect(identifier).to receive(:identify_using_ssh_key).
with("key-#{key.id}")
identifier.identify("key-#{key.id}", project, '123')
end
end
end
describe '#identify_using_commit' do
it "returns the User for an existing commit author's Email address" do
commit = double(:commit, author_email: user.email)
expect(project).to receive(:commit).with('123').and_return(commit)
expect(identifier.identify_using_commit(project, '123')).to eq(user)
end
it 'returns nil when no user could be found' do
allow(project).to receive(:commit).with('123').and_return(nil)
expect(identifier.identify_using_commit(project, '123')).to be_nil
end
it 'returns nil when the commit does not have an author Email' do
commit = double(:commit, author_email: nil)
expect(project).to receive(:commit).with('123').and_return(commit)
expect(identifier.identify_using_commit(project, '123')).to be_nil
end
it 'caches the found users per Email' do
commit = double(:commit, author_email: user.email)
expect(project).to receive(:commit).with('123').twice.and_return(commit)
expect(User).to receive(:find_by).once.and_call_original
2.times do
expect(identifier.identify_using_commit(project, '123')).to eq(user)
end
end
end
describe '#identify_using_user' do
it 'returns the User for an existing ID in the identifier' do
found = identifier.identify_using_user("user-#{user.id}")
expect(found).to eq(user)
end
it 'returns nil for a non existing user ID' do
found = identifier.identify_using_user('user--1')
expect(found).to be_nil
end
it 'caches the found users per ID' do
expect(User).to receive(:find_by).once.and_call_original
2.times do
found = identifier.identify_using_user("user-#{user.id}")
expect(found).to eq(user)
end
end
end
describe '#identify_using_ssh_key' do
it 'returns the User for an existing SSH key' do
found = identifier.identify_using_ssh_key("key-#{key.id}")
expect(found).to eq(user)
end
it 'returns nil for an invalid SSH key' do
found = identifier.identify_using_ssh_key('key--1')
expect(found).to be_nil
end
it 'caches the found users per key' do
expect(User).to receive(:find_by_ssh_key_id).once.and_call_original
2.times do
found = identifier.identify_using_ssh_key("key-#{key.id}")
expect(found).to eq(user)
end
end
end
end
......@@ -39,8 +39,8 @@ describe Ci::Build, models: true do
end
end
describe '#ignored?' do
subject { build.ignored? }
describe '#failed_but_allowed?' do
subject { build.failed_but_allowed? }
context 'when build is not allowed to fail' do
before do
......
......@@ -7,7 +7,11 @@ describe CommitStatus, models: true do
create(:ci_pipeline, project: project, sha: project.commit.id)
end
let(:commit_status) { create(:commit_status, pipeline: pipeline) }
let(:commit_status) { create_status }
def create_status(args = {})
create(:commit_status, args.merge(pipeline: pipeline))
end
it { is_expected.to belong_to(:pipeline) }
it { is_expected.to belong_to(:user) }
......@@ -125,32 +129,53 @@ describe CommitStatus, models: true do
describe '.latest' do
subject { CommitStatus.latest.order(:id) }
before do
@commit1 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: 'bb', status: 'running'
@commit2 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'cc', ref: 'cc', status: 'pending'
@commit3 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: 'cc', status: 'success'
@commit4 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'cc', ref: 'bb', status: 'success'
@commit5 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: 'bb', status: 'success'
let(:statuses) do
[create_status(name: 'aa', ref: 'bb', status: 'running'),
create_status(name: 'cc', ref: 'cc', status: 'pending'),
create_status(name: 'aa', ref: 'cc', status: 'success'),
create_status(name: 'cc', ref: 'bb', status: 'success'),
create_status(name: 'aa', ref: 'bb', status: 'success')]
end
it 'returns unique statuses' do
is_expected.to eq([@commit4, @commit5])
is_expected.to eq(statuses.values_at(3, 4))
end
end
describe '.running_or_pending' do
subject { CommitStatus.running_or_pending.order(:id) }
before do
@commit1 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: 'bb', status: 'running'
@commit2 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'cc', ref: 'cc', status: 'pending'
@commit3 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: nil, status: 'success'
@commit4 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'dd', ref: nil, status: 'failed'
@commit5 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'ee', ref: nil, status: 'canceled'
let(:statuses) do
[create_status(name: 'aa', ref: 'bb', status: 'running'),
create_status(name: 'cc', ref: 'cc', status: 'pending'),
create_status(name: 'aa', ref: nil, status: 'success'),
create_status(name: 'dd', ref: nil, status: 'failed'),
create_status(name: 'ee', ref: nil, status: 'canceled')]
end
it 'returns statuses that are running or pending' do
is_expected.to eq([@commit1, @commit2])
is_expected.to eq(statuses.values_at(0, 1))
end
end
describe '.exclude_ignored' do
subject { CommitStatus.exclude_ignored.order(:id) }
let(:statuses) do
[create_status(when: 'manual', status: 'skipped'),
create_status(when: 'manual', status: 'success'),
create_status(when: 'manual', status: 'failed'),
create_status(when: 'on_failure', status: 'skipped'),
create_status(when: 'on_failure', status: 'success'),
create_status(when: 'on_failure', status: 'failed'),
create_status(allow_failure: true, status: 'success'),
create_status(allow_failure: true, status: 'failed'),
create_status(allow_failure: false, status: 'success'),
create_status(allow_failure: false, status: 'failed')]
end
it 'returns statuses without what we want to ignore' do
is_expected.to eq(statuses.values_at(1, 2, 4, 5, 6, 8, 9))
end
end
......
require 'spec_helper'
describe HasStatus do
before do
@object = Object.new
@object.extend(HasStatus::ClassMethods)
end
describe '.status' do
before do
allow(@object).to receive(:all).and_return(CommitStatus.where(id: statuses))
end
subject { @object.status }
subject { CommitStatus.status }
shared_examples 'build status summary' do
context 'all successful' do
let(:statuses) { Array.new(2) { create(type, status: :success) } }
let!(:statuses) { Array.new(2) { create(type, status: :success) } }
it { is_expected.to eq 'success' }
end
context 'at least one failed' do
let(:statuses) do
let!(:statuses) do
[create(type, status: :success), create(type, status: :failed)]
end
......@@ -28,7 +19,7 @@ describe HasStatus do
end
context 'at least one running' do
let(:statuses) do
let!(:statuses) do
[create(type, status: :success), create(type, status: :running)]
end
......@@ -36,7 +27,7 @@ describe HasStatus do
end
context 'at least one pending' do
let(:statuses) do
let!(:statuses) do
[create(type, status: :success), create(type, status: :pending)]
end
......@@ -44,7 +35,7 @@ describe HasStatus do
end
context 'success and failed but allowed to fail' do
let(:statuses) do
let!(:statuses) do
[create(type, status: :success),
create(type, status: :failed, allow_failure: true)]
end
......@@ -53,12 +44,15 @@ describe HasStatus do
end
context 'one failed but allowed to fail' do
let(:statuses) { [create(type, status: :failed, allow_failure: true)] }
let!(:statuses) do
[create(type, status: :failed, allow_failure: true)]
end
it { is_expected.to eq 'success' }
end
context 'success and canceled' do
let(:statuses) do
let!(:statuses) do
[create(type, status: :success), create(type, status: :canceled)]
end
......@@ -66,7 +60,7 @@ describe HasStatus do
end
context 'one failed and one canceled' do
let(:statuses) do
let!(:statuses) do
[create(type, status: :failed), create(type, status: :canceled)]
end
......@@ -74,7 +68,7 @@ describe HasStatus do
end
context 'one failed but allowed to fail and one canceled' do
let(:statuses) do
let!(:statuses) do
[create(type, status: :failed, allow_failure: true),
create(type, status: :canceled)]
end
......@@ -83,7 +77,7 @@ describe HasStatus do
end
context 'one running one canceled' do
let(:statuses) do
let!(:statuses) do
[create(type, status: :running), create(type, status: :canceled)]
end
......@@ -91,14 +85,15 @@ describe HasStatus do
end
context 'all canceled' do
let(:statuses) do
let!(:statuses) do
[create(type, status: :canceled), create(type, status: :canceled)]
end
it { is_expected.to eq 'canceled' }
end
context 'success and canceled but allowed to fail' do
let(:statuses) do
let!(:statuses) do
[create(type, status: :success),
create(type, status: :canceled, allow_failure: true)]
end
......@@ -107,7 +102,7 @@ describe HasStatus do
end
context 'one finished and second running but allowed to fail' do
let(:statuses) do
let!(:statuses) do
[create(type, status: :success),
create(type, status: :running, allow_failure: true)]
end
......@@ -118,11 +113,13 @@ describe HasStatus do
context 'ci build statuses' do
let(:type) { :ci_build }
it_behaves_like 'build status summary'
end
context 'generic commit statuses' do
let(:type) { :generic_commit_status }
it_behaves_like 'build status summary'
end
end
......
......@@ -173,13 +173,11 @@ describe Event, models: true do
it 'updates the project' do
project.update(last_activity_at: 1.year.ago)
expect_any_instance_of(Gitlab::ExclusiveLease).
to receive(:try_obtain).and_return(true)
create_event(project, project.owner)
expect(project).to receive(:update_column).
with(:last_activity_at, a_kind_of(Time))
project.reload
create_event(project, project.owner)
project.last_activity_at <= 1.minute.ago
end
end
end
......
......@@ -308,8 +308,7 @@ describe Project, models: true do
end
describe 'last_activity methods' do
let(:timestamp) { Time.now - 2.hours }
let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) }
let(:project) { create(:project, last_activity_at: 2.hours.ago) }
describe 'last_activity' do
it 'alias last_activity to last_event' do
......@@ -321,7 +320,6 @@ describe Project, models: true do
describe 'last_activity_date' do
it 'returns the creation date of the project\'s last event if present' do
expect_any_instance_of(Event).to receive(:try_obtain_lease).and_return(true)
new_event = create(:event, project: project, created_at: Time.now)
expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
......
......@@ -610,6 +610,23 @@ describe User, models: true do
end
end
describe '.find_by_ssh_key_id' do
context 'using an existing SSH key ID' do
let(:user) { create(:user) }
let(:key) { create(:key, user: user) }
it 'returns the corresponding User' do
expect(described_class.find_by_ssh_key_id(key.id)).to eq(user)
end
end
context 'using an invalid SSH key ID' do
it 'returns nil' do
expect(described_class.find_by_ssh_key_id(-1)).to be_nil
end
end
end
describe '.by_login' do
let(:username) { 'John' }
let!(:user) { create(:user, username: username) }
......
......@@ -35,18 +35,24 @@ describe Ci::API::API do
end
end
it "starts a build" do
register_builds info: { platform: :darwin }
expect(response).to have_http_status(201)
expect(json_response['sha']).to eq(build.sha)
expect(runner.reload.platform).to eq("darwin")
expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] })
expect(json_response["variables"]).to include(
{ "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
{ "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true }
)
context 'when there is a pending build' do
it 'starts a build' do
register_builds info: { platform: :darwin }
expect(response).to have_http_status(201)
expect(json_response['sha']).to eq(build.sha)
expect(runner.reload.platform).to eq("darwin")
expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] })
expect(json_response["variables"]).to include(
{ "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
{ "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true }
)
end
it 'updates runner info' do
expect { register_builds }.to change { runner.reload.contacted_at }
end
end
context 'when builds are finished' do
......@@ -159,13 +165,18 @@ describe Ci::API::API do
end
context 'when runner is paused' do
let(:inactive_runner) { create(:ci_runner, :inactive, token: "InactiveRunner") }
let(:runner) { create(:ci_runner, :inactive, token: 'InactiveRunner') }
before do
register_builds inactive_runner.token
it 'responds with 404' do
register_builds
expect(response).to have_http_status 404
end
it { expect(response).to have_http_status 404 }
it 'does not update runner info' do
expect { register_builds }
.not_to change { runner.reload.contacted_at }
end
end
def register_builds(token = runner.token, **params)
......
......@@ -18,7 +18,7 @@ describe Ci::ProcessPipelineService, services: true do
all_builds.where.not(status: [:created, :skipped])
end
def create_builds
def process_pipeline
described_class.new(pipeline.project, user).execute(pipeline)
end
......@@ -36,26 +36,26 @@ describe Ci::ProcessPipelineService, services: true do
end
it 'processes a pipeline' do
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
succeed_pending
expect(builds.success.count).to eq(2)
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
succeed_pending
expect(builds.success.count).to eq(4)
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
succeed_pending
expect(builds.success.count).to eq(5)
expect(create_builds).to be_falsey
expect(process_pipeline).to be_falsey
end
it 'does not process pipeline if existing stage is running' do
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
expect(builds.pending.count).to eq(2)
expect(create_builds).to be_falsey
expect(process_pipeline).to be_falsey
expect(builds.pending.count).to eq(2)
end
end
......@@ -67,7 +67,7 @@ describe Ci::ProcessPipelineService, services: true do
end
it 'automatically triggers a next stage when build finishes' do
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
expect(builds.pluck(:status)).to contain_exactly('pending')
pipeline.builds.running_or_pending.each(&:drop)
......@@ -88,7 +88,7 @@ describe Ci::ProcessPipelineService, services: true do
context 'when builds are successful' do
it 'properly creates builds' do
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
expect(builds.pluck(:name)).to contain_exactly('build')
expect(builds.pluck(:status)).to contain_exactly('pending')
pipeline.builds.running_or_pending.each(&:success)
......@@ -113,7 +113,7 @@ describe Ci::ProcessPipelineService, services: true do
context 'when test job fails' do
it 'properly creates builds' do
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
expect(builds.pluck(:name)).to contain_exactly('build')
expect(builds.pluck(:status)).to contain_exactly('pending')
pipeline.builds.running_or_pending.each(&:success)
......@@ -138,7 +138,7 @@ describe Ci::ProcessPipelineService, services: true do
context 'when test and test_failure jobs fail' do
it 'properly creates builds' do
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
expect(builds.pluck(:name)).to contain_exactly('build')
expect(builds.pluck(:status)).to contain_exactly('pending')
pipeline.builds.running_or_pending.each(&:success)
......@@ -164,7 +164,7 @@ describe Ci::ProcessPipelineService, services: true do
context 'when deploy job fails' do
it 'properly creates builds' do
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
expect(builds.pluck(:name)).to contain_exactly('build')
expect(builds.pluck(:status)).to contain_exactly('pending')
pipeline.builds.running_or_pending.each(&:success)
......@@ -189,7 +189,7 @@ describe Ci::ProcessPipelineService, services: true do
context 'when build is canceled in the second stage' do
it 'does not schedule builds after build has been canceled' do
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
expect(builds.pluck(:name)).to contain_exactly('build')
expect(builds.pluck(:status)).to contain_exactly('pending')
pipeline.builds.running_or_pending.each(&:success)
......@@ -208,7 +208,7 @@ describe Ci::ProcessPipelineService, services: true do
context 'when listing manual actions' do
it 'returns only for skipped builds' do
# currently all builds are created
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
expect(manual_actions).to be_empty
# succeed stage build
......@@ -230,6 +230,69 @@ describe Ci::ProcessPipelineService, services: true do
end
end
context 'when there are manual/on_failure jobs in earlier stages' do
before do
builds
process_pipeline
builds.each(&:reload)
end
context 'when first stage has only manual jobs' do
let(:builds) do
[create_build('build', 0, 'manual'),
create_build('check', 1),
create_build('test', 2)]
end
it 'starts from the second stage' do
expect(builds.map(&:status)).to eq(%w[skipped pending created])
end
end
context 'when second stage has only manual jobs' do
let(:builds) do
[create_build('check', 0),
create_build('build', 1, 'manual'),
create_build('test', 2)]
end
it 'skips second stage and continues on third stage' do
expect(builds.map(&:status)).to eq(%w[pending created created])
builds.first.success
builds.each(&:reload)
expect(builds.map(&:status)).to eq(%w[success skipped pending])
end
end
context 'when second stage has only on_failure jobs' do
let(:builds) do
[create_build('check', 0),
create_build('build', 1, 'on_failure'),
create_build('test', 2)]
end
it 'skips second stage and continues on third stage' do
expect(builds.map(&:status)).to eq(%w[pending created created])
builds.first.success
builds.each(&:reload)
expect(builds.map(&:status)).to eq(%w[success skipped pending])
end
end
def create_build(name, stage_idx, when_value = nil)
create(:ci_build,
:created,
pipeline: pipeline,
name: name,
stage_idx: stage_idx,
when: when_value)
end
end
context 'when failed build in the middle stage is retried' do
context 'when failed build is the only unsuccessful build in the stage' do
before do
......@@ -242,7 +305,7 @@ describe Ci::ProcessPipelineService, services: true do
end
it 'does trigger builds in the next stage' do
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
expect(builds.pluck(:name)).to contain_exactly('build:1', 'build:2')
pipeline.builds.running_or_pending.each(&:success)
......@@ -297,14 +360,14 @@ describe Ci::ProcessPipelineService, services: true do
expect(all_builds.count).to eq(2)
# Create builds will mark the created as pending
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
expect(builds.count).to eq(2)
expect(all_builds.count).to eq(2)
# When we builds succeed we will create a rest of pipeline from .gitlab-ci.yml
# We will have 2 succeeded, 2 pending (from stage test), total 5 (one more build from deploy)
succeed_pending
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
expect(builds.success.count).to eq(2)
expect(builds.pending.count).to eq(2)
expect(all_builds.count).to eq(5)
......@@ -312,14 +375,14 @@ describe Ci::ProcessPipelineService, services: true do
# When we succeed the 2 pending from stage test,
# We will queue a deploy stage, no new builds will be created
succeed_pending
expect(create_builds).to be_truthy
expect(process_pipeline).to be_truthy
expect(builds.pending.count).to eq(1)
expect(builds.success.count).to eq(4)
expect(all_builds.count).to eq(5)
# When we succeed last pending build, we will have a total of 5 succeeded builds, no new builds will be created
succeed_pending
expect(create_builds).to be_falsey
expect(process_pipeline).to be_falsey
expect(builds.success.count).to eq(5)
expect(all_builds.count).to eq(5)
end
......
......@@ -5,6 +5,7 @@ describe Projects::DestroyService, services: true do
let!(:project) { create(:project, namespace: user.namespace) }
let!(:path) { project.repository.path_to_repo }
let!(:remove_path) { path.sub(/\.git\Z/, "+#{project.id}+deleted.git") }
let!(:async) { false } # execute or async_execute
context 'Sidekiq inline' do
before do
......@@ -28,6 +29,22 @@ describe Projects::DestroyService, services: true do
it { expect(Dir.exist?(remove_path)).to be_truthy }
end
context 'async delete of project with private issue visibility' do
let!(:async) { true }
before do
project.project_feature.update_attribute("issues_access_level", ProjectFeature::PRIVATE)
# Run sidekiq immediately to check that renamed repository will be removed
Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
end
it 'deletes the project' do
expect(Project.all).not_to include(project)
expect(Dir.exist?(path)).to be_falsey
expect(Dir.exist?(remove_path)).to be_falsey
end
end
context 'container registry' do
before do
stub_container_registry_config(enabled: true)
......@@ -52,6 +69,10 @@ describe Projects::DestroyService, services: true do
end
def destroy_project(project, user, params)
Projects::DestroyService.new(project, user, params).execute
if async
Projects::DestroyService.new(project, user, params).async_execute
else
Projects::DestroyService.new(project, user, params).execute
end
end
end
......@@ -79,7 +79,9 @@ describe PostReceive do
end
it "does not run if the author is not in the project" do
allow(Key).to receive(:find_by).with(hash_including(id: anything())) { nil }
allow_any_instance_of(Gitlab::GitPostReceive).
to receive(:identify_using_ssh_key).
and_return(nil)
expect(project).not_to receive(:execute_hooks)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment