Commit 617f00f8 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 3328-one-request-deploy-boards

* master: (52 commits)
  Auto DevOps docs cleanup
  Document that group Owners can always create subgroups
  Prevent a database update on Geo secondaries
  CE->EE: Fix merge conflicts
  Added QUestion Mark to Feature Promotion Call
  Make resolve discussion icon gray
  this spec will correctly fail when reverting the fix
  Deleted another Fixture due to User Callout
  Fixture Creation removed
  Deleted Karma Specs for userCallout
  Removed User Callout for Customer Experience
  rework the spec
  Fix the "resolve discussion in a new issue" button
  Upgrade gitlab-markup gem
  Use Helper
  Changed based on Feedback
  Fix image diff swipe handle offset to correctly align with the frame
  Stop using Sidekiq for updating Key#last_used_at
  Use gitlab-workhorse 3.1.0
  Remove redundant WHERE from event queries
  ...
parents dbb5bc16 5d31778c
......@@ -4,18 +4,19 @@ entry.
## 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 problems sanitizing URLs with empty passwords. !14083
- [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] 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 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] Normalize styles for empty state combo button.
- [FIXED] Fix broken svg in jobs dropdown for success status.
- [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)
- [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
......
......@@ -126,7 +126,7 @@ gem 'faraday_middleware-aws-signers-v4'
# Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.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 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 4.2'
......
......@@ -318,7 +318,7 @@ GEM
mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
gitlab-license (1.0.0)
gitlab-markup (1.5.1)
gitlab-markup (1.6.2)
gitlab_omniauth-ldap (2.0.4)
net-ldap (~> 0.16)
omniauth (~> 1.3)
......@@ -1064,7 +1064,7 @@ DEPENDENCIES
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0)
gitlab-markup (~> 1.5.1)
gitlab-markup (~> 1.6.2)
gitlab_omniauth-ldap (~> 2.0.4)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
......
......@@ -134,8 +134,9 @@
width: maxWidth + 1,
height: maxHeight + 2
});
// Set swipeBar left position to match image frame
$swipeBar.css({
left: 0
left: 1
});
wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
......
......@@ -375,8 +375,6 @@ header.navbar-gitlab-new {
display: flex;
width: 100%;
position: relative;
padding-top: $gl-padding;
padding-bottom: $gl-padding;
align-items: center;
border-bottom: 1px solid $border-color;
}
......@@ -388,6 +386,11 @@ header.navbar-gitlab-new {
align-self: center;
color: $gl-text-color-secondary;
@media (max-width: $screen-xs-max) {
padding-left: 17px;
border-left: 1px solid $gl-text-color-quaternary;
}
.avatar-tile {
margin-right: 4px;
border: 1px solid $border-color;
......
......@@ -445,9 +445,8 @@ $new-sidebar-collapsed-width: 50px;
background-color: transparent;
border: 0;
padding: 6px 16px;
margin: 0 16px 0 -15px;
margin: 0 0 0 -15px;
height: 46px;
border-right: 1px solid $gl-text-color-quaternary;
i {
font-size: 20px;
......@@ -455,7 +454,12 @@ $new-sidebar-collapsed-width: 50px;
}
@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 {
border-bottom-left-radius: 0;
}
.btn {
svg path {
fill: $gray-darkest;
}
}
.btn.discussion-create-issue-btn {
margin-left: -4px;
border-radius: 0;
......@@ -741,10 +747,6 @@ ul.notes {
border: 0;
}
}
.new-issue-for-discussion path {
fill: $gray-darkest;
}
}
}
......@@ -817,16 +819,6 @@ ul.notes {
vertical-align: middle;
}
.discussion-next-btn {
svg {
margin: 0;
path {
fill: $gray-darkest;
}
}
}
// Merge request notes in diffs
.diff-file {
// Diff is inline
......
......@@ -9,14 +9,12 @@ class Projects::ForksController < Projects::ApplicationController
def index
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
@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
@sort = params[:sort] || 'id_desc'
@forks = @forks.search(params[:filter_projects]) if params[:filter_projects].present?
@forks = @forks.order_by(@sort).page(params[:page])
@forks = forks.page(params[:page])
respond_to do |format|
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
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)
if milestone.expired?
content_tag(:strong, 'Past due')
......
class Event < ActiveRecord::Base
include Sortable
include IgnorableColumn
default_scope { reorder(nil).where.not(author_id: nil) }
default_scope { reorder(nil) }
CREATED = 1
UPDATED = 2
......@@ -83,6 +83,12 @@ class Event < ActiveRecord::Base
scope :merged, -> { where(action: MERGED) }
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'
# "data" will be removed in 10.0 but it may be possible that JOINs happen that
......
......@@ -310,8 +310,6 @@ class Issue < ActiveRecord::Base
end
def update_project_counter_caches
return unless update_project_counter_caches?
Projects::OpenIssuesCountService.new(project).refresh_cache
end
......
......@@ -4,8 +4,6 @@ class Key < ActiveRecord::Base
include Gitlab::CurrentSettings
include Sortable
LAST_USED_AT_REFRESH_TIME = 1.day.to_i
belongs_to :user
before_validation :generate_fingerprint
......@@ -57,10 +55,7 @@ class Key < ActiveRecord::Base
end
def update_last_used_at
lease = Gitlab::ExclusiveLease.new("key_update_last_used_at:#{id}", timeout: LAST_USED_AT_REFRESH_TIME)
return unless lease.try_obtain
UseKeyWorker.perform_async(id)
Keys::LastUsedService.new(self).execute
end
def add_to_shell
......
......@@ -987,8 +987,6 @@ class MergeRequest < ActiveRecord::Base
end
def update_project_counter_caches
return unless update_project_counter_caches?
Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache
end
......
......@@ -166,9 +166,7 @@ class Milestone < ActiveRecord::Base
# Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1"
# Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1"
#
def to_reference(from_project = nil, format: :iid, full: false)
return if group_milestone? && format != :name
def to_reference(from_project = nil, format: :name, full: false)
format_reference = milestone_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
......@@ -245,6 +243,10 @@ class Milestone < ActiveRecord::Base
def milestone_format_reference(format = :iid)
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?('"')
%("#{name}")
else
......
......@@ -3,12 +3,6 @@ class PushEvent < Event
# different "action" value.
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.
#
# We're just validating the presence of the ID here as foreign key constraints
......
......@@ -184,6 +184,7 @@ class IssuableBaseService < BaseService
after_create(issuable)
execute_hooks(issuable)
invalidate_cache_counts(issuable, users: issuable.assignees)
issuable.update_project_counter_caches
end
issuable
......@@ -195,8 +196,6 @@ class IssuableBaseService < BaseService
def after_create(issuable)
# To be overridden by subclasses
issuable.update_project_counter_caches
end
def before_update(issuable)
......@@ -205,8 +204,6 @@ class IssuableBaseService < BaseService
def after_update(issuable)
# To be overridden by subclasses
issuable.update_project_counter_caches
end
def update(issuable)
......@@ -231,6 +228,10 @@ class IssuableBaseService < BaseService
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 }
# We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do
......@@ -251,6 +252,8 @@ class IssuableBaseService < BaseService
after_update(issuable)
issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update')
issuable.update_project_counter_caches if update_project_counters
end
end
......
......@@ -29,6 +29,7 @@ module Issues
todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close')
invalidate_cache_counts(issue, users: issue.assignees)
issue.update_project_counter_caches
end
issue
......
module Keys
class LastUsedService
prepend ::EE::Keys::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
todo_service.close_merge_request(merge_request, current_user)
execute_hooks(merge_request, 'close')
invalidate_cache_counts(merge_request, users: merge_request.assignees)
merge_request.update_project_counter_caches
end
merge_request
......
......@@ -2,6 +2,11 @@ module Projects
# Base class for the various service classes that count project data (e.g.
# issues or forks).
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)
@project = project
end
......@@ -37,7 +42,7 @@ module Projects
end
def cache_key
['projects', @project.id, cache_key_name]
['projects', 'count_service', VERSION, @project.id, cache_key_name]
end
end
end
......@@ -10,7 +10,7 @@
.row
.col-md-4
.info-well
.well-segment.admin-well
.well-segment.admin-well.admin-well-statistics
%h4 Statistics
%p
Forks
......@@ -46,7 +46,7 @@
= number_with_delimiter(User.active.count)
.col-md-4
.info-well
.well-segment.admin-well
.well-segment.admin-well.admin-well-features
%h4 Features
- sign_up = "Sign up"
%p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") }
......@@ -126,6 +126,10 @@
GitLab API
%span.pull-right
= API::API::version
%p
Gitaly
%span.pull-right
= Gitlab::GitalyClient.expected_server_version
- if Gitlab.config.pages.enabled
%p
GitLab Pages
......
......@@ -10,9 +10,6 @@
= render "projects/last_push"
%div{ class: container_class }
- if show_callout?('user_callout_dismissed')
= render 'shared/user_callout'
- if has_projects_or_name?(@projects, params)
= render 'dashboard/projects_head'
= render 'projects'
......
- 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.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve all discussions in new issue",
"aria-label" => "Resolve all discussions in a new issue",
"data-container" => "body" }
= 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'
= 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',
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 @@
%new-issue-for-discussion-btn{ ":discussion-id" => "'#{discussion.id}'",
"inline-template" => true }
.btn-group{ role: "group", "v-if" => "showButton" }
.btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve this discussion in a new issue",
"aria-label" => "Resolve this discussion in a new issue",
"data-container" => "body" }
= 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'
= 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',
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 @@
%h4.prepend-top-0.warning-title
Change username
%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
= form_for @user, url: update_username_profile_path, method: :put, html: {class: "update-username"} do |f|
.form-group
......
......@@ -24,7 +24,7 @@
- if issue.milestone
%span.issuable-milestone.hidden-xs
&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')
= issue.milestone.title
- if issue.due_date
......
......@@ -23,7 +23,7 @@
- if merge_request.milestone
%span.issuable-milestone.hidden-xs
&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')
= merge_request.milestone.title
- if merge_request.target_project.default_branch != merge_request.target_branch
......
......@@ -22,11 +22,9 @@
- if @group.persisted?
.alert.alert-warning.prepend-top-10
%ul
%li Changing group path can have unintended side effects.
%li Renaming group path will rename directory for all related projects
%li It will change web url for access group and group projects.
%li It will change the git path to repositories under this group.
Changing group path can have unintended side effects.
= succeed '.' do
= link_to 'Learn more', help_page_path('user/group/index', anchor: 'changing-a-groups-path'), target: '_blank'
.form-group.group-name-holder
= 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 @@
= icon('clock-o', 'aria-hidden': 'true')
%span.milestone-title
- 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
- else
None
......@@ -37,7 +37,7 @@
= link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
.value.hide-collapsed
- 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
%span.no-value None
......
......@@ -30,10 +30,10 @@
merge request from being merged before it's ready.
- if no_issuable_templates && can?(current_user, :push_code, issuable.project)
- if show_promotions?
= render 'shared/promotions/promote_issue_templates'
- else
- if @project.feature_available?(:issuable_default_templates)
%p.help-block
Add
= link_to 'description templates', help_page_path('user/project/description_templates'), tabindex: -1
to help your contributors communicate effectively!
- elsif show_promotions?
= render 'shared/promotions/promote_issue_templates'
......@@ -99,8 +99,6 @@
Snippets
%div{ class: container_class }
- if @user == current_user && show_callout?('user_callout_dismissed')
= render 'shared/user_callout'
.tab-content
#activity.tab-pane
.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: Fix a merge request validation error on forked projects.
merge_request: 2932
author:
type: fixed
---
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 @@
- [invalid_gpg_signature_update, 2]
- [create_gpg_signature, 2]
- [upload_checksum, 1]
- [use_key, 1]
- [repository_fork, 1]
- [repository_import, 1]
- [project_service, 1]
......
......@@ -4,9 +4,9 @@ Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
project_urls = [
'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/gnuwget/wget2.git',
'https://gitlab.com/Commit451/LabCoat.git',
'https://github.com/documentcloud/underscore.git',
'https://github.com/twitter/flight.git',
'https://github.com/twitter/typeahead.js.git',
......
......@@ -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) |
| `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
Stars a given project. Returns status code `304` if the project is already starred.
......
# Auto DevOps
> [Introduced][ce-37115] in GitLab 10.0. Auto DevOps is currently in Beta and
**not recommended for production use**. Access to the Container Registry is only
available while the pipeline is running. Restarting a pod, scaling a service, or
other actions which require on-going access **will fail** even for public
projects. On-going secure access is planned for a subsequent release.
Auto DevOps brings best practices to your project in an easy and default way. A
typical web project starts with Continuous Integration (CI), then adds automated
deployment to production, and maybe some time in the future adds some kind of
monitoring. With Auto DevOps, every project has a complete workflow, with
no configuration, including:
- [Auto Build](#auto-build)
- [Auto Test](#auto-test)
- [Auto Code Quality](#auto-code-quality)
- [Auto Review Apps](#auto-review-apps)
- [Auto Deploy](#auto-deploy)
- [Auto Monitoring](#-auto-monitoring)
DANGER: Auto DevOps is currently in **Beta** and _not recommended for production use_.
> [Introduced][ce-37115] in GitLab 10.0.
Auto DevOps automatically detects, builds, tests, deploys, and monitors your
applications.
## Overview
You will need [Kubernetes](https://kubernetes.io/) and
[Prometheus](https://prometheus.io/) to make full use of Auto DevOps, but
even projects using only [GitLab Runners](https://docs.gitlab.com/runner/) will
be able to make use of Auto Build, Auto Test, and Auto Code Quality.
Auto DevOps makes use of an open source tool called
[Herokuish](https://github.com/gliderlabs/herokuish) which uses [Heroku
buildpacks](https://devcenter.heroku.com/articles/buildpacks) to automatically
detect, build, and test applications. Auto DevOps supports all of the languages
and frameworks that are [supported by
Herokuish](https://github.com/gliderlabs/herokuish#buildpacks) such as Ruby,
Rails, Node, PHP, Python, and Java, and [custom buildpacks can be
specified](#using-custom-buildpacks). *GitLab is in no way affiliated with Heroku
or Glider Labs.*
Projects can [customize](#customizing) the process by specifying [custom
buildpacks](#custom-buildpack), [custom `Dockerfile`s](#custom-dockerfile),
[custom Helm charts](#custom-helm-chart), or even copying the complete CI/CD
configuration into your project to enable staging and canary deployments, and
more.
With Auto DevOps, the software development process becomes easier to set up
as every project can have a complete workflow from build to deploy and monitoring,
with minimal to zero configuration.
## Quick start
Comprised of a set of stages, Auto DevOps brings these best practices to your
project in an easy and automatic way:
If you are using GitLab.com, see our [quick start guide](quick_start_guide.md)
for using Auto DevOps with GitLab.com and an external Kubernetes cluster on
Google Cloud.
1. [Auto Build](#auto-build)
1. [Auto Test](#auto-test)
1. [Auto Code Quality](#auto-code-quality)
1. [Auto Review Apps](#auto-review-apps)
1. [Auto Deploy](#auto-deploy)
1. [Auto Monitoring](#auto-monitoring)
For self-hosted installations, the easiest way to make use of Auto DevOps is to
install GitLab inside a Kubernetes cluster using the [GitLab-Omnibus Helm
Chart](../../install/kubernetes/gitlab_omnibus.md) which automatically installs
and configures everything you need.
As Auto DevOps relies on many different components, it's good to have a basic
knowledge of the following:
## Prerequisites
- [Kubernetes](https://kubernetes.io/docs/home/)
- [Helm](https://docs.helm.sh/)
- [Docker](https://docs.docker.com)
- [GitLab Runner](https://docs.gitlab.com/runner/)
- [Prometheus](https://prometheus.io/docs/introduction/overview/)
You will need one or more GitLab Runners, a Kubernetes cluster, and Prometheus
installed in the cluster to make full use of Auto DevOps. If you do not have
Kubernetes or Prometheus installed then Auto Review Apps, Auto Deploy, and Auto
Monitoring will be silently skipped.
Auto DevOps provides great defaults for all the stages; you can, however,
[customize](#customizing) almost everything to your needs.
If you are using GitLab outside of Kubernetes, for example with GitLab.com, then
you should take these prerequisites into account:
## Prerequisites
TIP: **Tip:**
For self-hosted installations, the easiest way to make use of Auto DevOps is to
install GitLab inside a Kubernetes cluster using the [GitLab Omnibus Helm Chart]
which automatically installs and configures everything you need!
1. **Base domain** - You will need a base domain configured with wildcard DNS to
be used by all of your Auto DevOps applications.
To make full use of Auto DevOps, you will need:
1. **GitLab Runner** - Your Runner needs to be configured to be able to run Docker.
Generally this means using the
1. **GitLab Runner** (needed for all stages) - Your Runner needs to be
configured to be able to run Docker. Generally this means using the
[Docker](https://docs.gitlab.com/runner/executors/docker.html) or [Kubernetes
executor](https://docs.gitlab.com/runner/executors/kubernetes.html), with
[privileged mode enabled](https://docs.gitlab.com/runner/executors/docker.html#use-docker-in-docker-with-privileged-mode).
......@@ -78,35 +56,98 @@ you should take these prerequisites into account:
should be registered as [shared Runners](../../ci/runners/README.md#registering-a-shared-runner)
for the entire GitLab instance, or [specific Runners](../../ci/runners/README.md#registering-a-specific-runner)
that are assigned to specific projects.
1. **Kubernetes** - To enable deploys, you will need Kubernetes 1.5+, with NGINX
ingress and wildcard SSL termination, for example using the
[`nginx-ingress`](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress)
and [`kube-lego`](https://github.com/kubernetes/charts/tree/master/stable/kube-lego)
Helm charts respectively. The [Kubernetes service][kubernetes-service]
1. **Base domain** (needed for Auto Review Apps and Auto Deploy) - You will need
a domain configured with wildcard DNS which is gonna be used by all of your
Auto DevOps applications. [Read the specifics](#auto-devops-base-domain).
1. **Kubernetes** (needed for Auto Review Apps, Auto Deploy, and Auto Monitoring) -
To enable deployments, you will need Kubernetes 1.5+. The [Kubernetes service][kubernetes-service]
integration will need to be enabled for the project, or enabled as a
[default service template](../../user/project/integrations/services_templates.md)
for the entire GitLab installation.
1. **Prometheus** - To enable Auto Monitoring, you will need Prometheus installed
somewhere (inside or outside your cluster) and configured to scrape your
Kubernetes cluster. To get response metrics (in addition to system metrics),
you need to [configure Prometheus to monitor NGINX](../../user/project/integrations/prometheus_library/nginx_ingress.md#configuring-prometheus-to-monitor-for-nginx-ingress-metrics).
1. **A load balancer** - You can use NGINX ingress by deploying it to your
Kubernetes cluster using the
[`nginx-ingress`](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress)
Helm chart.
1. **Wildcard TLS termination** - You can deploy the
[`kube-lego`](https://github.com/kubernetes/charts/tree/master/stable/kube-lego)
Helm chart to your Kubernetes cluster to automatically issue certificates
for your domains using Let's Encrypt.
1. **Prometheus** (needed for Auto Monitoring) - To enable Auto Monitoring, you
will need Prometheus installed somewhere (inside or outside your cluster) and
configured to scrape your Kubernetes cluster. To get response metrics
(in addition to system metrics), you need to
[configure Prometheus to monitor NGINX](../../user/project/integrations/prometheus_library/nginx_ingress.md#configuring-prometheus-to-monitor-for-nginx-ingress-metrics).
The [Prometheus service](../../user/project/integrations/prometheus.md)
integration needs to be enabled for the project, or enabled as a
[default service template](../../user/project/integrations/services_templates.md)
for the entire GitLab installation.
## Enabling Auto DevOps
NOTE: **Note:**
If you do not have Kubernetes or Prometheus installed, then Auto Review Apps,
Auto Deploy, and Auto Monitoring will be silently skipped.
### Auto DevOps base domain
The Auto DevOps base domain is required if you want to make use of [Auto
Review Apps](#auto-review-apps) and [Auto Deploy](#auto-deploy). It is defined
under the project's CI/CD settings while [enabling Auto DevOps](#enabling-auto-devops).
It can also be set at the project or group level as a variable, `AUTO_DEVOPS_DOMAIN`.
A wildcard DNS A record matching the base domain is required, for example,
given a base domain of `example.com`, you'd need a DNS entry like:
```
*.example.com 3600 A 1.2.3.4
```
where `example.com` is the domain name under which the deployed apps will be served,
and `1.2.3.4` is the IP address of your load balancer; generally NGINX
([see prerequisites](#prerequisites)). How to set up the DNS record is beyond
the scope of this document; you should check with your DNS provider.
Once set up, all requests will hit the load balancer, which in turn will route
them to the Kubernetes pods that run your application(s).
In your GitLab.com project, go to **Settings > CI/CD** and find the Auto DevOps
section. Select "Enable Auto DevOps", add in your base domain, and save.
NOTE: **Note:**
If GitLab is installed using the [GitLab Omnibus Helm Chart], there are two
options: provide a static IP, or have one assigned. For more information see the
relevant docs on the [network prerequisites](../../install/kubernetes/gitlab_omnibus.md#networking-prerequisites).
![auto devops settings](img/auto_devops_settings.png)
## Quick start
If you are using GitLab.com, see our [quick start guide](quick_start_guide.md)
for using Auto DevOps with GitLab.com and an external Kubernetes cluster on
Google Cloud.
## Enabling Auto DevOps
NOTE: **Note:**
If you haven't done already, read the [prerequisites](#prerequisites) to make
full use of Auto DevOps. If this is your fist time, we recommend you follow the
[quick start guide](#quick-start).
1. Go to your project's **Settings > CI/CD > General pipelines settings** and
find the Auto DevOps section
1. Select "Enable Auto DevOps"
1. Optionally, but recommended, add in the [base domain](#auto-devops-base-domain)
that will be used by Kubernetes to deploy your application
1. Hit **Save changes** for the changes to take effect
Now that it's enabled, there are a few more steps depending on whether your project
has a `.gitlab-ci.yml` or not:
- **For projects with no `.gitlab-ci.yml` present:**
A pipeline needs to be triggered either by pushing a new commit to the
repository or manually visiting `https://example.gitlab.com/<username>/<project>/pipelines/new`
and creating a new pipeline for your default branch, generally `master`.
- **For projects with a `.gitlab-ci.yml` present:**
All you need to do is remove your existing `.gitlab-ci.yml`, and you can even
do that in a branch to test Auto DevOps before committing to `master`.
## Stages of Auto DevOps
The following sections describe the stages of Auto DevOps.
The following sections describe the stages of Auto DevOps. Read them carefully
to understand how each one works.
### Auto Build
......@@ -118,18 +159,24 @@ Auto Build creates a build of the application in one of two ways:
to automatically detect and build the application into a Docker image.
Either way, the resulting Docker image is automatically pushed to the
[Container Registry][container-registry], tagged with the commit SHA.
[Container Registry][container-registry] and tagged with the commit SHA.
CAUTION: **Important:**
If you are also using Auto Review Apps and Auto Deploy and choose to provide
your own `Dockerfile`, make sure you expose your application to port
`5000` as this is the port assumed by the default Helm chart.
### Auto Test
Auto Test automatically tests your application using
Auto Test automatically runs the appropriate tests for your application using
[Herokuish](https://github.com/gliderlabs/herokuish) and [Heroku
buildpacks](https://devcenter.heroku.com/articles/buildpacks). Auto Test will
analyze your project to detect the language and framework, and run appropriate
tests. Several languages and frameworks are detected automatically, but if your
language is not detected, you may succeed with a [custom
buildpack](#custom-buildpack).
buildpacks](https://devcenter.heroku.com/articles/buildpacks) by analyzing
your project to detect the language and framework. Several languages and
frameworks are detected automatically, but if your language is not detected,
you may succeed with a [custom buildpack](#custom-buildpacks). Check the
[currently supported languages](#currently-supported-languages).
NOTE: **Note:**
Auto Test uses tests you already have in your application. If there are no
tests, it's up to you to add them.
......@@ -137,99 +184,110 @@ tests, it's up to you to add them.
Auto Code Quality uses the open source
[`codeclimate` image](https://hub.docker.com/r/codeclimate/codeclimate/) to run
static analysis and other code checks on the current code, creating a report
that is uploaded as an artifact. In GitLab EE, differences between the source
and target branches are shown in the merge request widget. *GitLab is in no way
affiliated with Code Climate.*
static analysis and other code checks on the current code. The report is
created, and is uploaded as an artifact which you can later download and check
out. In GitLab Enterprise Edition Starter, differences between the source and
target branches are
[shown in the merge request widget](../../user/project/merge_requests/code_quality_diff.md).
### Auto Review Apps
Auto Review Apps create a [Review App][review-app] for each branch. Review Apps
are temporary application environments based on the branch's code so developers,
designers, QA, product managers, and other reviewers can actually see and
interact with code changes as part of the review process.
The review app will have a unique URL based on the project name, the branch
NOTE: **Note:**
This is an optional step, since many projects do not have a Kubernetes cluster
available. If the [prerequisites](#prerequisites) are not met, the job will
silently be skipped.
CAUTION: **Caution:**
Your apps should *not* be manipulated outside of Helm (using Kubernetes directly.)
This can cause confusion with Helm not detecting the change, and subsequent
deploys with Auto DevOps can undo your changes. Also, if you change something
and want to undo it by deploying again, Helm may not detect that anything changed
in the first place, and thus not realize that it needs to re-apply the old config.
[Review Apps][review-app] are temporary application environments based on the
branch's code so developers, designers, QA, product managers, and other
reviewers can actually see and interact with code changes as part of the review
process. Auto Review Apps create a Review App for each branch.
The Review App will have a unique URL based on the project name, the branch
name, and a unique number, combined with the Auto DevOps base domain. For
example, `user-project-branch-1234.example.com`. A link to the Review App shows
up in the merge request widget for easy discovery. When the branch is deleted,
for example after the merge request is merged, the Review App will automatically
be deleted.
This is an optional step, since many projects do not have a Kubernetes cluster
available. If the Kubernetes service is not configured, or if the variable
`AUTO_DEVOPS_DOMAIN` is not available (usually set automatically by the Auto
DevOps setting), the job will silently be skipped.
### Auto Deploy
After a branch or merge request is merged into `master`, Auto Deploy deploys the
application to a `production` environment in the Kubernetes cluster, with a
namespace based on the project name and unique project ID. e.g. `project-4321`.
NOTE: **Note:**
This is an optional step, since many projects do not have a Kubernetes cluster
available. If the Kubernetes service is not configured, or if the variable
`AUTO_DEVOPS_DOMAIN` is not available (usually set automatically by the Auto
DevOps setting), the job will silently be skipped.
available. If the [prerequisites](#prerequisites) are not met, the job will
silently be skipped.
CAUTION: **Caution:**
Your apps should *not* be manipulated outside of Helm (using Kubernetes directly.)
This can cause confusion with Helm not detecting the change, and subsequent
deploys with Auto DevOps can undo your changes. Also, if you change something
and want to undo it by deploying again, Helm may not detect that anything changed
in the first place, and thus not realize that it needs to re-apply the old config.
After a branch or merge request is merged into the project's default branch (usually
`master`), Auto Deploy deploys the application to a `production` environment in
the Kubernetes cluster, with a namespace based on the project name and unique
project ID, for example `project-4321`.
Auto Deploy doesn't include deployments to staging or canary by default, but the
Auto DevOps template contains job definitions for these tasks if you want to
[Auto DevOps template] contains job definitions for these tasks if you want to
enable them.
You can make use of [environment variables](#helm-chart-variables) to automatically
scale your pod replicas.
### Auto Monitoring
NOTE: **Note:**
Check the [prerequisites](#prerequisites) for Auto Monitoring to make this stage
work.
Once your application is deployed, Auto Monitoring makes it possible to monitor
your application's server and response metrics right out of the box. Auto
Monitoring uses [Prometheus](../../user/project/integrations/prometheus.md) to
get system metrics such as CPU and memory usage directly from
[Kubernetes](../../user/project/integrations/prometheus_library/kubernetes.md),
and response metrics such as HTTP error rates, latency, and throughput from the
[NGINX
server](../../user/project/integrations/prometheus_library/nginx_ingress.md).
[NGINX server](../../user/project/integrations/prometheus_library/nginx_ingress.md).
* Response Metrics: latency, throughput, error rate
* System Metrics: CPU utilization, memory utilization
The metrics include:
To view the metrics, open the [Monitoring dashboard for a deployed environment](../../ci/environments.md#monitoring-environments).
- **Response Metrics:** latency, throughput, error rate
- **System Metrics:** CPU utilization, memory utilization
![Auto Metrics](img/auto_monitoring.png)
### Configuring Auto Monitoring
If GitLab has been deployed using the
[omnibus-gitlab](../../install/kubernetes/gitlab_omnibus.md) Helm chart, no
If GitLab has been deployed using the [GitLab Omnibus Helm Chart], no
configuration is required.
If you have installed GitLab using a different method:
If you have installed GitLab using a different method, you need to:
1. [Deploy Prometheus](../../user/project/integrations/prometheus.md#configuring-your-own-prometheus-server-within-kubernetes) into your Kubernetes cluster
1. If you would like response metrics, ensure you are running at least version 0.9.0 of NGINX Ingress and [enable Prometheus metrics](https://github.com/kubernetes/ingress/blob/master/examples/customization/custom-vts-metrics/nginx/nginx-vts-metrics-conf.yaml).
1. Finally, [annotate](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) the NGINX Ingress deployment to be scraped by Prometheus using `prometheus.io/scrape: "true"` and `prometheus.io/port: "10254"`.
## Customizing
1. If you would like response metrics, ensure you are running at least version
0.9.0 of NGINX Ingress and
[enable Prometheus metrics](https://github.com/kubernetes/ingress/blob/master/examples/customization/custom-vts-metrics/nginx/nginx-vts-metrics-conf.yaml).
1. Finally, [annotate](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)
the NGINX Ingress deployment to be scraped by Prometheus using
`prometheus.io/scrape: "true"` and `prometheus.io/port: "10254"`.
### PostgreSQL Database Support
To view the metrics, open the
[Monitoring dashboard for a deployed environment](../../ci/environments.md#monitoring-environments).
In order to support applications that require a database,
[PostgreSQL][postgresql] is provisioned by default. Credentials to access the
database are preconfigured, but can be customized by setting the associated
[variables](#postgresql-variables). These credentials can be used for defining a
`DATABASE_URL` of the format:
`postgres://user:password@postgres-host:postgres-port/postgres-database`.
PostgreSQL provisioning can be disabled by creating a project variable
`POSTGRES_ENABLED` set to `false`.
#### PostgreSQL Variables
![Auto Metrics](img/auto_monitoring.png)
Any variables set at the project or group level will override variables set in
the CI/CD configuration.
## Customizing
1. `POSTGRES_ENABLED: "false"`: disable automatic deployment of PostgreSQL
1. `POSTGRES_USER: "my-user"`: use custom username for PostgreSQL
1. `POSTGRES_PASSWORD: "password"`: use custom password for PostgreSQL
1. `POSTGRES_DB: "my-database"`: use custom database name for PostgreSQL
While Auto DevOps provides great defaults to get you started, you can customize
almost everything to fit your needs; from custom [buildpacks](#custom-buildpacks),
to [`Dockerfile`s](#custom-dockerfile), [Helm charts](#custom-helm-chart), or
even copying the complete [CI/CD configuration](#customizing-gitlab-ci-yml)
into your project to enable staging and canary deployments, and more.
### Custom buildpack
### Custom buildpacks
If the automatic buildpack detection fails for your project, or if you want to
use a custom buildpack, you can override the buildpack using a project variable
......@@ -237,12 +295,12 @@ or a `.buildpack` file in your project:
- **Project variable** - Create a project variable `BUILDPACK_URL` with the URL
of the buildpack to use.
- **`.buildpack` file** - Add a file in your project's repo called `.buildpack`
and add the URL of the buildpack to use on a line in the file. If you want to
use multiple buildpacks, you can enter them in, one on each line
use multiple buildpacks, you can enter them in, one on each line.
>**Note:** Using multiple buildpacks may break Auto Test.
CAUTION: **Caution:**
Using multiple buildpacks isn't yet supported by Auto DevOps.
### Custom `Dockerfile`
......@@ -253,37 +311,138 @@ Dockerfile is based on [Alpine](https://hub.docker.com/_/alpine/).
### Custom Helm Chart
Auto DevOps uses Helm to deploy your application to Kubernetes. You can override
the Helm chart used by bundling up a chart into your project repo or by
specifying a project variable.
**Bundled chart** - If your project has a `chart` directory with a `Chart.yaml`
file in it, Auto DevOps will detect the chart and use it instead of the default
chart. This can be a great way to control exactly how your application is
deployed.
Auto DevOps uses [Helm](https://helm.sh/) to deploy your application to Kubernetes.
You can override the Helm chart used by bundling up a chart into your project
repo or by specifying a project variable:
**Project variable** - Create a project variable `AUTO_DEVOPS_CHART` with the
URL of a custom chart to use.
- **Bundled chart** - If your project has a `./charts` directory with a `Chart.yaml`
file in it, Auto DevOps will detect the chart and use it instead of the [default
one](https://gitlab.com/charts/charts.gitlab.io/tree/master/charts/auto-deploy-app).
This can be a great way to control exactly how your application is deployed.
- **Project variable** - Create a [project variable](../../ci/variables/README.md#secret-variables)
`AUTO_DEVOPS_CHART` with the URL of a custom chart to use.
### Enable staging, canaries, and more with custom `.gitlab-ci.yml`
### Customizing `.gitlab-ci.yml`
If you want to modify the CI/CD pipeline used by Auto DevOps, you can copy the
Auto DevOps template into your project's repo and edit as you see fit.
[Auto DevOps template] into your project's repo and edit as you see fit.
Assuming that your project is new or it doesn't have a `.gitlab-ci.yml` file
present:
From your project home page, click on the `Set up CI` button, or click on the `+`
button and `New file` and pick `.gitlab-ci.yml` as the template type, or view an
existing `.gitlab-ci.yml` file. Then select "Auto DevOps" from the template
dropdown. You will then be able to edit or add any jobs needed.
1. From your project home page, either click on the "Set up CI" button, or click
on the plus button and (`+`), then "New file"
1. Pick `.gitlab-ci.yml` as the template type
1. Select "Auto-DevOps" from the template dropdown
1. Edit the template or add any jobs needed
1. Give an appropriate commit message and hit "Commit changes"
For example, if you want deploys to go to a staging environment instead of
directly to a production environment, you can enable the `staging` job by
TIP: **Tip:** The Auto DevOps template includes useful comments to help you
customize it. For example, if you want deployments to go to a staging environment
instead of directly to a production one, you can enable the `staging` job by
renaming `.staging` to `staging`. Then make sure to uncomment the `when` key of
the `production` job to turn it into a manual action instead of deploying
automatically.
### PostgreSQL database support
In order to support applications that require a database,
[PostgreSQL][postgresql] is provisioned by default. The credentials to access
the database are preconfigured, but can be customized by setting the associated
[variables](#environment-variables). These credentials can be used for defining a
`DATABASE_URL` of the format:
```yaml
postgres://user:password@postgres-host:postgres-port/postgres-database
```
### Environment variables
The following variables can be used for setting up the Auto DevOps domain,
providing a custom Helm chart, or scaling your application. PostgreSQL can be
also be customized, and you can easily use a [custom buildpack](#custom-buildpacks).
| **Variable** | **Description** |
| ------------ | --------------- |
| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain); by default set automatically by the [Auto DevOps setting](#enabling-auto-devops). |
| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/charts.gitlab.io/tree/master/charts/auto-deploy-app). |
| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment; defaults to 1. |
| `CANARY_PRODUCTION_REPLICAS`| The number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md) in the production environment. |
| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. |
| `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. |
| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. |
| `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-variables-environment-variables). Set it to use a custom database name. |
| `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142`|
TIP: **Tip:**
Set up the replica variables using a
[project variable](../../ci/variables/README.md#secret-variables)
and scale your application by just redeploying it!
CAUTION: **Caution:**
You should *not* scale your application using Kubernetes directly. This can
cause confusion with Helm not detecting the change, and subsequent deploys with
Auto DevOps can undo your changes.
#### Advanced replica variables setup
Apart from the two replica-related variables for production mentioned above,
you can also use others for different environments.
There's a very specific mapping between Kubernetes' label named `track`,
GitLab CI/CD environment names, and the replicas environment variable.
The general rule is: `TRACK_ENV_REPLICAS`. Where:
- `TRACK`: The capitalized value of the `track`
[Kubernetes label](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)
in the Helm Chart app definition. If not set, it will not be taken into account
to the variable name.
- `ENV`: The capitalized environment name of the deploy job that is set in
`.gitlab-ci.yml`.
That way, you can define your own `TRACK_ENV_REPLICAS` variables with which
you will be able to scale the pod's replicas easily.
In the example below, the environment's name is `qa` which would result in
looking for the `QA_REPLICAS` environment variable:
```yaml
QA testing:
stage: deploy
environment:
name: qa
script:
- deploy qa
```
If, in addition, there was also a `track: foo` defined in the application's Helm
chart, like:
```yaml
replicaCount: 1
image:
repository: gitlab.example.com/group/project
tag: stable
pullPolicy: Always
secrets:
- name: gitlab-registry
application:
track: foo
tier: web
service:
enabled: true
name: web
type: ClusterIP
url: http://my.host.com/
externalPort: 5000
internalPort: 5000
```
then the environment variable would be `FOO_QA_REPLICAS`.
## Currently supported languages
>**Note:**
NOTE: **Note:**
Not all buildpacks support Auto Test yet, as it's a relatively new
enhancement. All of Heroku's [officially supported
languages](https://devcenter.heroku.com/articles/heroku-ci#currently-supported-languages)
......@@ -294,22 +453,28 @@ multi-buildpack does not.
As of GitLab 10.0, the supported buildpacks are:
```
* heroku-buildpack-multi v1.0.0
* heroku-buildpack-ruby v168
* heroku-buildpack-nodejs v99
* heroku-buildpack-clojure v77
* heroku-buildpack-python v99
* heroku-buildpack-java v53
* heroku-buildpack-gradle v23
* heroku-buildpack-scala v78
* heroku-buildpack-play v26
* heroku-buildpack-php v122
* heroku-buildpack-go v72
* heroku-buildpack-erlang fa17af9
* buildpack-nginx v8
- heroku-buildpack-multi v1.0.0
- heroku-buildpack-ruby v168
- heroku-buildpack-nodejs v99
- heroku-buildpack-clojure v77
- heroku-buildpack-python v99
- heroku-buildpack-java v53
- heroku-buildpack-gradle v23
- heroku-buildpack-scala v78
- heroku-buildpack-play v26
- heroku-buildpack-php v122
- heroku-buildpack-go v72
- heroku-buildpack-erlang fa17af9
- buildpack-nginx v8
```
## Private Project Support - Experimental
## Limitations
The following restrictions apply.
### Private project support
CAUTION: **Caution:** Private project support in Auto DevOps is experimental.
When a project has been marked as private, GitLab's [Container
Registry][container-registry] requires authentication when downloading
......@@ -319,27 +484,10 @@ Authentication credentials will be valid while the pipeline is running, allowing
for a successful initial deployment.
After the pipeline completes, Kubernetes will no longer be able to access the
container registry. **Restarting a pod, scaling a service, or other actions which
require on-going access to the registry will fail**. On-going secure access is
Container Registry. **Restarting a pod, scaling a service, or other actions which
require on-going access to the registry may fail**. On-going secure access is
planned for a subsequent release.
## Disable the banner instance wide
If an administrater would like to disable the banners on an instance level, this
feature can be disabled either through the console:
```basb
$ gitlab-rails console
[1] pry(main)> Feature.get(:auto_devops_banner_disabled).disable
=> true
```
Or through the HTTP API with the admin access token:
```
curl --data "value=true" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/features/auto_devops_banner_disabled
```
## Troubleshooting
- Auto Build and Auto Test may fail in detecting your language/framework. There
......@@ -347,13 +495,36 @@ curl --data "value=true" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://
key files the buildpack is looking for. For example, for ruby apps, you must
have a `Gemfile` to be properly detected, even though it is possible to write a
Ruby app without a `Gemfile`. Try specifying a [custom
buildpack](#custom-buildpack).
buildpack](#custom-buildpacks).
- Auto Test may fail because of a mismatch between testing frameworks. In this
case, you may need to customize your `.gitlab-ci.yml` with your test commands.
### Disable the banner instance wide
If an administrator would like to disable the banners on an instance level, this
feature can be disabled either through the console:
```sh
sudo gitlab-rails console
```
Then run:
```ruby
Feature.get(:auto_devops_banner_disabled).disable
```
Or through the HTTP API with an admin access token:
```sh
curl --data "value=true" --header "PRIVATE-TOKEN: private_token" https://gitlab.example.com/api/v4/features/auto_devops_banner_disabled
```
[ce-37115]: https://gitlab.com/gitlab-org/gitlab-ce/issues/37115
[kubernetes-service]: ../../user/project/integrations/kubernetes.md
[docker-in-docker]: ../../docker/using_docker_build.md#use-docker-in-docker-executor
[review-app]: ../../ci/review_apps/index.md
[container-registry]: ../../user/project/container_registry.md
[postgresql]: https://www.postgresql.org/
[Auto DevOps template]: https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Auto-DevOps.gitlab-ci.yml
[GitLab Omnibus Helm Chart]: ../../install/kubernetes/gitlab_omnibus.md
# Auto DevOps: quick start guide
> [Introduced][ce-37115] in GitLab 10.0. Auto DevOps is currently in Beta and
**not recommended for production use**.
DANGER: Auto DevOps is currently in **Beta** and _not recommended for production use_.
> [Introduced][ce-37115] in GitLab 10.0.
This is a step-by-step guide to deploying a project hosted on GitLab.com to
Google Cloud, using Auto DevOps.
We made a minimal [Ruby
application](https://gitlab.com/gitlab-examples/minimal-ruby-app) to use as an
example for this guide. It contains two files:
application](https://gitlab.com/auto-devops-examples/minimal-ruby-app) to use
as an example for this guide. It contains two main files:
* `server.rb` - our application. It will start an HTTP server on port 5000 and
render "Hello, world!"
......@@ -113,11 +114,9 @@ assigned to the cluster IP.
In your GitLab.com project, go to **Settings > CI/CD** and find the Auto DevOps
section. Select "Enable Auto DevOps", add in your base domain, and save.
![auto devops settings](img/auto_devops_settings.png)
Next, a pipeline needs to be triggered. Since the test project doesn't have a
`.gitlab-ci.yml`, you need to either push a change to the repository or
manually visit `https://gitlab.com/<username>/minimal-ruby-app/pipelines/run`,
manually visit `https://gitlab.com/<username>/minimal-ruby-app/pipelines/new`,
where `<username>` is your username.
This will create a new pipeline with several jobs: `build`, `test`, `codequality`,
......
......@@ -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
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
Add a security layer to your group by
......
......@@ -84,10 +84,13 @@ structure.
a subgroup. For more information check the [permissions table][permissions].
- For a list of words that are not allowed to be used as group names see the
[reserved names][reserved].
- Users can always create subgroups if they are explicitly added as an Owner to
a parent group even if group creation is disabled by an administrator in their
settings.
To create a subgroup:
1. In the group's dashboard go to the **Subgroups** page and click **Create subgroup**.
1. In the group's dashboard go to the **Subgroups** page and click **New subgroup**.
![Subgroups page](img/create_subgroup_button.png)
......@@ -100,9 +103,7 @@ To create a subgroup:
1. Click the **Create group** button and you will be taken to the new group's
dashboard page.
---
You can follow the same process to create any subsequent groups.
Follow the same process to create any subsequent groups.
## Membership
......
......@@ -8,10 +8,27 @@ experience according to the best approach to their cases.
Your `username` is a unique [`namespace`](../group/index.md#namespaces)
related to your user ID.
### Changing your username
You can change your `username` from your
[profile settings](#profile-settings). To avoid breaking
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).
[profile settings](#profile-settings).
> **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
......
......@@ -36,8 +36,15 @@ module EE
def clamp_approvals_before_merge(mr_params)
return mr_params unless mr_params[:approvals_before_merge]
target_project = @project.forked_from_project if @project.id.to_s != mr_params[:target_project_id]
target_project ||= @project
# Target the MR target project in priority, else it depends whether the project
# is forked.
target_project = if @merge_request
@merge_request.target_project
elsif @project.forked? && @project.id.to_s != mr_params[:target_project_id]
@project.forked_from_project
else
@project
end
if mr_params[:approvals_before_merge].to_i <= target_project.approvals_before_merge
mr_params[:approvals_before_merge] = nil
......
module EE
module Keys
module LastUsedService
def update?
raise NotImplementedError unless defined?(super)
!::Gitlab::Geo.secondary? && super
end
end
end
end
- if show_promotions? && show_callout?('promote_burndown_charts_dismissed')
- if show_project_feature_promotion?(:burndown_charts, 'promote_burndown_charts_dismissed')
.user-callout.promotion-callout#promote_burndown_charts{ data: { uid: 'promote_burndown_charts_dismissed' } }
.bordered-box.content-block
%button.btn.btn-default.close.js-close-callout{ type: 'button', 'aria-label' => 'Dismiss burndown charts promotion' }
......
.help-block
= _('Add')
= link_to 'description templates', 'javascript:void(0);', id: 'promotion-issue-template-link', data: {toggle: 'dropdown'}
= link_to 'description templates', 'javascript:void(0);', id: 'promotion-issue-template-link', data: {toggle: 'dropdown'}, tabindex: -1
= _('to help your contributors communicate effectively!')
.dropdown.promotion-issue-template
.dropdown-menu.promotion-issue-template-message
......
......@@ -76,8 +76,11 @@ module API
optional :import_url, type: String, desc: 'URL from which the project is imported'
end
def present_projects(options = {})
projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
def load_projects
ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
end
def present_projects(projects, options = {})
projects = reorder_projects(projects)
projects = projects.with_statistics if params[:statistics]
projects = projects.with_issues_enabled if params[:with_issues_enabled]
......@@ -117,7 +120,7 @@ module API
params[:user] = user
present_projects
present_projects load_projects
end
end
......@@ -130,7 +133,7 @@ module API
use :statistics_params
end
get do
present_projects
present_projects load_projects
end
desc 'Create new project' do
......@@ -235,6 +238,18 @@ module API
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
success Entities::Project
end
......
......@@ -79,7 +79,7 @@ module Backup
# - 1495527122_gitlab_backup.tar
# - 1495527068_2017_05_23_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
......
......@@ -29,6 +29,8 @@ module Gitlab
# http://gitlab.com/some/link/#1234, and code `puts #1234`'
#
class ReferenceRewriter
RewriteError = Class.new(StandardError)
def initialize(text, source_project, current_user)
@text = text
@source_project = source_project
......@@ -61,6 +63,10 @@ module Gitlab
cross_reference = build_cross_reference(referable, target_project)
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
substitution_valid?(new_text) ? cross_reference : reference
end
......
module Gitlab
module Git
class OperationService
include Gitlab::Git::Popen
WithBranchResult = Struct.new(:newrev, :repo_created, :branch_created) do
alias_method :repo_created?, :repo_created
alias_method :branch_created?, :branch_created
......@@ -150,7 +152,7 @@ module Gitlab
# (and have!) accidentally reset the ref to an earlier state, clobbering
# commits. See also https://github.com/libgit2/libgit2/issues/1534.
command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]
_, status = Gitlab::Popen.popen(
_, status = popen(
command,
repository.path) do |stdin|
stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00")
......
......@@ -5,17 +5,21 @@ require 'open3'
module Gitlab
module Git
module Popen
def popen(cmd, path)
def popen(cmd, path, vars = {})
unless cmd.is_a?(Array)
raise "System commands must be given as an array of strings"
end
vars = { "PWD" => path }
path ||= Dir.pwd
vars['PWD'] = path
options = { chdir: path }
@cmd_output = ""
@cmd_status = 0
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
yield(stdin) if block_given?
stdin.close
@cmd_output << stdout.read
@cmd_output << stderr.read
@cmd_status = wait_thr.value.exitstatus
......
......@@ -490,7 +490,7 @@ module Gitlab
# Not found -> ["", 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
......@@ -792,9 +792,7 @@ module Gitlab
end
command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]
message, status = Gitlab::Popen.popen(
command,
path) do |stdin|
message, status = popen(command, path) do |stdin|
stdin.write(instructions.join)
end
......
......@@ -3,6 +3,8 @@
module Gitlab
module Git
class RevList
include Gitlab::Git::Popen
attr_reader :oldrev, :newrev, :path_to_repo
def initialize(path_to_repo:, newrev:, oldrev: nil)
......@@ -26,7 +28,7 @@ module Gitlab
private
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?
raise "Got a non-zero exit code while calling out `#{args.join(' ')}`."
......
require 'spec_helper'
describe Projects::MergeRequestsController do
let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
let(:user) { project.owner }
let(:viewer) { user }
shared_examples 'approvals' do
def json_response
JSON.parse(response.body)
end
let!(:approver) { create(:approver, target: project) }
let!(:user_approver) { create(:approver, target: project, user: user) }
before do
sign_in(viewer)
merge_request.update_attribute :approvals_before_merge, 2
project.team << [approver.user, :developer]
end
context 'approvals' do
def json_response
JSON.parse(response.body)
describe 'approve' do
before do
post :approve,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid,
format: :json
end
let(:approver) { create(:user) }
before do
merge_request.update_attribute :approvals_before_merge, 2
project.team << [approver, :developer]
project.approver_ids = [user, approver].map(&:id).join(',')
it 'approves the merge request' do
approvals = json_response
expect(response).to be_success
expect(approvals['approvals_left']).to eq 1
expect(approvals['approved_by'].size).to eq 1
expect(approvals['approved_by'][0]['user']['username']).to eq user.username
expect(approvals['user_has_approved']).to be true
expect(approvals['user_can_approve']).to be false
expect(approvals['suggested_approvers'].size).to eq 1
expect(approvals['suggested_approvers'][0]['username']).to eq approver.user.username
end
end
describe 'approve' do
before do
post :approve,
describe 'approvals' do
let!(:approval) { create(:approval, merge_request: merge_request, user: approver.user) }
before do
get :approvals,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid,
format: :json
end
end
it 'approves the merge request' do
expect(response).to be_success
expect(json_response['approvals_left']).to eq 1
expect(json_response['approved_by'].size).to eq 1
expect(json_response['approved_by'][0]['user']['username']).to eq user.username
expect(json_response['user_has_approved']).to be true
expect(json_response['user_can_approve']).to be false
expect(json_response['suggested_approvers'].size).to eq 1
expect(json_response['suggested_approvers'][0]['username']).to eq approver.username
end
it 'shows approval information' do
approvals = json_response
expect(response).to be_success
expect(approvals['approvals_left']).to eq 1
expect(approvals['approved_by'].size).to eq 1
expect(approvals['approved_by'][0]['user']['username']).to eq approver.user.username
expect(approvals['user_has_approved']).to be false
expect(approvals['user_can_approve']).to be true
expect(approvals['suggested_approvers'].size).to eq 1
expect(approvals['suggested_approvers'][0]['username']).to eq user.username
end
end
describe 'approvals' do
before do
merge_request.approvals.create(user: approver)
get :approvals,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid,
format: :json
end
describe 'unapprove' do
let!(:approval) { create(:approval, merge_request: merge_request, user: user) }
it 'shows approval information' do
expect(response).to be_success
expect(json_response['approvals_left']).to eq 1
expect(json_response['approved_by'].size).to eq 1
expect(json_response['approved_by'][0]['user']['username']).to eq approver.username
expect(json_response['user_has_approved']).to be false
expect(json_response['user_can_approve']).to be true
expect(json_response['suggested_approvers'].size).to eq 1
expect(json_response['suggested_approvers'][0]['username']).to eq user.username
end
before do
delete :unapprove,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid,
format: :json
end
describe 'unapprove' do
before do
merge_request.approvals.create(user: user)
delete :unapprove,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid,
format: :json
end
it 'unapproves the merge request' do
approvals = json_response
it 'unapproves the merge request' do
expect(response).to be_success
expect(json_response['approvals_left']).to eq 2
expect(json_response['approved_by']).to be_empty
expect(json_response['user_has_approved']).to be false
expect(json_response['user_can_approve']).to be true
expect(json_response['suggested_approvers'].size).to eq 2
end
expect(response).to be_success
expect(approvals['approvals_left']).to eq 2
expect(approvals['approved_by']).to be_empty
expect(approvals['user_has_approved']).to be false
expect(approvals['user_can_approve']).to be true
expect(approvals['suggested_approvers'].size).to eq 2
end
end
end
describe Projects::MergeRequestsController do
let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
let(:user) { project.owner }
let(:viewer) { user }
before do
sign_in(viewer)
end
it_behaves_like 'approvals'
describe 'PUT update' do
before do
project.update_attributes(approvals_before_merge: 2)
end
def update_merge_request(params = {})
post :update,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
namespace_id: merge_request.target_project.namespace.to_param,
project_id: merge_request.target_project.to_param,
id: merge_request.iid,
merge_request: params
end
......@@ -126,7 +140,7 @@ describe Projects::MergeRequestsController do
let(:new_approver_group) { create(:approver_group) }
before do
project.team << [new_approver, :developer]
project.add_developer(new_approver)
project.update_attributes(disable_overriding_approvers_per_merge_request: true)
end
......@@ -150,7 +164,7 @@ describe Projects::MergeRequestsController do
end
end
context 'the approvals_before_merge param' do
shared_examples 'approvals_before_merge param' do
before do
project.update_attributes(approvals_before_merge: 2)
end
......@@ -162,12 +176,12 @@ describe Projects::MergeRequestsController do
end
it 'sets the param to nil' do
expect(merge_request.reload.approvals_before_merge).to eq(nil)
expect(merge_request.approvals_before_merge).to eq(nil)
end
it 'updates the merge request' do
expect(merge_request.reload).to be_valid
expect(response).to redirect_to(project_merge_request_path(project, merge_request))
expect(merge_request).to be_valid
expect(response).to redirect_to(project_merge_request_path(merge_request.target_project, merge_request))
end
end
......@@ -182,7 +196,7 @@ describe Projects::MergeRequestsController do
it 'updates the merge request' do
expect(merge_request.reload).to be_valid
expect(response).to redirect_to(project_merge_request_path(project, merge_request))
expect(response).to redirect_to(project_merge_request_path(merge_request.target_project, merge_request))
end
end
......@@ -197,7 +211,7 @@ describe Projects::MergeRequestsController do
it 'updates the merge request' do
expect(merge_request.reload).to be_valid
expect(response).to redirect_to(project_merge_request_path(project, merge_request))
expect(response).to redirect_to(project_merge_request_path(merge_request.target_project, merge_request))
end
end
end
......@@ -218,7 +232,7 @@ describe Projects::MergeRequestsController do
it 'updates the merge request' do
expect(merge_request.reload).to be_valid
expect(response).to redirect_to(project_merge_request_path(project, merge_request))
expect(response).to redirect_to(project_merge_request_path(merge_request.target_project, merge_request))
end
end
......@@ -233,7 +247,7 @@ describe Projects::MergeRequestsController do
it 'updates the merge request' do
expect(merge_request.reload).to be_valid
expect(response).to redirect_to(project_merge_request_path(project, merge_request))
expect(response).to redirect_to(project_merge_request_path(merge_request.target_project, merge_request))
end
end
......@@ -248,7 +262,7 @@ describe Projects::MergeRequestsController do
it 'updates the merge request' do
expect(merge_request.reload).to be_valid
expect(response).to redirect_to(project_merge_request_path(project, merge_request))
expect(response).to redirect_to(project_merge_request_path(merge_request.target_project, merge_request))
end
end
......@@ -263,11 +277,42 @@ describe Projects::MergeRequestsController do
it 'updates the merge request' do
expect(merge_request.reload).to be_valid
expect(response).to redirect_to(project_merge_request_path(project, merge_request))
expect(response).to redirect_to(project_merge_request_path(merge_request.target_project, merge_request))
end
end
end
end
context 'when the MR targets the project' do
it_behaves_like 'approvals_before_merge param'
end
context 'when the project is a fork' do
let(:upstream) { create(:project, :repository) }
let(:project) { create(:project, :repository, forked_from_project: upstream) }
context 'when the MR target upstream' do
let(:merge_request) { create(:merge_request, title: 'This is targeting upstream', source_project: project, target_project: upstream) }
before do
upstream.add_developer(user)
upstream.update_attributes(approvals_before_merge: 2)
end
it_behaves_like 'approvals_before_merge param'
end
context 'when the MR target the fork' do
let(:merge_request) { create(:merge_request, title: 'This is targeting the fork', source_project: project, target_project: project) }
before do
project.add_developer(user)
project.update_attributes(approvals_before_merge: 0)
end
it_behaves_like 'approvals_before_merge param'
end
end
end
describe 'POST merge' do
......@@ -353,6 +398,8 @@ describe Projects::MergeRequestsController do
fork_project.add_reporter(user)
end
it_behaves_like 'approvals'
context 'user cannot push to source branch' do
it 'returns 404' do
expect_rebase_worker_for(viewer).never
......
require 'spec_helper'
describe Keys::LastUsedService do
it 'does not run on Geo secondaries', :clean_gitlab_redis_shared_state do
key = create(:key, last_used_at: 1.year.ago)
original_time = key.last_used_at
allow(::Gitlab::Geo).to receive(:secondary?).and_return(true)
described_class.new(key).execute
expect(key.reload.last_used_at).to be_like_time(original_time)
end
end
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
context 'project milestones' do
let(:milestone) { create(:milestone, project: project) }
let(:reference) { milestone.to_reference }
let(:reference) { milestone.to_reference(format: :iid) }
include_examples 'reference parsing'
......
......@@ -26,6 +26,8 @@ describe Backup::Manager do
[
'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-pre_gitlab_backup.tar',
'1451520000_2015_12_31_4.5.6-rc1_gitlab_backup.tar',
'1451510000_2015_12_30_gitlab_backup.tar',
'1450742400_2015_12_22_gitlab_backup.tar',
'1449878400_gitlab_backup.tar',
......@@ -57,6 +59,30 @@ describe Backup::Manager do
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
before do
# Set to 30 days
......@@ -84,28 +110,30 @@ describe Backup::Manager do
it 'removes matching files with a human-readable versioned timestamp' do
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[3])
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[5])
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
expect(FileUtils).not_to have_received(:rm).with(files[0])
end
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
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
......@@ -121,14 +149,14 @@ describe Backup::Manager do
end
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[5])
expect(FileUtils).to have_received(:rm).with(files[6])
expect(FileUtils).to have_received(:rm).with(files[7])
end
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
it 'prints the error from file that could not be removed' do
......
......@@ -5,13 +5,13 @@ describe Gitlab::Checks::ForcePush 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
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
end
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)
end
......
require 'spec_helper'
describe Gitlab::Gfm::ReferenceRewriter do
let(:text) { 'some text' }
let(:old_project) { create(:project, name: 'old-project') }
let(:new_project) { create(:project, name: 'new-project') }
let(:group) { create(:group) }
let(:old_project) { create(:project, name: 'old-project', group: group) }
let(:new_project) { create(:project, name: 'new-project', group: group) }
let(:user) { create(:user) }
let(:old_project_ref) { old_project.to_reference(new_project) }
let(:text) { 'some text' }
before do
old_project.team << [user, :reporter]
end
......@@ -39,7 +42,7 @@ describe Gitlab::Gfm::ReferenceRewriter do
it { is_expected.not_to include merge_request.to_reference(new_project) }
end
context 'description ambigous elements' do
context 'rewrite ambigous references' do
context 'url' do
let(:url) { 'http://gitlab.com/#1' }
let(:text) { "This references #1, but not #{url}" }
......@@ -66,23 +69,21 @@ describe Gitlab::Gfm::ReferenceRewriter do
context 'description with project labels' do
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
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
context 'label referenced by text' do
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
context 'description with group labels' do
let(:old_group) { create(: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
old_project.update(namespace: old_group)
......@@ -90,21 +91,53 @@ describe Gitlab::Gfm::ReferenceRewriter do
context 'label referenced by id' do
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
context 'label referenced by text' do
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
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
let(:text) { 'milestone %"10.0"' }
it { is_expected.to eq text }
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
context 'reference contains milestone' do
let(:milestone) { create(:milestone) }
let(:text) { "milestone ref: #{milestone.to_reference}" }
let(:text) { 'milestone: %"9.0"' }
it { is_expected.to eq text }
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
......
......@@ -481,7 +481,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
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
@repo.delete_refs('refs/heads/fix')
......
......@@ -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) }
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,
"--git-dir=#{project.repository.path_to_repo}",
'rev-list',
......@@ -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) }
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,
"--git-dir=#{project.repository.path_to_repo}",
'rev-list',
......
......@@ -37,30 +37,17 @@ describe Key, :mailer do
end
describe "#update_last_used_at" do
let(:key) { create(:key) }
context 'when key was not updated during the last day' do
before do
allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain)
.and_return('000000')
end
it 'enqueues a UseKeyWorker job' do
expect(UseKeyWorker).to receive(:perform_async).with(key.id)
key.update_last_used_at
end
end
it 'updates the last used timestamp' do
key = build(:key)
service = double(:service)
expect(Keys::LastUsedService).to receive(:new)
.with(key)
.and_return(service)
context 'when key was updated during the last day' do
before do
allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain)
.and_return(false)
end
expect(service).to receive(:execute)
it 'does not enqueue a UseKeyWorker job' do
expect(UseKeyWorker).not_to receive(:perform_async)
key.update_last_used_at
end
key.update_last_used_at
end
end
end
......
......@@ -240,7 +240,7 @@ describe Milestone do
let(:milestone) { build_stubbed(:milestone, iid: 1, project: project, name: 'milestone') }
it 'returns a String reference to the object' do
expect(milestone.to_reference).to eq '%1'
expect(milestone.to_reference).to eq '%"milestone"'
end
it 'returns a reference by name when the format is set to :name' do
......@@ -248,24 +248,29 @@ describe Milestone do
end
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
context 'for a group milestone' do
let(:milestone) { build_stubbed(:milestone, iid: 1, group: group, name: 'milestone') }
it 'returns nil with the default format' do
expect(milestone.to_reference).to be_nil
it 'returns a group milestone reference with a default format' do
expect(milestone.to_reference).to eq '%"milestone"'
end
it 'returns a reference by name when the format is set to :name' do
expect(milestone.to_reference(format: :name)).to eq '%"milestone"'
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"'
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
......
......@@ -1232,6 +1232,59 @@ describe API::Projects do
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
describe "POST /projects/:id/share" do
......
......@@ -42,7 +42,7 @@ describe Issues::CloseService do
service.execute(issue)
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) }
.to change { project.open_issues_count }.from(1).to(0)
end
......
......@@ -35,7 +35,7 @@ describe Issues::CreateService do
expect(issue.due_date).to eq Date.tomorrow
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)
end
......
......@@ -64,6 +64,13 @@ describe Issues::UpdateService, :mailer do
expect(issue.due_date).to eq Date.tomorrow
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
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)
expect { described_class.new(key).execute }.not_to change { key.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
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, {})
expect { service.execute(merge_request) }
......
......@@ -37,7 +37,7 @@ describe MergeRequests::CreateService do
expect(service).to have_received(:execute_hooks).with(merge_request)
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 }
.to change { project.open_merge_requests_count }.from(0).to(1)
end
......
......@@ -66,8 +66,8 @@ describe Projects::CountService do
describe '#cache_key' do
it 'returns the cache key as an Array' do
allow(service).to receive(:cache_key_name).and_return('count_service')
expect(service.cache_key).to eq(['projects', 1, 'count_service'])
allow(service).to receive(:cache_key_name).and_return('foo')
expect(service.cache_key).to eq(['projects', 'count_service', described_class::VERSION, 1, 'foo'])
end
end
end
......@@ -232,7 +232,9 @@ describe SystemNoteService do
context 'when milestone added' 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
......
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:
--version="$CI_PIPELINE_ID-$CI_JOB_ID" \
"$name" \
chart/
if [[ "$track" == "stable" ]]; then
kubectl rollout status -n "$KUBE_NAMESPACE" -w "deployment/${CI_ENVIRONMENT_SLUG}-auto-deploy"
fi
}
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