summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFatih Acet <acetfatih@gmail.com>2016-10-05 14:42:41 +0300
committerFatih Acet <acetfatih@gmail.com>2016-10-05 14:42:41 +0300
commit2a62f47bcd82ee692c2b19e70ed702a74bf1151a (patch)
tree77296047c251f6597274a4f792f52984fd99e8c1
parent1956bd5e9e44adcdf24ceaa45fddb7d02ba79b92 (diff)
parentc9396bc7c5b7b55fd01ec63279f5ab4223c0a184 (diff)
downloadgitlab-ce-2a62f47bcd82ee692c2b19e70ed702a74bf1151a.tar.gz
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into revert-c676283b-existing
-rw-r--r--.rubocop.yml4
-rw-r--r--.rubocop_todo.yml171
-rw-r--r--CHANGELOG10
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/stylesheets/pages/todos.scss19
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/ci/runner.rb2
-rw-r--r--app/models/commit_status.rb19
-rw-r--r--app/models/concerns/has_status.rb28
-rw-r--r--app/models/event.rb19
-rw-r--r--app/models/project_feature.rb5
-rw-r--r--app/models/user.rb5
-rw-r--r--app/services/projects/create_service.rb8
-rw-r--r--doc/administration/housekeeping.md2
-rw-r--r--doc/administration/img/housekeeping_settings.pngbin19347 -> 27420 bytes
-rw-r--r--doc/ci/docker/using_docker_images.md2
-rw-r--r--lib/banzai/filter/sanitization_filter.rb2
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb2
-rw-r--r--lib/ci/api/builds.rb6
-rw-r--r--lib/ci/api/helpers.rb29
-rw-r--r--lib/gitlab/github_import/importer.rb3
-rw-r--r--lib/gitlab/github_import/project_creator.rb35
-rw-r--r--lib/gitlab/identifier.rb58
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb12
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb8
-rw-r--r--spec/lib/gitlab/github_import/project_creator_spec.rb24
-rw-r--r--spec/lib/gitlab/identifier_spec.rb123
-rw-r--r--spec/models/build_spec.rb4
-rw-r--r--spec/models/commit_status_spec.rb55
-rw-r--r--spec/models/concerns/has_status_spec.rb43
-rw-r--r--spec/models/event_spec.rb8
-rw-r--r--spec/models/project_spec.rb4
-rw-r--r--spec/models/user_spec.rb17
-rw-r--r--spec/requests/ci/api/builds_spec.rb43
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb101
-rw-r--r--spec/services/projects/destroy_service_spec.rb23
-rw-r--r--spec/workers/post_receive_spec.rb4
38 files changed, 690 insertions, 218 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index c84755859cb..bec2464c740 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -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
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index b6ed6e33397..11b34fafa2a 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1,21 +1,21 @@
# This configuration was generated by
# `rubocop --auto-gen-config --exclude-limit 0`
-# on 2016-09-14 15:44:53 -0400 using RuboCop version 0.42.0.
+# on 2016-10-04 13:16:20 +0200 using RuboCop version 0.43.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
-# Offense count: 158
+# Offense count: 160
Lint/AmbiguousRegexpLiteral:
Enabled: false
-# Offense count: 41
+# Offense count: 40
# Configuration parameters: AllowSafeAssignment.
Lint/AssignmentInCondition:
Enabled: false
-# Offense count: 16
+# Offense count: 18
Lint/HandleExceptions:
Enabled: false
@@ -23,11 +23,21 @@ Lint/HandleExceptions:
Lint/Loop:
Enabled: false
-# Offense count: 16
+# Offense count: 19
Lint/ShadowingOuterLocalVariable:
Enabled: false
-# Offense count: 49
+# Offense count: 9
+# Cop supports --auto-correct.
+Lint/UnifiedInteger:
+ Enabled: false
+
+# Offense count: 13
+# Cop supports --auto-correct.
+Lint/UnneededSplatExpansion:
+ Enabled: false
+
+# Offense count: 69
# Cop supports --auto-correct.
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
Lint/UnusedBlockArgument:
@@ -39,32 +49,81 @@ Lint/UnusedBlockArgument:
Lint/UnusedMethodArgument:
Enabled: false
-# Offense count: 9
-# Cop supports --auto-correct.
-Performance/PushSplat:
- Enabled: false
-
# Offense count: 2
# Cop supports --auto-correct.
Performance/RedundantBlockCall:
Enabled: false
-# Offense count: 4
+# Offense count: 5
# Cop supports --auto-correct.
Performance/RedundantMatch:
Enabled: false
-# Offense count: 27
+# Offense count: 26
# Cop supports --auto-correct.
# Configuration parameters: MaxKeyValuePairs.
Performance/RedundantMerge:
Enabled: false
-# Offense count: 61
+# Offense count: 7
+RSpec/BeEql:
+ Enabled: false
+
+# Offense count: 20
+# Configuration parameters: CustomIncludeMethods.
+RSpec/EmptyExampleGroup:
+ Enabled: false
+
+# Offense count: 16
+RSpec/ExpectActual:
+ Enabled: false
+
+# Offense count: 34
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: implicit, each, example
+RSpec/HookArgument:
+ Enabled: false
+
+# Offense count: 168
+RSpec/LeadingSubject:
+ Enabled: false
+
+# Offense count: 162
+RSpec/LetSetup:
+ Enabled: false
+
+# Offense count: 10
+RSpec/MessageChain:
+ Enabled: false
+
+# Offense count: 714
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: allow, expect
+RSpec/MessageExpectation:
+ Enabled: false
+
+# Offense count: 2423
+RSpec/MultipleExpectations:
+ Max: 36
+
+# Offense count: 1504
+RSpec/NamedSubject:
+ Enabled: false
+
+# Offense count: 1335
+# Configuration parameters: MaxNesting.
+RSpec/NestedGroups:
+ Enabled: false
+
+# Offense count: 99
+RSpec/SubjectStub:
+ Enabled: false
+
+# Offense count: 64
Rails/OutputSafety:
Enabled: false
-# Offense count: 129
+# Offense count: 151
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: strict, flexible
Rails/TimeZone:
@@ -77,58 +136,63 @@ Rails/TimeZone:
Rails/Validation:
Enabled: false
-# Offense count: 273
+# Offense count: 2
+# Cop supports --auto-correct.
+Security/JSONLoad:
+ Enabled: false
+
+# Offense count: 284
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
# SupportedStyles: with_first_parameter, with_fixed_indentation
Style/AlignParameters:
Enabled: false
-# Offense count: 30
+# Offense count: 28
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: always, conditionals
Style/AndOr:
Enabled: false
-# Offense count: 50
+# Offense count: 52
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: percent_q, bare_percent
Style/BarePercentLiterals:
Enabled: false
-# Offense count: 289
+# Offense count: 291
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: braces, no_braces, context_dependent
Style/BracesAroundHashParameters:
Enabled: false
-# Offense count: 5
+# Offense count: 6
Style/CaseEquality:
Enabled: false
-# Offense count: 19
+# Offense count: 26
# Cop supports --auto-correct.
Style/ColonMethodCall:
Enabled: false
-# Offense count: 3
+# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: Keywords.
# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW
Style/CommentAnnotation:
Enabled: false
-# Offense count: 33
+# Offense count: 30
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly.
# SupportedStyles: assign_to_condition, assign_inside_condition
Style/ConditionalAssignment:
Enabled: false
-# Offense count: 881
+# Offense count: 957
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: leading, trailing
@@ -139,12 +203,12 @@ Style/DotPosition:
Style/DoubleNegation:
Enabled: false
-# Offense count: 4
+# Offense count: 6
# Cop supports --auto-correct.
Style/EachWithObject:
Enabled: false
-# Offense count: 25
+# Offense count: 26
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: empty, nil, both
@@ -156,24 +220,24 @@ Style/EmptyElse:
Style/EmptyLiteral:
Enabled: false
-# Offense count: 135
+# Offense count: 140
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
Style/ExtraSpacing:
Enabled: false
-# Offense count: 7
+# Offense count: 6
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: format, sprintf, percent
Style/FormatString:
Enabled: false
-# Offense count: 51
+# Offense count: 201
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Enabled: false
-# Offense count: 9
+# Offense count: 11
Style/IfInsideElse:
Enabled: false
@@ -183,21 +247,21 @@ Style/IfInsideElse:
Style/IfUnlessModifier:
Enabled: false
-# Offense count: 52
+# Offense count: 53
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_brackets
Style/IndentArray:
Enabled: false
-# Offense count: 97
+# Offense count: 95
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_braces
Style/IndentHash:
Enabled: false
-# Offense count: 12
+# Offense count: 29
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: line_count_dependent, lambda, literal
@@ -209,7 +273,7 @@ Style/Lambda:
Style/LineEndConcatenation:
Enabled: false
-# Offense count: 13
+# Offense count: 15
# Cop supports --auto-correct.
Style/MethodCallParentheses:
Enabled: false
@@ -218,7 +282,7 @@ Style/MethodCallParentheses:
Style/MethodMissing:
Enabled: false
-# Offense count: 85
+# Offense count: 95
# Cop supports --auto-correct.
Style/MutableConstant:
Enabled: false
@@ -235,14 +299,14 @@ Style/NestedParenthesizedCalls:
Style/Next:
Enabled: false
-# Offense count: 8
+# Offense count: 12
# Cop supports --auto-correct.
# Configuration parameters: EnforcedOctalStyle, SupportedOctalStyles.
# SupportedOctalStyles: zero_with_o, zero_only
Style/NumericLiteralPrefix:
Enabled: false
-# Offense count: 64
+# Offense count: 53
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: predicate, comparison
@@ -254,7 +318,7 @@ Style/NumericPredicate:
Style/ParallelAssignment:
Enabled: false
-# Offense count: 264
+# Offense count: 294
# Cop supports --auto-correct.
# Configuration parameters: PreferredDelimiters.
Style/PercentLiteralDelimiters:
@@ -272,7 +336,7 @@ Style/PercentQLiterals:
Style/PerlBackrefs:
Enabled: false
-# Offense count: 35
+# Offense count: 38
# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
# NamePrefix: is_, has_, have_
# NamePrefixBlacklist: is_, has_, have_
@@ -280,7 +344,7 @@ Style/PerlBackrefs:
Style/PredicateName:
Enabled: false
-# Offense count: 27
+# Offense count: 26
# Cop supports --auto-correct.
Style/PreferredHashMethods:
Enabled: false
@@ -312,12 +376,12 @@ Style/RedundantException:
Style/RedundantFreeze:
Enabled: false
-# Offense count: 408
+# Offense count: 427
# Cop supports --auto-correct.
Style/RedundantSelf:
Enabled: false
-# Offense count: 93
+# Offense count: 97
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
@@ -329,7 +393,12 @@ Style/RegexpLiteral:
Style/RescueModifier:
Enabled: false
-# Offense count: 5
+# Offense count: 114
+# Cop supports --auto-correct.
+Style/SafeNavigation:
+ Enabled: false
+
+# Offense count: 7
# Cop supports --auto-correct.
Style/SelfAssignment:
Enabled: false
@@ -346,7 +415,7 @@ Style/SingleLineBlockParams:
Style/SingleLineMethods:
Enabled: false
-# Offense count: 124
+# Offense count: 125
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: space, no_space
@@ -359,19 +428,19 @@ Style/SpaceBeforeBlockBraces:
Style/SpaceBeforeFirstArg:
Enabled: false
-# Offense count: 141
+# Offense count: 145
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
# SupportedStyles: space, no_space
Style/SpaceInsideBlockBraces:
Enabled: false
-# Offense count: 96
+# Offense count: 99
# Cop supports --auto-correct.
Style/SpaceInsideBrackets:
Enabled: false
-# Offense count: 62
+# Offense count: 65
# Cop supports --auto-correct.
Style/SpaceInsideParens:
Enabled: false
@@ -381,21 +450,21 @@ Style/SpaceInsideParens:
Style/SpaceInsidePercentLiteralDelimiters:
Enabled: false
-# Offense count: 40
+# Offense count: 41
# Cop supports --auto-correct.
# Configuration parameters: SupportedStyles.
# SupportedStyles: use_perl_names, use_english_names
Style/SpecialGlobalVars:
EnforcedStyle: use_perl_names
-# Offense count: 30
+# Offense count: 31
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiteralsInInterpolation:
Enabled: false
-# Offense count: 32
+# Offense count: 33
# Cop supports --auto-correct.
# Configuration parameters: IgnoredMethods.
# IgnoredMethods: respond_to, define_method
@@ -409,7 +478,7 @@ Style/SymbolProc:
Style/TernaryParentheses:
Enabled: false
-# Offense count: 24
+# Offense count: 29
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
# SupportedStyles: comma, consistent_comma, no_comma
diff --git a/CHANGELOG b/CHANGELOG
index dd3b15f513b..d424fbe43b8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
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
diff --git a/Gemfile b/Gemfile
index d54beedf67e..18654a1e88c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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
diff --git a/Gemfile.lock b/Gemfile.lock
index 6f8a8236866..3f756fec929 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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)
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 68a5d1ae06c..ea76fe18876 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -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%;
+ }
+ }
}
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 663c5b1e231..97df74b0cfe 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -196,7 +196,7 @@ module Ci
end
def has_warnings?
- builds.latest.ignored.any?
+ builds.latest.failed_but_allowed.any?
end
def config_processor
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index ed5d4b13b7e..44cb19ece3b 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -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]
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 736db1ab0f6..ee3396abe04 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -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
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 0fa4df0fb56..9f64f76721d 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -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
diff --git a/app/models/event.rb b/app/models/event.rb
index 55a76e26f3c..633019fe0af 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -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
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 8c9534c3565..530f7d5a30e 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -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
diff --git a/app/models/user.rb b/app/models/user.rb
index 7f5a8562907..508efd85050 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -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
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index be749ba4a1c..76266139d09 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -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
diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md
index 34b4f1faa94..ad1fa98b63b 100644
--- a/doc/administration/housekeeping.md
+++ b/doc/administration/housekeeping.md
@@ -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**.
---
diff --git a/doc/administration/img/housekeeping_settings.png b/doc/administration/img/housekeeping_settings.png
index f72ad9a45d5..6ebc6205635 100644
--- a/doc/administration/img/housekeeping_settings.png
+++ b/doc/administration/img/housekeeping_settings.png
Binary files differ
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index a849905ac6b..520c8b36a95 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -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
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 2470362e019..af1e575fc89 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -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
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index fcdb496aed2..026b81ac175 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -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)
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 59f85416ee5..ed87a2603e8 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -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,
diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index 23353c62885..e608f5f6cad 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -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
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index b8321244473..4b70f33a851 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -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,
diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb
index 605abfabdab..a2410068845 100644
--- a/lib/gitlab/github_import/project_creator.rb
+++ b/lib/gitlab/github_import/project_creator.rb
@@ -1,7 +1,7 @@
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
diff --git a/lib/gitlab/identifier.rb b/lib/gitlab/identifier.rb
index 3e5d728f3bc..f8809db21aa 100644
--- a/lib/gitlab/identifier.rb
+++ b/lib/gitlab/identifier.rb
@@ -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
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index f1c522155d3..5d7247e2a62 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -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
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index b1370bca833..d265d29ee86 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb
index ab06b7bc5bb..a73b1f4ff5d 100644
--- a/spec/lib/gitlab/github_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/github_import/project_creator_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/identifier_spec.rb b/spec/lib/gitlab/identifier_spec.rb
new file mode 100644
index 00000000000..47d6f1007d1
--- /dev/null
+++ b/spec/lib/gitlab/identifier_spec.rb
@@ -0,0 +1,123 @@
+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
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index e7864b7ad33..ae185de9ca3 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -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
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 2f1baff5d66..80c2a1bc7a9 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -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
diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb
index e118432d098..87bffbdc54e 100644
--- a/spec/models/concerns/has_status_spec.rb
+++ b/spec/models/concerns/has_status_spec.rb
@@ -1,26 +1,17 @@
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
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 8600eb4d2c4..af5002487cc 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -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
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index ef854a25321..3ab5ac78bba 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -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)
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index a1770d96f83..65b2896930a 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -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) }
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index df97f1bf7b6..7b7d62feb2c 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -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)
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index 8326e5cd313..ff113efd916 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -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
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 29341c5e57e..7dcd03496bb 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -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
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 1d2cf7acddd..ffeaafe654a 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -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)