Commit 86b41da8 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ce-to-ee-2017-09-21' into 'master'

CE upstream: Thursday

Closes gitaly#597, gitlab-ce#36663, gitlab-ce#38129 et gitlab-ce#38061

See merge request gitlab-org/gitlab-ee!2981
parents 92f5a5fd 0f4a6d0f
...@@ -4,18 +4,19 @@ entry. ...@@ -4,18 +4,19 @@ entry.
## 9.5.5 (2017-09-18) ## 9.5.5 (2017-09-18)
- [SECURITY] Upgrade mail and nokogiri gems due to security issues. !13662 (Markus Koller)
- [FIXED] Fix division by zero error in blame age mapping. !13803 (Jeff Stubler) - [FIXED] Fix division by zero error in blame age mapping. !13803 (Jeff Stubler)
- [FIXED] Fix problems sanitizing URLs with empty passwords. !14083 - [FIXED] Fix problems sanitizing URLs with empty passwords. !14083
- [FIXED] Fix a wrong `X-Gitlab-Event` header when testing webhooks. !14108 - [FIXED] Fix a wrong `X-Gitlab-Event` header when testing webhooks. !14108
- [FIXED] Fixes the 500 errors caused by a race condition in GPG's tmp directory handling. !14194 (Alexis Reigel) - [FIXED] Fixes the 500 errors caused by a race condition in GPG's tmp directory handling. !14194 (Alexis Reigel)
- [FIXED] Fix Pipeline Triggers to show triggered label and predefined variables (e.g. CI_PIPELINE_TRIGGERED). !14244 - [FIXED] Fix Pipeline Triggers to show triggered label and predefined variables (e.g. CI_PIPELINE_TRIGGERED). !14244
- [FIXED] Disable GitLab Project Import Button if source disabled.
- [FIXED] Fix project feature being deleted when updating project with invalid visibility level. - [FIXED] Fix project feature being deleted when updating project with invalid visibility level.
- [FIXED] Fix new navigation wrapping and causing height to grow. - [FIXED] Fix new navigation wrapping and causing height to grow.
- [FIXED] Normalize styles for empty state combo button.
- [FIXED] Fix buttons with different height in merge request widget. - [FIXED] Fix buttons with different height in merge request widget.
- [FIXED] Normalize styles for empty state combo button.
- [FIXED] Fix broken svg in jobs dropdown for success status. - [FIXED] Fix broken svg in jobs dropdown for success status.
- [FIXED] Improve migrations using triggers. - [FIXED] Improve migrations using triggers.
- [FIXED] Disable GitLab Project Import Button if source disabled.
- [CHANGED] Update the GPG verification semantics: A GPG signature must additionally match the committer in order to be verified. !13771 (Alexis Reigel) - [CHANGED] Update the GPG verification semantics: A GPG signature must additionally match the committer in order to be verified. !13771 (Alexis Reigel)
- [OTHER] Fix repository equality check and avoid fetching ref if the commit is already available. This affects merge request creation performance. !13685 - [OTHER] Fix repository equality check and avoid fetching ref if the commit is already available. This affects merge request creation performance. !13685
- [OTHER] Update documentation for confidential issue. !14117 - [OTHER] Update documentation for confidential issue. !14117
......
...@@ -126,7 +126,7 @@ gem 'faraday_middleware-aws-signers-v4' ...@@ -126,7 +126,7 @@ gem 'faraday_middleware-aws-signers-v4'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0' gem 'html-pipeline', '~> 1.11.0'
gem 'deckar01-task_list', '2.0.0' gem 'deckar01-task_list', '2.0.0'
gem 'gitlab-markup', '~> 1.5.1' gem 'gitlab-markup', '~> 1.6.2'
gem 'redcarpet', '~> 3.4' gem 'redcarpet', '~> 3.4'
gem 'RedCloth', '~> 4.3.2' gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 4.2' gem 'rdoc', '~> 4.2'
......
...@@ -318,7 +318,7 @@ GEM ...@@ -318,7 +318,7 @@ GEM
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab-license (1.0.0) gitlab-license (1.0.0)
gitlab-markup (1.5.1) gitlab-markup (1.6.2)
gitlab_omniauth-ldap (2.0.4) gitlab_omniauth-ldap (2.0.4)
net-ldap (~> 0.16) net-ldap (~> 0.16)
omniauth (~> 1.3) omniauth (~> 1.3)
...@@ -1064,7 +1064,7 @@ DEPENDENCIES ...@@ -1064,7 +1064,7 @@ DEPENDENCIES
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
gitlab-markup (~> 1.5.1) gitlab-markup (~> 1.6.2)
gitlab_omniauth-ldap (~> 2.0.4) gitlab_omniauth-ldap (~> 2.0.4)
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4) gollum-rugged_adapter (~> 0.4.4)
......
...@@ -134,8 +134,9 @@ ...@@ -134,8 +134,9 @@
width: maxWidth + 1, width: maxWidth + 1,
height: maxHeight + 2 height: maxHeight + 2
}); });
// Set swipeBar left position to match image frame
$swipeBar.css({ $swipeBar.css({
left: 0 left: 1
}); });
wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10); wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
......
...@@ -375,8 +375,6 @@ header.navbar-gitlab-new { ...@@ -375,8 +375,6 @@ header.navbar-gitlab-new {
display: flex; display: flex;
width: 100%; width: 100%;
position: relative; position: relative;
padding-top: $gl-padding;
padding-bottom: $gl-padding;
align-items: center; align-items: center;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
} }
...@@ -388,6 +386,11 @@ header.navbar-gitlab-new { ...@@ -388,6 +386,11 @@ header.navbar-gitlab-new {
align-self: center; align-self: center;
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
@media (max-width: $screen-xs-max) {
padding-left: 17px;
border-left: 1px solid $gl-text-color-quaternary;
}
.avatar-tile { .avatar-tile {
margin-right: 4px; margin-right: 4px;
border: 1px solid $border-color; border: 1px solid $border-color;
......
...@@ -445,9 +445,8 @@ $new-sidebar-collapsed-width: 50px; ...@@ -445,9 +445,8 @@ $new-sidebar-collapsed-width: 50px;
background-color: transparent; background-color: transparent;
border: 0; border: 0;
padding: 6px 16px; padding: 6px 16px;
margin: 0 16px 0 -15px; margin: 0 0 0 -15px;
height: 46px; height: 46px;
border-right: 1px solid $gl-text-color-quaternary;
i { i {
font-size: 20px; font-size: 20px;
...@@ -455,7 +454,12 @@ $new-sidebar-collapsed-width: 50px; ...@@ -455,7 +454,12 @@ $new-sidebar-collapsed-width: 50px;
} }
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
display: inline-block; display: flex;
align-items: center;
i {
font-size: 18px;
}
} }
} }
......
.info-well {
.admin-well-statistics,
.admin-well-features {
padding-bottom: 46px;
}
}
...@@ -727,6 +727,12 @@ ul.notes { ...@@ -727,6 +727,12 @@ ul.notes {
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
.btn {
svg path {
fill: $gray-darkest;
}
}
.btn.discussion-create-issue-btn { .btn.discussion-create-issue-btn {
margin-left: -4px; margin-left: -4px;
border-radius: 0; border-radius: 0;
...@@ -741,10 +747,6 @@ ul.notes { ...@@ -741,10 +747,6 @@ ul.notes {
border: 0; border: 0;
} }
} }
.new-issue-for-discussion path {
fill: $gray-darkest;
}
} }
} }
...@@ -817,16 +819,6 @@ ul.notes { ...@@ -817,16 +819,6 @@ ul.notes {
vertical-align: middle; vertical-align: middle;
} }
.discussion-next-btn {
svg {
margin: 0;
path {
fill: $gray-darkest;
}
}
}
// Merge request notes in diffs // Merge request notes in diffs
.diff-file { .diff-file {
// Diff is inline // Diff is inline
......
...@@ -9,14 +9,12 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -9,14 +9,12 @@ class Projects::ForksController < Projects::ApplicationController
def index def index
base_query = project.forks.includes(:creator) base_query = project.forks.includes(:creator)
@forks = base_query.merge(ProjectsFinder.new(current_user: current_user).execute) forks = ForkProjectsFinder.new(project, params: params.merge(search: params[:filter_projects]), current_user: current_user).execute
@total_forks_count = base_query.size @total_forks_count = base_query.size
@private_forks_count = @total_forks_count - @forks.size @private_forks_count = @total_forks_count - forks.size
@public_forks_count = @total_forks_count - @private_forks_count @public_forks_count = @total_forks_count - @private_forks_count
@sort = params[:sort] || 'id_desc' @forks = forks.page(params[:page])
@forks = @forks.search(params[:filter_projects]) if params[:filter_projects].present?
@forks = @forks.order_by(@sort).page(params[:page])
respond_to do |format| respond_to do |format|
format.html format.html
......
class ForkProjectsFinder < ProjectsFinder
def initialize(project, params: {}, current_user: nil)
project_ids = project.forks.includes(:creator).select(:id)
super(params: params, current_user: current_user, project_ids_relation: project_ids)
end
end
...@@ -94,6 +94,12 @@ module MilestonesHelper ...@@ -94,6 +94,12 @@ module MilestonesHelper
end end
end end
def milestone_tooltip_title(milestone)
if milestone.due_date
[milestone.due_date.to_s(:medium), "(#{milestone_remaining_days(milestone)})"].join(' ')
end
end
def milestone_remaining_days(milestone) def milestone_remaining_days(milestone)
if milestone.expired? if milestone.expired?
content_tag(:strong, 'Past due') content_tag(:strong, 'Past due')
......
class Event < ActiveRecord::Base class Event < ActiveRecord::Base
include Sortable include Sortable
include IgnorableColumn include IgnorableColumn
default_scope { reorder(nil).where.not(author_id: nil) } default_scope { reorder(nil) }
CREATED = 1 CREATED = 1
UPDATED = 2 UPDATED = 2
...@@ -83,6 +83,12 @@ class Event < ActiveRecord::Base ...@@ -83,6 +83,12 @@ class Event < ActiveRecord::Base
scope :merged, -> { where(action: MERGED) } scope :merged, -> { where(action: MERGED) }
scope :totals_by_author, -> { group(:author_id).count } scope :totals_by_author, -> { group(:author_id).count }
# Authors are required as they're used to display who pushed data.
#
# We're just validating the presence of the ID here as foreign key constraints
# should ensure the ID points to a valid user.
validates :author_id, presence: true
self.inheritance_column = 'action' self.inheritance_column = 'action'
# "data" will be removed in 10.0 but it may be possible that JOINs happen that # "data" will be removed in 10.0 but it may be possible that JOINs happen that
......
...@@ -310,8 +310,6 @@ class Issue < ActiveRecord::Base ...@@ -310,8 +310,6 @@ class Issue < ActiveRecord::Base
end end
def update_project_counter_caches def update_project_counter_caches
return unless update_project_counter_caches?
Projects::OpenIssuesCountService.new(project).refresh_cache Projects::OpenIssuesCountService.new(project).refresh_cache
end end
......
...@@ -4,8 +4,6 @@ class Key < ActiveRecord::Base ...@@ -4,8 +4,6 @@ class Key < ActiveRecord::Base
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Sortable include Sortable
LAST_USED_AT_REFRESH_TIME = 1.day.to_i
belongs_to :user belongs_to :user
before_validation :generate_fingerprint before_validation :generate_fingerprint
...@@ -57,10 +55,7 @@ class Key < ActiveRecord::Base ...@@ -57,10 +55,7 @@ class Key < ActiveRecord::Base
end end
def update_last_used_at def update_last_used_at
lease = Gitlab::ExclusiveLease.new("key_update_last_used_at:#{id}", timeout: LAST_USED_AT_REFRESH_TIME) Keys::LastUsedService.new(self).execute
return unless lease.try_obtain
UseKeyWorker.perform_async(id)
end end
def add_to_shell def add_to_shell
......
...@@ -987,8 +987,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -987,8 +987,6 @@ class MergeRequest < ActiveRecord::Base
end end
def update_project_counter_caches def update_project_counter_caches
return unless update_project_counter_caches?
Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache
end end
......
...@@ -166,9 +166,7 @@ class Milestone < ActiveRecord::Base ...@@ -166,9 +166,7 @@ class Milestone < ActiveRecord::Base
# Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1" # Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1"
# Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1" # Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1"
# #
def to_reference(from_project = nil, format: :iid, full: false) def to_reference(from_project = nil, format: :name, full: false)
return if group_milestone? && format != :name
format_reference = milestone_format_reference(format) format_reference = milestone_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}" reference = "#{self.class.reference_prefix}#{format_reference}"
...@@ -245,6 +243,10 @@ class Milestone < ActiveRecord::Base ...@@ -245,6 +243,10 @@ class Milestone < ActiveRecord::Base
def milestone_format_reference(format = :iid) def milestone_format_reference(format = :iid)
raise ArgumentError, 'Unknown format' unless [:iid, :name].include?(format) raise ArgumentError, 'Unknown format' unless [:iid, :name].include?(format)
if group_milestone? && format == :iid
raise ArgumentError, 'Cannot refer to a group milestone by an internal id!'
end
if format == :name && !name.include?('"') if format == :name && !name.include?('"')
%("#{name}") %("#{name}")
else else
......
...@@ -3,12 +3,6 @@ class PushEvent < Event ...@@ -3,12 +3,6 @@ class PushEvent < Event
# different "action" value. # different "action" value.
validate :validate_push_action validate :validate_push_action
# Authors are required as they're used to display who pushed data.
#
# We're just validating the presence of the ID here as foreign key constraints
# should ensure the ID points to a valid user.
validates :author_id, presence: true
# The project is required to build links to commits, commit ranges, etc. # The project is required to build links to commits, commit ranges, etc.
# #
# We're just validating the presence of the ID here as foreign key constraints # We're just validating the presence of the ID here as foreign key constraints
......
...@@ -184,6 +184,7 @@ class IssuableBaseService < BaseService ...@@ -184,6 +184,7 @@ class IssuableBaseService < BaseService
after_create(issuable) after_create(issuable)
execute_hooks(issuable) execute_hooks(issuable)
invalidate_cache_counts(issuable, users: issuable.assignees) invalidate_cache_counts(issuable, users: issuable.assignees)
issuable.update_project_counter_caches
end end
issuable issuable
...@@ -195,8 +196,6 @@ class IssuableBaseService < BaseService ...@@ -195,8 +196,6 @@ class IssuableBaseService < BaseService
def after_create(issuable) def after_create(issuable)
# To be overridden by subclasses # To be overridden by subclasses
issuable.update_project_counter_caches
end end
def before_update(issuable) def before_update(issuable)
...@@ -205,8 +204,6 @@ class IssuableBaseService < BaseService ...@@ -205,8 +204,6 @@ class IssuableBaseService < BaseService
def after_update(issuable) def after_update(issuable)
# To be overridden by subclasses # To be overridden by subclasses
issuable.update_project_counter_caches
end end
def update(issuable) def update(issuable)
...@@ -231,6 +228,10 @@ class IssuableBaseService < BaseService ...@@ -231,6 +228,10 @@ class IssuableBaseService < BaseService
before_update(issuable) before_update(issuable)
# We have to perform this check before saving the issuable as Rails resets
# the changed fields upon calling #save.
update_project_counters = issuable.update_project_counter_caches?
if issuable.with_transaction_returning_status { issuable.save } if issuable.with_transaction_returning_status { issuable.save }
# We do not touch as it will affect a update on updated_at field # We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do ActiveRecord::Base.no_touching do
...@@ -251,6 +252,8 @@ class IssuableBaseService < BaseService ...@@ -251,6 +252,8 @@ class IssuableBaseService < BaseService
after_update(issuable) after_update(issuable)
issuable.create_new_cross_references!(current_user) issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update') execute_hooks(issuable, 'update')
issuable.update_project_counter_caches if update_project_counters
end end
end end
......
...@@ -29,6 +29,7 @@ module Issues ...@@ -29,6 +29,7 @@ module Issues
todo_service.close_issue(issue, current_user) todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close') execute_hooks(issue, 'close')
invalidate_cache_counts(issue, users: issue.assignees) invalidate_cache_counts(issue, users: issue.assignees)
issue.update_project_counter_caches
end end
issue issue
......
module Keys
class LastUsedService
TIMEOUT = 1.day.to_i
attr_reader :key
# key - The Key for which to update the last used timestamp.
def initialize(key)
@key = key
end
def execute
# We _only_ want to update last_used_at and not also updated_at (which
# would be updated when using #touch).
key.update_column(:last_used_at, Time.zone.now) if update?
end
def update?
last_used = key.last_used_at
return false if last_used && (Time.zone.now - last_used) <= TIMEOUT
!!redis_lease.try_obtain
end
private
def redis_lease
Gitlab::ExclusiveLease
.new("key_update_last_used_at:#{key.id}", timeout: TIMEOUT)
end
end
end
...@@ -14,6 +14,7 @@ module MergeRequests ...@@ -14,6 +14,7 @@ module MergeRequests
todo_service.close_merge_request(merge_request, current_user) todo_service.close_merge_request(merge_request, current_user)
execute_hooks(merge_request, 'close') execute_hooks(merge_request, 'close')
invalidate_cache_counts(merge_request, users: merge_request.assignees) invalidate_cache_counts(merge_request, users: merge_request.assignees)
merge_request.update_project_counter_caches
end end
merge_request merge_request
......
...@@ -2,6 +2,11 @@ module Projects ...@@ -2,6 +2,11 @@ module Projects
# Base class for the various service classes that count project data (e.g. # Base class for the various service classes that count project data (e.g.
# issues or forks). # issues or forks).
class CountService class CountService
# The version of the cache format. This should be bumped whenever the
# underlying logic changes. This removes the need for explicitly flushing
# all caches.
VERSION = 1
def initialize(project) def initialize(project)
@project = project @project = project
end end
...@@ -37,7 +42,7 @@ module Projects ...@@ -37,7 +42,7 @@ module Projects
end end
def cache_key def cache_key
['projects', @project.id, cache_key_name] ['projects', 'count_service', VERSION, @project.id, cache_key_name]
end end
end end
end end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
.row .row
.col-md-4 .col-md-4
.info-well .info-well
.well-segment.admin-well .well-segment.admin-well.admin-well-statistics
%h4 Statistics %h4 Statistics
%p %p
Forks Forks
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
= number_with_delimiter(User.active.count) = number_with_delimiter(User.active.count)
.col-md-4 .col-md-4
.info-well .info-well
.well-segment.admin-well .well-segment.admin-well.admin-well-features
%h4 Features %h4 Features
- sign_up = "Sign up" - sign_up = "Sign up"
%p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") } %p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") }
...@@ -126,6 +126,10 @@ ...@@ -126,6 +126,10 @@
GitLab API GitLab API
%span.pull-right %span.pull-right
= API::API::version = API::API::version
%p
Gitaly
%span.pull-right
= Gitlab::GitalyClient.expected_server_version
- if Gitlab.config.pages.enabled - if Gitlab.config.pages.enabled
%p %p
GitLab Pages GitLab Pages
......
...@@ -10,9 +10,6 @@ ...@@ -10,9 +10,6 @@
= render "projects/last_push" = render "projects/last_push"
%div{ class: container_class } %div{ class: container_class }
- if show_callout?('user_callout_dismissed')
= render 'shared/user_callout'
- if has_projects_or_name?(@projects, params) - if has_projects_or_name?(@projects, params)
= render 'dashboard/projects_head' = render 'dashboard/projects_head'
= render 'projects' = render 'projects'
......
- if merge_request.discussions_can_be_resolved_by?(current_user) && can?(current_user, :create_issue, @project) - if merge_request.discussions_can_be_resolved_by?(current_user) && can?(current_user, :create_issue, @project)
.btn-group{ role: "group", "v-if" => "unresolvedDiscussionCount > 0" } .btn-group{ role: "group", "v-if" => "unresolvedDiscussionCount > 0" }
.btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve all discussions in new issue", = link_to custom_icon('icon_mr_issue'),
"aria-label" => "Resolve all discussions in a new issue", new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid),
"data-container" => "body" } title: 'Resolve all discussions in new issue',
= link_to custom_icon('icon_mr_issue'), new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid), title: "Resolve all discussions in new issue", class: 'new-issue-for-discussion' aria: { label: 'Resolve all discussions in new issue' },
data: { container: 'body' },
class: 'new-issue-for-discussion btn btn-default discussion-create-issue-btn has-tooltip'
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
%new-issue-for-discussion-btn{ ":discussion-id" => "'#{discussion.id}'", %new-issue-for-discussion-btn{ ":discussion-id" => "'#{discussion.id}'",
"inline-template" => true } "inline-template" => true }
.btn-group{ role: "group", "v-if" => "showButton" } .btn-group{ role: "group", "v-if" => "showButton" }
.btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve this discussion in a new issue", = link_to custom_icon('icon_mr_issue'),
"aria-label" => "Resolve this discussion in a new issue", new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id),
"data-container" => "body" } title: 'Resolve this discussion in a new issue',
= link_to custom_icon('icon_mr_issue'), new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id), title: "Resolve this discussion in a new issue", class: 'new-issue-for-discussion' aria: { label: 'Resolve this discussion in a new issue' },
data: { container: 'body' },
class: 'new-issue-for-discussion btn btn-default discussion-create-issue-btn has-tooltip'
...@@ -74,7 +74,9 @@ ...@@ -74,7 +74,9 @@
%h4.prepend-top-0.warning-title %h4.prepend-top-0.warning-title
Change username Change username
%p %p
Changing your username will change path to all personal projects! Changing your username can have unintended side effects.
= succeed '.' do
= link_to 'Learn more', help_page_path('user/profile/index', anchor: 'changing-your-username'), target: '_blank'
.col-lg-8 .col-lg-8
= form_for @user, url: update_username_profile_path, method: :put, html: {class: "update-username"} do |f| = form_for @user, url: update_username_profile_path, method: :put, html: {class: "update-username"} do |f|
.form-group .form-group
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
- if issue.milestone - if issue.milestone
%span.issuable-milestone.hidden-xs %span.issuable-milestone.hidden-xs
&nbsp; &nbsp;
= link_to project_issues_path(issue.project, milestone_title: issue.milestone.title) do = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_title(issue.milestone) } do
= icon('clock-o') = icon('clock-o')
= issue.milestone.title = issue.milestone.title
- if issue.due_date - if issue.due_date
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
- if merge_request.milestone - if merge_request.milestone
%span.issuable-milestone.hidden-xs %span.issuable-milestone.hidden-xs
&nbsp; &nbsp;
= link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title) do = link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_title(merge_request.milestone) } do
= icon('clock-o') = icon('clock-o')
= merge_request.milestone.title = merge_request.milestone.title
- if merge_request.target_project.default_branch != merge_request.target_branch - if merge_request.target_project.default_branch != merge_request.target_branch
......
...@@ -22,11 +22,9 @@ ...@@ -22,11 +22,9 @@
- if @group.persisted? - if @group.persisted?
.alert.alert-warning.prepend-top-10 .alert.alert-warning.prepend-top-10
%ul Changing group path can have unintended side effects.
%li Changing group path can have unintended side effects. = succeed '.' do
%li Renaming group path will rename directory for all related projects = link_to 'Learn more', help_page_path('user/group/index', anchor: 'changing-a-groups-path'), target: '_blank'
%li It will change web url for access group and group projects.
%li It will change the git path to repositories under this group.
.form-group.group-name-holder .form-group.group-name-holder
= f.label :name, class: 'control-label' do = f.label :name, class: 'control-label' do
......
.user-callout{ data: { uid: 'user_callout_dismissed' } }
.bordered-box.landing.content-block
%button.btn.btn-default.close.js-close-callout{ type: 'button',
'aria-label' => 'Dismiss customize experience box' }
= icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true')
.svg-container
= custom_icon('icon_customization')
.user-callout-copy
%h4
Customize your experience
%p
Change syntax themes, default project pages, and more in preferences.
= link_to 'Check it out', profile_preferences_path, class: 'btn btn-primary js-close-callout'
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
= icon('clock-o', 'aria-hidden': 'true') = icon('clock-o', 'aria-hidden': 'true')
%span.milestone-title %span.milestone-title
- if issuable.milestone - if issuable.milestone
%span.has-tooltip{ title: "#{issuable.milestone.title}<br>#{milestone_remaining_days(issuable.milestone)}", data: { container: 'body', html: 1, placement: 'left' } } %span.has-tooltip{ title: "#{issuable.milestone.title}<br>#{milestone_tooltip_title(issuable.milestone)}", data: { container: 'body', html: 1, placement: 'left' } }
= issuable.milestone.title = issuable.milestone.title
- else - else
None None
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
= link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right' = link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
.value.hide-collapsed .value.hide-collapsed
- if issuable.milestone - if issuable.milestone
= link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 } = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_title(issuable.milestone), data: { container: "body", html: 1 }
- else - else
%span.no-value None %span.no-value None
......
...@@ -99,8 +99,6 @@ ...@@ -99,8 +99,6 @@
Snippets Snippets
%div{ class: container_class } %div{ class: container_class }
- if @user == current_user && show_callout?('user_callout_dismissed')
= render 'shared/user_callout'
.tab-content .tab-content
#activity.tab-pane #activity.tab-pane
.row-content-block.calender-block.white.second-block.hidden-xs .row-content-block.calender-block.white.second-block.hidden-xs
......
class UseKeyWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
def perform(key_id)
key = Key.find(key_id)
key.touch(:last_used_at)
rescue ActiveRecord::RecordNotFound
Rails.logger.error("UseKeyWorker: couldn't find key with ID=#{key_id}, skipping job")
false
end
end
---
title: Upgrade gitlab-markup gem
merge_request: 14395
author: Markus Koller
type: other
---
title: Add an API endpoint to determine the forks of a project
merge_request:
author:
type: added
---
title: Add Gitaly version to Admin Dashboard
merge_request: 14313
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Fix the "resolve discussion in a new issue" button
merge_request: 14357
author:
type: fixed
---
title: Add tooltip for milestone due date to issue and merge request lists
merge_request: 14318
author: Vitaliy @blackst0ne Klachkov
type: added
---
title: Remove redundant WHERE from event queries
merge_request:
author:
type: other
---
title: Fix errors when moving issue with reference to a group milestone
merge_request: 14294
author:
type: fixed
---
title: Fixes the 500 errors caused by a race condition in GPG's tmp directory handling
merge_request: 14194
author: Alexis Reigel
type: fixed
---
title: Fix image diff swipe handle offset to correctly align with the frame
merge_request:
author:
type: fixed
---
title: Fix Pipeline Triggers to show triggered label and predefined variables (e.g.
CI_PIPELINE_TRIGGERED)
merge_request: 14244
author:
type: fixed
---
title: Fix project feature being deleted when updating project with invalid visibility
level
merge_request:
author:
type: fixed
---
title: Expand docs for changing username or group path
merge_request: 13914
author:
type: other
---
title: Stop using Sidekiq for updating Key#last_used_at
merge_request:
author:
type: changed
...@@ -38,7 +38,6 @@ ...@@ -38,7 +38,6 @@
- [invalid_gpg_signature_update, 2] - [invalid_gpg_signature_update, 2]
- [create_gpg_signature, 2] - [create_gpg_signature, 2]
- [upload_checksum, 1] - [upload_checksum, 1]
- [use_key, 1]
- [repository_fork, 1] - [repository_fork, 1]
- [repository_import, 1] - [repository_import, 1]
- [project_service, 1] - [project_service, 1]
......
...@@ -4,9 +4,9 @@ Sidekiq::Testing.inline! do ...@@ -4,9 +4,9 @@ Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do Gitlab::Seeder.quiet do
project_urls = [ project_urls = [
'https://gitlab.com/gitlab-org/gitlab-test.git', 'https://gitlab.com/gitlab-org/gitlab-test.git',
'https://gitlab.com/gitlab-org/gitlab-ce.git',
'https://gitlab.com/gitlab-org/gitlab-ci.git',
'https://gitlab.com/gitlab-org/gitlab-shell.git', 'https://gitlab.com/gitlab-org/gitlab-shell.git',
'https://gitlab.com/gnuwget/wget2.git',
'https://gitlab.com/Commit451/LabCoat.git',
'https://github.com/documentcloud/underscore.git', 'https://github.com/documentcloud/underscore.git',
'https://github.com/twitter/flight.git', 'https://github.com/twitter/flight.git',
'https://github.com/twitter/typeahead.js.git', 'https://github.com/twitter/typeahead.js.git',
......
...@@ -645,6 +645,98 @@ POST /projects/:id/fork ...@@ -645,6 +645,98 @@ POST /projects/:id/fork
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `namespace` | integer/string | yes | The ID or path of the namespace that the project will be forked to | | `namespace` | integer/string | yes | The ID or path of the namespace that the project will be forked to |
## List Forks of a project
>**Note:** This feature was introduced in GitLab 10.1
List the projects accessible to the calling user that have an established, forked relationship with the specified project
```
GET /projects/:id/forks
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `archived` | boolean | no | Limit by archived status |
| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of projects matching the search criteria |
| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
| `owned` | boolean | no | Limit by projects owned by the current user |
| `membership` | boolean | no | Limit by projects that the current user is a member of |
| `starred` | boolean | no | Limit by projects starred by the current user |
| `statistics` | boolean | no | Include project statistics |
| `with_issues_enabled` | boolean | no | Limit by enabled issues feature |
| `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/forks"
```
Example responses:
```json
[
{
"id": 3,
"description": null,
"default_branch": "master",
"visibility": "internal",
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
"web_url": "http://example.com/diaspora/diaspora-project-site",
"tag_list": [
"example",
"disapora project"
],
"name": "Diaspora Project Site",
"name_with_namespace": "Diaspora / Diaspora Project Site",
"path": "diaspora-project-site",
"path_with_namespace": "diaspora/diaspora-project-site",
"issues_enabled": true,
"open_issues_count": 1,
"merge_requests_enabled": true,
"jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"resolve_outdated_diff_discussions": false,
"container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
"id": 3,
"name": "Diaspora",
"path": "diaspora",
"kind": "group",
"full_path": "diaspora"
},
"import_status": "none",
"archived": true,
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 1,
"public_jobs": true,
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false,
"_links": {
"self": "http://example.com/api/v4/projects",
"issues": "http://example.com/api/v4/projects/1/issues",
"merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
"repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
"labels": "http://example.com/api/v4/projects/1/labels",
"events": "http://example.com/api/v4/projects/1/events",
"members": "http://example.com/api/v4/projects/1/members"
}
}
]
```
## Star a project ## Star a project
Stars a given project. Returns status code `304` if the project is already starred. Stars a given project. Returns status code `304` if the project is already starred.
......
...@@ -188,6 +188,27 @@ Besides giving you the option to edit any settings you've previously ...@@ -188,6 +188,27 @@ Besides giving you the option to edit any settings you've previously
set when [creating the group](#create-a-new-group), you can also set when [creating the group](#create-a-new-group), you can also
access further configurations for your group. access further configurations for your group.
#### Changing a group's path
> **Note:** If you want to retain ownership over the original namespace and
protect the URL redirects, then instead of changing a group's path or renaming a
username, you can create a new group and transfer projects to it.
Changing a group's path can have unintended side effects.
* Existing web URLs for the group and anything under it (i.e. projects) will
redirect to the new URLs
* Existing Git remote URLs for projects under the group will no longer work, but
Git responses will show an error with the new remote URL
* The original namespace can be claimed again by any group or user, which will
destroy web redirects and Git remote warnings
* If you are vacating the path so it can be claimed by another group or user,
you may need to rename the group name as well since both names and paths must be
unique
> It is currently not possible to rename a namespace if it contains a
project with container registry tags, because the project cannot be moved.
#### Enforce 2FA to group members #### Enforce 2FA to group members
Add a security layer to your group by Add a security layer to your group by
......
...@@ -8,10 +8,27 @@ experience according to the best approach to their cases. ...@@ -8,10 +8,27 @@ experience according to the best approach to their cases.
Your `username` is a unique [`namespace`](../group/index.md#namespaces) Your `username` is a unique [`namespace`](../group/index.md#namespaces)
related to your user ID. related to your user ID.
### Changing your username
You can change your `username` from your You can change your `username` from your
[profile settings](#profile-settings). To avoid breaking [profile settings](#profile-settings).
paths when you change your `username`, we suggest you follow
[this procedure from the GitLab Team Handbook](https://about.gitlab.com/handbook/tools-and-tips/#how-to-change-your-username-at-gitlabcom). > **Note:** If you want to retain ownership over the original namespace and
protect the URL redirects, then instead of changing your username, you can
create a new group and transfer projects to it.
Alternatively, you can follow [this detailed procedure from the GitLab Team Handbook](https://about.gitlab.com/handbook/tools-and-tips/#how-to-change-your-username-at-gitlabcom).
Changing your username can have unintended side effects.
* Existing web URLs for the user and anything under it (i.e. projects) will
redirect to the new URLs
* Existing Git remote URLs for projects under the user will no longer work, but
Git responses will show an error with the new remote URL
* The original namespace can be claimed again by any group or user, which will
destroy any web redirects and Git remote warnings
> It is currently not possible to rename a namespace if it contains a
project with container registry tags, because the project cannot be moved.
## User profile ## User profile
......
...@@ -76,8 +76,11 @@ module API ...@@ -76,8 +76,11 @@ module API
optional :import_url, type: String, desc: 'URL from which the project is imported' optional :import_url, type: String, desc: 'URL from which the project is imported'
end end
def present_projects(options = {}) def load_projects
projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
end
def present_projects(projects, options = {})
projects = reorder_projects(projects) projects = reorder_projects(projects)
projects = projects.with_statistics if params[:statistics] projects = projects.with_statistics if params[:statistics]
projects = projects.with_issues_enabled if params[:with_issues_enabled] projects = projects.with_issues_enabled if params[:with_issues_enabled]
...@@ -117,7 +120,7 @@ module API ...@@ -117,7 +120,7 @@ module API
params[:user] = user params[:user] = user
present_projects present_projects load_projects
end end
end end
...@@ -130,7 +133,7 @@ module API ...@@ -130,7 +133,7 @@ module API
use :statistics_params use :statistics_params
end end
get do get do
present_projects present_projects load_projects
end end
desc 'Create new project' do desc 'Create new project' do
...@@ -235,6 +238,18 @@ module API ...@@ -235,6 +238,18 @@ module API
end end
end end
desc 'List forks of this project' do
success Entities::Project
end
params do
use :collection_params
end
get ':id/forks' do
forks = ForkProjectsFinder.new(user_project, params: project_finder_params, current_user: current_user).execute
present_projects forks
end
desc 'Update an existing project' do desc 'Update an existing project' do
success Entities::Project success Entities::Project
end end
......
...@@ -79,7 +79,7 @@ module Backup ...@@ -79,7 +79,7 @@ module Backup
# - 1495527122_gitlab_backup.tar # - 1495527122_gitlab_backup.tar
# - 1495527068_2017_05_23_gitlab_backup.tar # - 1495527068_2017_05_23_gitlab_backup.tar
# - 1495527097_2017_05_23_9.3.0-pre_gitlab_backup.tar # - 1495527097_2017_05_23_9.3.0-pre_gitlab_backup.tar
next unless file =~ /(\d+)(?:_\d{4}_\d{2}_\d{2}(_\d+\.\d+\.\d+.*)?)?_gitlab_backup\.tar$/ next unless file =~ /^(\d{10})(?:_\d{4}_\d{2}_\d{2}(_\d+\.\d+\.\d+((-|\.)(pre|rc\d))?)?)?_gitlab_backup\.tar$/
timestamp = $1.to_i timestamp = $1.to_i
......
...@@ -29,6 +29,8 @@ module Gitlab ...@@ -29,6 +29,8 @@ module Gitlab
# http://gitlab.com/some/link/#1234, and code `puts #1234`' # http://gitlab.com/some/link/#1234, and code `puts #1234`'
# #
class ReferenceRewriter class ReferenceRewriter
RewriteError = Class.new(StandardError)
def initialize(text, source_project, current_user) def initialize(text, source_project, current_user)
@text = text @text = text
@source_project = source_project @source_project = source_project
...@@ -61,6 +63,10 @@ module Gitlab ...@@ -61,6 +63,10 @@ module Gitlab
cross_reference = build_cross_reference(referable, target_project) cross_reference = build_cross_reference(referable, target_project)
return reference if reference == cross_reference return reference if reference == cross_reference
if cross_reference.nil?
raise RewriteError, "Unspecified reference detected for #{referable.class.name}"
end
new_text = before + cross_reference + after new_text = before + cross_reference + after
substitution_valid?(new_text) ? cross_reference : reference substitution_valid?(new_text) ? cross_reference : reference
end end
......
module Gitlab module Gitlab
module Git module Git
class OperationService class OperationService
include Gitlab::Git::Popen
WithBranchResult = Struct.new(:newrev, :repo_created, :branch_created) do WithBranchResult = Struct.new(:newrev, :repo_created, :branch_created) do
alias_method :repo_created?, :repo_created alias_method :repo_created?, :repo_created
alias_method :branch_created?, :branch_created alias_method :branch_created?, :branch_created
...@@ -150,7 +152,7 @@ module Gitlab ...@@ -150,7 +152,7 @@ module Gitlab
# (and have!) accidentally reset the ref to an earlier state, clobbering # (and have!) accidentally reset the ref to an earlier state, clobbering
# commits. See also https://github.com/libgit2/libgit2/issues/1534. # commits. See also https://github.com/libgit2/libgit2/issues/1534.
command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z] command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]
_, status = Gitlab::Popen.popen( _, status = popen(
command, command,
repository.path) do |stdin| repository.path) do |stdin|
stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00") stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00")
......
...@@ -5,17 +5,21 @@ require 'open3' ...@@ -5,17 +5,21 @@ require 'open3'
module Gitlab module Gitlab
module Git module Git
module Popen module Popen
def popen(cmd, path) def popen(cmd, path, vars = {})
unless cmd.is_a?(Array) unless cmd.is_a?(Array)
raise "System commands must be given as an array of strings" raise "System commands must be given as an array of strings"
end end
vars = { "PWD" => path } path ||= Dir.pwd
vars['PWD'] = path
options = { chdir: path } options = { chdir: path }
@cmd_output = "" @cmd_output = ""
@cmd_status = 0 @cmd_status = 0
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
yield(stdin) if block_given?
stdin.close
@cmd_output << stdout.read @cmd_output << stdout.read
@cmd_output << stderr.read @cmd_output << stderr.read
@cmd_status = wait_thr.value.exitstatus @cmd_status = wait_thr.value.exitstatus
......
...@@ -490,7 +490,7 @@ module Gitlab ...@@ -490,7 +490,7 @@ module Gitlab
# Not found -> ["", 0] # Not found -> ["", 0]
# Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0] # Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0]
Gitlab::Popen.popen(args, @path).first.split.last popen(args, @path).first.split.last
end end
end end
end end
...@@ -792,9 +792,7 @@ module Gitlab ...@@ -792,9 +792,7 @@ module Gitlab
end end
command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z] command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]
message, status = Gitlab::Popen.popen( message, status = popen(command, path) do |stdin|
command,
path) do |stdin|
stdin.write(instructions.join) stdin.write(instructions.join)
end end
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
module Gitlab module Gitlab
module Git module Git
class RevList class RevList
include Gitlab::Git::Popen
attr_reader :oldrev, :newrev, :path_to_repo attr_reader :oldrev, :newrev, :path_to_repo
def initialize(path_to_repo:, newrev:, oldrev: nil) def initialize(path_to_repo:, newrev:, oldrev: nil)
...@@ -26,7 +28,7 @@ module Gitlab ...@@ -26,7 +28,7 @@ module Gitlab
private private
def execute(args) def execute(args)
output, status = Gitlab::Popen.popen(args, nil, Gitlab::Git::Env.all.stringify_keys) output, status = popen(args, nil, Gitlab::Git::Env.all.stringify_keys)
unless status.zero? unless status.zero?
raise "Got a non-zero exit code while calling out `#{args.join(' ')}`." raise "Got a non-zero exit code while calling out `#{args.join(' ')}`."
......
require 'spec_helper'
describe 'User Callouts', js: true do
let(:user) { create(:user) }
let(:another_user) { create(:user) }
let(:project) { create(:project, path: 'gitlab', name: 'sample') }
before do
sign_in(user)
project.team << [user, :master]
end
it 'takes you to the profile preferences when the link is clicked' do
visit dashboard_projects_path
click_link 'Check it out'
expect(current_path).to eq profile_preferences_path
end
it 'does not show when cookie is set' do
visit dashboard_projects_path
within('.user-callout') do
find('.close').trigger('click')
end
visit dashboard_projects_path
expect(page).not_to have_selector('.user-callout')
end
describe 'user callout should appear in two routes' do
it 'shows up on the user profile' do
visit user_path(user)
expect(find('.user-callout')).to have_content 'Customize your experience'
end
it 'shows up on the dashboard projects' do
visit dashboard_projects_path
expect(find('.user-callout')).to have_content 'Customize your experience'
end
end
it 'hides the user callout when click on the dismiss icon' do
visit user_path(user)
within('.user-callout') do
find('.close').click
end
expect(page).not_to have_selector('.user-callout')
end
it 'does not show callout on another users profile' do
visit user_path(another_user)
expect(page).not_to have_selector('.user-callout')
end
end
require 'spec_helper'
describe ForkProjectsFinder do
let(:source_project) { create(:project, :empty_repo) }
let(:private_fork) { create(:project, :private, :empty_repo, name: 'A') }
let(:internal_fork) { create(:project, :internal, :empty_repo, name: 'B') }
let(:public_fork) { create(:project, :public, :empty_repo, name: 'C') }
let(:non_member) { create(:user) }
let(:private_fork_member) { create(:user) }
before do
private_fork.add_developer(private_fork_member)
source_project.forks << private_fork
source_project.forks << internal_fork
source_project.forks << public_fork
end
describe '#execute' do
let(:finder) { described_class.new(source_project, params: {}, current_user: current_user) }
subject { finder.execute }
describe 'without a user' do
let(:current_user) { nil }
it { is_expected.to eq([public_fork]) }
end
describe 'with a user' do
let(:current_user) { non_member }
it { is_expected.to eq([public_fork, internal_fork]) }
end
describe 'with a member' do
let(:current_user) { private_fork_member }
it { is_expected.to eq([public_fork, internal_fork, private_fork]) }
end
end
end
require 'spec_helper'
describe Dashboard::ProjectsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, namespace: namespace, path: 'builds-project') }
render_views
before(:all) do
clean_frontend_fixtures('dashboard/')
end
before do
sign_in(admin)
end
after do
remove_repository(project)
end
it 'dashboard/user-callout.html.raw' do |example|
rendered = render_template('shared/_user_callout')
store_frontend_fixture(rendered, example.description)
end
private
def render_template(template_file_name)
controller.prepend_view_path(JavaScriptFixturesHelpers::FIXTURE_PATH)
controller.render_to_string(template_file_name, layout: false)
end
end
import Cookies from 'js-cookie';
import UserCallout from '~/user_callout';
const USER_CALLOUT_COOKIE = 'user_callout_dismissed';
describe('UserCallout', function () {
const fixtureName = 'dashboard/user-callout.html.raw';
preloadFixtures(fixtureName);
beforeEach(() => {
loadFixtures(fixtureName);
Cookies.remove(USER_CALLOUT_COOKIE);
this.userCallout = new UserCallout();
this.closeButton = $('.js-close-callout.close');
this.userCalloutBtn = $('.js-close-callout:not(.close)');
});
it('hides when user clicks on the dismiss-icon', (done) => {
this.closeButton.click();
expect(Cookies.get(USER_CALLOUT_COOKIE)).toBe('true');
setTimeout(() => {
expect(
document.querySelector('.user-callout'),
).toBeNull();
done();
});
});
it('hides when user clicks on the "check it out" button', () => {
this.userCalloutBtn.click();
expect(Cookies.get(USER_CALLOUT_COOKIE)).toBe('true');
});
describe('Sets cookie with setCalloutPerProject', () => {
beforeEach(() => {
spyOn(Cookies, 'set').and.callFake(() => {});
document.querySelector('.user-callout').setAttribute('data-project-path', 'foo/bar');
this.userCallout = new UserCallout({ setCalloutPerProject: true });
});
it('sets a cookie when the user clicks the close button', () => {
this.userCalloutBtn.click();
expect(Cookies.set).toHaveBeenCalledWith('user_callout_dismissed', 'true', Object({ expires: 365, path: 'foo/bar' }));
});
});
});
...@@ -296,7 +296,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do ...@@ -296,7 +296,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
context 'project milestones' do context 'project milestones' do
let(:milestone) { create(:milestone, project: project) } let(:milestone) { create(:milestone, project: project) }
let(:reference) { milestone.to_reference } let(:reference) { milestone.to_reference(format: :iid) }
include_examples 'reference parsing' include_examples 'reference parsing'
......
...@@ -26,6 +26,8 @@ describe Backup::Manager do ...@@ -26,6 +26,8 @@ describe Backup::Manager do
[ [
'1451606400_2016_01_01_1.2.3_gitlab_backup.tar', '1451606400_2016_01_01_1.2.3_gitlab_backup.tar',
'1451520000_2015_12_31_4.5.6_gitlab_backup.tar', '1451520000_2015_12_31_4.5.6_gitlab_backup.tar',
'1451520000_2015_12_31_4.5.6-pre_gitlab_backup.tar',
'1451520000_2015_12_31_4.5.6-rc1_gitlab_backup.tar',
'1451510000_2015_12_30_gitlab_backup.tar', '1451510000_2015_12_30_gitlab_backup.tar',
'1450742400_2015_12_22_gitlab_backup.tar', '1450742400_2015_12_22_gitlab_backup.tar',
'1449878400_gitlab_backup.tar', '1449878400_gitlab_backup.tar',
...@@ -57,6 +59,30 @@ describe Backup::Manager do ...@@ -57,6 +59,30 @@ describe Backup::Manager do
end end
end end
context 'when no valid file is found' do
let(:files) do
[
'14516064000_2016_01_01_1.2.3_gitlab_backup.tar',
'foo_1451520000_2015_12_31_4.5.6_gitlab_backup.tar',
'1451520000_2015_12_31_4.5.6-foo_gitlab_backup.tar'
]
end
before do
allow(Gitlab.config.backup).to receive(:keep_time).and_return(1)
subject.remove_old
end
it 'removes no files' do
expect(FileUtils).not_to have_received(:rm)
end
it 'prints a done message' do
expect(progress).to have_received(:puts).with('done. (0 removed)')
end
end
context 'when there are no files older than keep_time' do context 'when there are no files older than keep_time' do
before do before do
# Set to 30 days # Set to 30 days
...@@ -84,28 +110,30 @@ describe Backup::Manager do ...@@ -84,28 +110,30 @@ describe Backup::Manager do
it 'removes matching files with a human-readable versioned timestamp' do it 'removes matching files with a human-readable versioned timestamp' do
expect(FileUtils).to have_received(:rm).with(files[1]) expect(FileUtils).to have_received(:rm).with(files[1])
end
it 'removes matching files with a human-readable non-versioned timestamp' do
expect(FileUtils).to have_received(:rm).with(files[2]) expect(FileUtils).to have_received(:rm).with(files[2])
expect(FileUtils).to have_received(:rm).with(files[3]) expect(FileUtils).to have_received(:rm).with(files[3])
end end
it 'removes matching files without a human-readable timestamp' do it 'removes matching files with a human-readable non-versioned timestamp' do
expect(FileUtils).to have_received(:rm).with(files[4]) expect(FileUtils).to have_received(:rm).with(files[4])
expect(FileUtils).to have_received(:rm).with(files[5]) expect(FileUtils).to have_received(:rm).with(files[5])
end end
it 'removes matching files without a human-readable timestamp' do
expect(FileUtils).to have_received(:rm).with(files[6])
expect(FileUtils).to have_received(:rm).with(files[7])
end
it 'does not remove files that are not old enough' do it 'does not remove files that are not old enough' do
expect(FileUtils).not_to have_received(:rm).with(files[0]) expect(FileUtils).not_to have_received(:rm).with(files[0])
end end
it 'does not remove non-matching files' do it 'does not remove non-matching files' do
expect(FileUtils).not_to have_received(:rm).with(files[6]) expect(FileUtils).not_to have_received(:rm).with(files[8])
end end
it 'prints a done message' do it 'prints a done message' do
expect(progress).to have_received(:puts).with('done. (5 removed)') expect(progress).to have_received(:puts).with('done. (7 removed)')
end end
end end
...@@ -121,14 +149,14 @@ describe Backup::Manager do ...@@ -121,14 +149,14 @@ describe Backup::Manager do
end end
it 'removes the remaining expected files' do it 'removes the remaining expected files' do
expect(FileUtils).to have_received(:rm).with(files[2])
expect(FileUtils).to have_received(:rm).with(files[3])
expect(FileUtils).to have_received(:rm).with(files[4]) expect(FileUtils).to have_received(:rm).with(files[4])
expect(FileUtils).to have_received(:rm).with(files[5]) expect(FileUtils).to have_received(:rm).with(files[5])
expect(FileUtils).to have_received(:rm).with(files[6])
expect(FileUtils).to have_received(:rm).with(files[7])
end end
it 'sets the correct removed count' do it 'sets the correct removed count' do
expect(progress).to have_received(:puts).with('done. (4 removed)') expect(progress).to have_received(:puts).with('done. (6 removed)')
end end
it 'prints the error from file that could not be removed' do it 'prints the error from file that could not be removed' do
......
...@@ -5,13 +5,13 @@ describe Gitlab::Checks::ForcePush do ...@@ -5,13 +5,13 @@ describe Gitlab::Checks::ForcePush do
context "exit code checking", skip_gitaly_mock: true do context "exit code checking", skip_gitaly_mock: true do
it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do
allow(Gitlab::Popen).to receive(:popen).and_return(['normal output', 0]) allow_any_instance_of(Gitlab::Git::RevList).to receive(:popen).and_return(['normal output', 0])
expect { described_class.force_push?(project, 'oldrev', 'newrev') }.not_to raise_error expect { described_class.force_push?(project, 'oldrev', 'newrev') }.not_to raise_error
end end
it "raises a runtime error if the `popen` call to git returns a non-zero exit code" do it "raises a runtime error if the `popen` call to git returns a non-zero exit code" do
allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1]) allow_any_instance_of(Gitlab::Git::RevList).to receive(:popen).and_return(['error', 1])
expect { described_class.force_push?(project, 'oldrev', 'newrev') }.to raise_error(RuntimeError) expect { described_class.force_push?(project, 'oldrev', 'newrev') }.to raise_error(RuntimeError)
end end
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Gfm::ReferenceRewriter do describe Gitlab::Gfm::ReferenceRewriter do
let(:text) { 'some text' } let(:group) { create(:group) }
let(:old_project) { create(:project, name: 'old-project') } let(:old_project) { create(:project, name: 'old-project', group: group) }
let(:new_project) { create(:project, name: 'new-project') } let(:new_project) { create(:project, name: 'new-project', group: group) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:old_project_ref) { old_project.to_reference(new_project) }
let(:text) { 'some text' }
before do before do
old_project.team << [user, :reporter] old_project.team << [user, :reporter]
end end
...@@ -39,7 +42,7 @@ describe Gitlab::Gfm::ReferenceRewriter do ...@@ -39,7 +42,7 @@ describe Gitlab::Gfm::ReferenceRewriter do
it { is_expected.not_to include merge_request.to_reference(new_project) } it { is_expected.not_to include merge_request.to_reference(new_project) }
end end
context 'description ambigous elements' do context 'rewrite ambigous references' do
context 'url' do context 'url' do
let(:url) { 'http://gitlab.com/#1' } let(:url) { 'http://gitlab.com/#1' }
let(:text) { "This references #1, but not #{url}" } let(:text) { "This references #1, but not #{url}" }
...@@ -66,23 +69,21 @@ describe Gitlab::Gfm::ReferenceRewriter do ...@@ -66,23 +69,21 @@ describe Gitlab::Gfm::ReferenceRewriter do
context 'description with project labels' do context 'description with project labels' do
let!(:label) { create(:label, id: 123, name: 'test', project: old_project) } let!(:label) { create(:label, id: 123, name: 'test', project: old_project) }
let(:project_ref) { old_project.to_reference(new_project) }
context 'label referenced by id' do context 'label referenced by id' do
let(:text) { '#1 and ~123' } let(:text) { '#1 and ~123' }
it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~123} } it { is_expected.to eq %Q{#{old_project_ref}#1 and #{old_project_ref}~123} }
end end
context 'label referenced by text' do context 'label referenced by text' do
let(:text) { '#1 and ~"test"' } let(:text) { '#1 and ~"test"' }
it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~123} } it { is_expected.to eq %Q{#{old_project_ref}#1 and #{old_project_ref}~123} }
end end
end end
context 'description with group labels' do context 'description with group labels' do
let(:old_group) { create(:group) } let(:old_group) { create(:group) }
let!(:group_label) { create(:group_label, id: 321, name: 'group label', group: old_group) } let!(:group_label) { create(:group_label, id: 321, name: 'group label', group: old_group) }
let(:project_ref) { old_project.to_reference(new_project) }
before do before do
old_project.update(namespace: old_group) old_project.update(namespace: old_group)
...@@ -90,22 +91,54 @@ describe Gitlab::Gfm::ReferenceRewriter do ...@@ -90,22 +91,54 @@ describe Gitlab::Gfm::ReferenceRewriter do
context 'label referenced by id' do context 'label referenced by id' do
let(:text) { '#1 and ~321' } let(:text) { '#1 and ~321' }
it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~321} } it { is_expected.to eq %Q{#{old_project_ref}#1 and #{old_project_ref}~321} }
end end
context 'label referenced by text' do context 'label referenced by text' do
let(:text) { '#1 and ~"group label"' } let(:text) { '#1 and ~"group label"' }
it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~321} } it { is_expected.to eq %Q{#{old_project_ref}#1 and #{old_project_ref}~321} }
end
end
end end
end end
context 'reference contains project milestone' do
let!(:milestone) do
create(:milestone, title: '9.0', project: old_project)
end
let(:text) { 'milestone: %"9.0"' }
it { is_expected.to eq %Q[milestone: #{old_project_ref}%"9.0"] }
end
context 'when referring to group milestone' do
let!(:milestone) do
create(:milestone, title: '10.0', group: group)
end end
context 'reference contains milestone' do let(:text) { 'milestone %"10.0"' }
let(:milestone) { create(:milestone) }
let(:text) { "milestone ref: #{milestone.to_reference}" }
it { is_expected.to eq text } it { is_expected.to eq text }
end end
context 'when referable has a nil reference' do
before do
create(:milestone, title: '9.0', project: old_project)
allow_any_instance_of(Milestone)
.to receive(:to_reference)
.and_return(nil)
end
let(:text) { 'milestone: %"9.0"' }
it 'raises an error that should be fixed' do
expect { subject }.to raise_error(
described_class::RewriteError,
'Unspecified reference detected for Milestone'
)
end
end end
end end
end end
...@@ -481,7 +481,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -481,7 +481,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
it 'raises an error if it failed' do it 'raises an error if it failed' do
expect(Gitlab::Popen).to receive(:popen).and_return(['Error', 1]) expect(@repo).to receive(:popen).and_return(['Error', 1])
expect do expect do
@repo.delete_refs('refs/heads/fix') @repo.delete_refs('refs/heads/fix')
......
...@@ -14,7 +14,7 @@ describe Gitlab::Git::RevList do ...@@ -14,7 +14,7 @@ describe Gitlab::Git::RevList do
let(:rev_list) { described_class.new(newrev: 'newrev', path_to_repo: project.repository.path_to_repo) } let(:rev_list) { described_class.new(newrev: 'newrev', path_to_repo: project.repository.path_to_repo) }
it 'calls out to `popen`' do it 'calls out to `popen`' do
expect(Gitlab::Popen).to receive(:popen).with([ expect(rev_list).to receive(:popen).with([
Gitlab.config.git.bin_path, Gitlab.config.git.bin_path,
"--git-dir=#{project.repository.path_to_repo}", "--git-dir=#{project.repository.path_to_repo}",
'rev-list', 'rev-list',
...@@ -36,7 +36,7 @@ describe Gitlab::Git::RevList do ...@@ -36,7 +36,7 @@ describe Gitlab::Git::RevList do
let(:rev_list) { described_class.new(oldrev: 'oldrev', newrev: 'newrev', path_to_repo: project.repository.path_to_repo) } let(:rev_list) { described_class.new(oldrev: 'oldrev', newrev: 'newrev', path_to_repo: project.repository.path_to_repo) }
it 'calls out to `popen`' do it 'calls out to `popen`' do
expect(Gitlab::Popen).to receive(:popen).with([ expect(rev_list).to receive(:popen).with([
Gitlab.config.git.bin_path, Gitlab.config.git.bin_path,
"--git-dir=#{project.repository.path_to_repo}", "--git-dir=#{project.repository.path_to_repo}",
'rev-list', 'rev-list',
......
...@@ -37,33 +37,20 @@ describe Key, :mailer do ...@@ -37,33 +37,20 @@ describe Key, :mailer do
end end
describe "#update_last_used_at" do describe "#update_last_used_at" do
let(:key) { create(:key) } it 'updates the last used timestamp' do
key = build(:key)
context 'when key was not updated during the last day' do service = double(:service)
before do
allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain)
.and_return('000000')
end
it 'enqueues a UseKeyWorker job' do expect(Keys::LastUsedService).to receive(:new)
expect(UseKeyWorker).to receive(:perform_async).with(key.id) .with(key)
key.update_last_used_at .and_return(service)
end
end
context 'when key was updated during the last day' do expect(service).to receive(:execute)
before do
allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain)
.and_return(false)
end
it 'does not enqueue a UseKeyWorker job' do
expect(UseKeyWorker).not_to receive(:perform_async)
key.update_last_used_at key.update_last_used_at
end end
end end
end end
end
context "validation of uniqueness (based on fingerprint uniqueness)" do context "validation of uniqueness (based on fingerprint uniqueness)" do
let(:user) { create(:user) } let(:user) { create(:user) }
......
...@@ -240,7 +240,7 @@ describe Milestone do ...@@ -240,7 +240,7 @@ describe Milestone do
let(:milestone) { build_stubbed(:milestone, iid: 1, project: project, name: 'milestone') } let(:milestone) { build_stubbed(:milestone, iid: 1, project: project, name: 'milestone') }
it 'returns a String reference to the object' do it 'returns a String reference to the object' do
expect(milestone.to_reference).to eq '%1' expect(milestone.to_reference).to eq '%"milestone"'
end end
it 'returns a reference by name when the format is set to :name' do it 'returns a reference by name when the format is set to :name' do
...@@ -248,24 +248,29 @@ describe Milestone do ...@@ -248,24 +248,29 @@ describe Milestone do
end end
it 'supports a cross-project reference' do it 'supports a cross-project reference' do
expect(milestone.to_reference(another_project)).to eq 'sample-project%1' expect(milestone.to_reference(another_project)).to eq 'sample-project%"milestone"'
end end
end end
context 'for a group milestone' do context 'for a group milestone' do
let(:milestone) { build_stubbed(:milestone, iid: 1, group: group, name: 'milestone') } let(:milestone) { build_stubbed(:milestone, iid: 1, group: group, name: 'milestone') }
it 'returns nil with the default format' do it 'returns a group milestone reference with a default format' do
expect(milestone.to_reference).to be_nil expect(milestone.to_reference).to eq '%"milestone"'
end end
it 'returns a reference by name when the format is set to :name' do it 'returns a reference by name when the format is set to :name' do
expect(milestone.to_reference(format: :name)).to eq '%"milestone"' expect(milestone.to_reference(format: :name)).to eq '%"milestone"'
end end
it 'does not supports cross-project references' do it 'does supports cross-project references within a group' do
expect(milestone.to_reference(another_project, format: :name)).to eq '%"milestone"' expect(milestone.to_reference(another_project, format: :name)).to eq '%"milestone"'
end end
it 'raises an error when using iid format' do
expect { milestone.to_reference(format: :iid) }
.to raise_error(ArgumentError, 'Cannot refer to a group milestone by an internal id!')
end
end end
end end
......
...@@ -1232,6 +1232,59 @@ describe API::Projects do ...@@ -1232,6 +1232,59 @@ describe API::Projects do
end end
end end
end end
describe 'GET /projects/:id/forks' do
let(:private_fork) { create(:project, :private, :empty_repo) }
let(:member) { create(:user) }
let(:non_member) { create(:user) }
before do
private_fork.add_developer(member)
end
context 'for a forked project' do
before do
post api("/projects/#{private_fork.id}/fork/#{project_fork_source.id}", admin)
private_fork.reload
expect(private_fork.forked_from_project).not_to be_nil
expect(private_fork.forked?).to be_truthy
project_fork_source.reload
expect(project_fork_source.forks.length).to eq(1)
expect(project_fork_source.forks).to include(private_fork)
end
context 'for a user that can access the forks' do
it 'returns the forks' do
get api("/projects/#{project_fork_source.id}/forks", member)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response.length).to eq(1)
expect(json_response[0]['name']).to eq(private_fork.name)
end
end
context 'for a user that cannot access the forks' do
it 'returns an empty array' do
get api("/projects/#{project_fork_source.id}/forks", non_member)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response.length).to eq(0)
end
end
end
context 'for a non-forked project' do
it 'returns an empty array' do
get api("/projects/#{project_fork_source.id}/forks")
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response.length).to eq(0)
end
end
end
end end
describe "POST /projects/:id/share" do describe "POST /projects/:id/share" do
......
...@@ -42,7 +42,7 @@ describe Issues::CloseService do ...@@ -42,7 +42,7 @@ describe Issues::CloseService do
service.execute(issue) service.execute(issue)
end end
it 'refreshes the number of open issues' do it 'refreshes the number of open issues', :use_clean_rails_memory_store_caching do
expect { service.execute(issue) } expect { service.execute(issue) }
.to change { project.open_issues_count }.from(1).to(0) .to change { project.open_issues_count }.from(1).to(0)
end end
......
...@@ -35,7 +35,7 @@ describe Issues::CreateService do ...@@ -35,7 +35,7 @@ describe Issues::CreateService do
expect(issue.due_date).to eq Date.tomorrow expect(issue.due_date).to eq Date.tomorrow
end end
it 'refreshes the number of open issues' do it 'refreshes the number of open issues', :use_clean_rails_memory_store_caching do
expect { issue }.to change { project.open_issues_count }.from(0).to(1) expect { issue }.to change { project.open_issues_count }.from(0).to(1)
end end
......
...@@ -64,6 +64,13 @@ describe Issues::UpdateService, :mailer do ...@@ -64,6 +64,13 @@ describe Issues::UpdateService, :mailer do
expect(issue.due_date).to eq Date.tomorrow expect(issue.due_date).to eq Date.tomorrow
end end
it 'refreshes the number of open issues when the issue is made confidential', :use_clean_rails_memory_store_caching do
issue # make sure the issue is created first so our counts are correct.
expect { update_issue(confidential: true) }
.to change { project.open_issues_count }.from(1).to(0)
end
it 'updates open issue counter for assignees when issue is reassigned' do it 'updates open issue counter for assignees when issue is reassigned' do
update_issue(assignee_ids: [user2.id]) update_issue(assignee_ids: [user2.id])
......
require 'spec_helper'
describe Keys::LastUsedService do
describe '#execute', :clean_gitlab_redis_shared_state do
it 'updates the key when it has not been used recently' do
key = create(:key, last_used_at: 1.year.ago)
time = Time.zone.now
Timecop.freeze(time) { described_class.new(key).execute }
expect(key.last_used_at).to eq(time)
end
it 'does not update the key when it has been used recently' do
time = 1.minute.ago
key = create(:key, last_used_at: time)
described_class.new(key).execute
expect(key.last_used_at).to eq(time)
end
it 'does not update the updated_at field' do
# Since a lot of these updates could happen in parallel for different keys
# we want these updates to be as lightweight as possible, hence we want to
# make sure we _only_ update last_used_at and not always updated_at.
key = create(:key, last_used_at: 1.year.ago)
recorder = ActiveRecord::QueryRecorder.new do
described_class.new(key).execute
end
expect(recorder.count).to eq(1)
expect(recorder.log[0]).not_to include('updated_at')
end
end
describe '#update?', :clean_gitlab_redis_shared_state do
it 'returns true when no last used timestamp is present' do
key = build(:key, last_used_at: nil)
service = described_class.new(key)
expect(service.update?).to eq(true)
end
it 'returns true when the key needs to be updated' do
key = build(:key, last_used_at: 1.year.ago)
service = described_class.new(key)
expect(service.update?).to eq(true)
end
it 'returns false when a lease has already been obtained' do
key = build(:key, last_used_at: 1.year.ago)
service = described_class.new(key)
expect(service.update?).to eq(true)
expect(service.update?).to eq(false)
end
it 'returns false when the key does not yet need to be updated' do
key = build(:key, last_used_at: 1.minute.ago)
service = described_class.new(key)
expect(service.update?).to eq(false)
end
end
end
...@@ -52,7 +52,7 @@ describe MergeRequests::CloseService do ...@@ -52,7 +52,7 @@ describe MergeRequests::CloseService do
end end
end end
it 'refreshes the number of open merge requests for a valid MR' do it 'refreshes the number of open merge requests for a valid MR', :use_clean_rails_memory_store_caching do
service = described_class.new(project, user, {}) service = described_class.new(project, user, {})
expect { service.execute(merge_request) } expect { service.execute(merge_request) }
......
...@@ -37,7 +37,7 @@ describe MergeRequests::CreateService do ...@@ -37,7 +37,7 @@ describe MergeRequests::CreateService do
expect(service).to have_received(:execute_hooks).with(merge_request) expect(service).to have_received(:execute_hooks).with(merge_request)
end end
it 'refreshes the number of open merge requests' do it 'refreshes the number of open merge requests', :use_clean_rails_memory_store_caching do
expect { service.execute } expect { service.execute }
.to change { project.open_merge_requests_count }.from(0).to(1) .to change { project.open_merge_requests_count }.from(0).to(1)
end end
......
...@@ -66,8 +66,8 @@ describe Projects::CountService do ...@@ -66,8 +66,8 @@ describe Projects::CountService do
describe '#cache_key' do describe '#cache_key' do
it 'returns the cache key as an Array' do it 'returns the cache key as an Array' do
allow(service).to receive(:cache_key_name).and_return('count_service') allow(service).to receive(:cache_key_name).and_return('foo')
expect(service.cache_key).to eq(['projects', 1, 'count_service']) expect(service.cache_key).to eq(['projects', 'count_service', described_class::VERSION, 1, 'foo'])
end end
end end
end end
...@@ -232,7 +232,9 @@ describe SystemNoteService do ...@@ -232,7 +232,9 @@ describe SystemNoteService do
context 'when milestone added' do context 'when milestone added' do
it 'sets the note text' do it 'sets the note text' do
expect(subject.note).to eq "changed milestone to #{milestone.to_reference}" reference = milestone.to_reference(format: :iid)
expect(subject.note).to eq "changed milestone to #{reference}"
end end
end end
......
require 'spec_helper'
describe UseKeyWorker do
describe "#perform" do
it "updates the key's last_used_at attribute to the current time when it exists" do
worker = described_class.new
key = create(:key)
current_time = Time.zone.now
Timecop.freeze(current_time) do
expect { worker.perform(key.id) }
.to change { key.reload.last_used_at }.from(nil).to be_like_time(current_time)
end
end
it "returns false and skips the job when the key doesn't exist" do
worker = described_class.new
key = create(:key)
expect(worker.perform(key.id + 1)).to eq false
end
end
end
...@@ -271,10 +271,6 @@ production: ...@@ -271,10 +271,6 @@ production:
--version="$CI_PIPELINE_ID-$CI_JOB_ID" \ --version="$CI_PIPELINE_ID-$CI_JOB_ID" \
"$name" \ "$name" \
chart/ chart/
if [[ "$track" == "stable" ]]; then
kubectl rollout status -n "$KUBE_NAMESPACE" -w "deployment/${CI_ENVIRONMENT_SLUG}-auto-deploy"
fi
} }
function install_dependencies() { function install_dependencies() {
......
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