Commit 80334707 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ce-to-ee-2017-07-20' into 'master'

CE upstream: Thursday

Closes gitlab-com/support-forum#2246

See merge request !2486
parents 913b4c7c bf80e15e
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-phantomjs-2.1-node-7.1-postgresql-9.6"
cache:
.default-cache: &default-cache
key: "ruby-233-with-yarn"
paths:
- vendor/ruby
- .yarn-cache/
.push-cache: &push-cache
cache:
<<: *default-cache
policy: push
.pull-cache: &pull-cache
cache:
<<: *default-cache
policy: pull
variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
ELASTIC_URL: "http://elasticsearch:9200"
......@@ -27,11 +37,11 @@ before_script:
- source scripts/prepare_build.sh
stages:
- build
- prepare
- test
- post-test
- pages
- build
- prepare
- test
- post-test
- pages
# Predefined scopes
.dedicated-runner: &dedicated-runner
......@@ -44,10 +54,6 @@ stages:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
KNAPSACK_S3_BUCKET: "gitlab-ce-cache"
cache:
key: "knapsack"
paths:
- knapsack/
artifacts:
expire_in: 31d
paths:
......@@ -84,8 +90,9 @@ stages:
- /(^docs[\/-].*|.*-docs$)/
.rspec-knapsack: &rspec-knapsack
stage: test
<<: *dedicated-runner
<<: *pull-cache
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[-2]}
......@@ -115,8 +122,9 @@ stages:
<<: *except-docs
.spinach-knapsack: &spinach-knapsack
stage: test
<<: *dedicated-runner
<<: *pull-cache
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[-2]}
......@@ -163,6 +171,7 @@ build-package:
USE_BUNDLE_INSTALL: "false"
EE_PACKAGE: "true"
stage: build
cache: {}
when: manual
script:
- scripts/trigger-build
......@@ -176,6 +185,11 @@ knapsack:
<<: *dedicated-runner
<<: *except-docs
stage: prepare
cache:
key: knapsack
paths:
- knapsack/
policy: pull
script:
- mkdir -p knapsack/${CI_PROJECT_NAME}/
- wget -O $KNAPSACK_RSPEC_SUITE_REPORT_PATH http://${KNAPSACK_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_RSPEC_SUITE_REPORT_PATH || rm $KNAPSACK_RSPEC_SUITE_REPORT_PATH
......@@ -188,6 +202,11 @@ update-knapsack:
<<: *dedicated-runner
<<: *only-canonical-masters
stage: post-test
cache:
key: knapsack
paths:
- knapsack/
policy: push
script:
- retry gem install fog-aws mime-types
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
......@@ -200,6 +219,8 @@ setup-test-env:
<<: *dedicated-runner
<<: *except-docs
stage: prepare
cache:
<<: *default-cache
script:
- node --version
- yarn install --pure-lockfile --cache-folder .yarn-cache
......@@ -279,6 +300,7 @@ spinach-mysql 4 5: *spinach-knapsack-mysql
# Static analysis jobs
.ruby-static-analysis: &ruby-static-analysis
<<: *pull-cache
variables:
SIMPLECOV: "false"
SETUP_DB: "false"
......@@ -287,6 +309,7 @@ spinach-mysql 4 5: *spinach-knapsack-mysql
<<: *ruby-static-analysis
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: test
script:
- bundle exec rake $CI_JOB_NAME
......@@ -303,9 +326,9 @@ static-analysis:
# - Check validity of relative links
# - Make sure cURL examples in API docs use the full switches
docs lint:
<<: *dedicated-runner
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine"
stage: test
<<: *dedicated-runner
cache: {}
dependencies: []
before_script: []
......@@ -328,9 +351,10 @@ downtime_check:
# DB migration, rollback, and seed jobs
.db-migrate-reset: &db-migrate-reset
stage: test
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: test
script:
- bundle exec rake db:migrate:reset
......@@ -343,11 +367,12 @@ db:migrate:reset-mysql:
<<: *use-mysql
.migration-paths: &migration-paths
stage: test
<<: *dedicated-runner
<<: *only-canonical-masters
<<: *pull-cache
stage: test
variables:
SETUP_DB: "false"
<<: *only-canonical-masters
script:
- git fetch origin v8.14.10-ee
- git checkout -f FETCH_HEAD
......@@ -368,9 +393,10 @@ migration:path-mysql:
<<: *use-mysql
.db-rollback: &db-rollback
stage: test
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: test
script:
- bundle exec rake db:rollback STEP=120
- bundle exec rake db:migrate
......@@ -384,9 +410,10 @@ db:rollback-mysql:
<<: *use-mysql
.db-seed_fu: &db-seed_fu
stage: test
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: test
variables:
SIZE: "1"
SETUP_DB: "false"
......@@ -411,9 +438,10 @@ db:seed_fu-mysql:
# Frontend-related jobs
gitlab:assets:compile:
stage: test
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: test
dependencies: []
variables:
NODE_ENV: "production"
......@@ -434,11 +462,12 @@ gitlab:assets:compile:
- webpack-report/
karma:
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-chrome-59.0-node-7.1-postgresql-9.6"
stage: test
<<: *use-pg
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-chrome-59.0-node-7.1-postgresql-9.6"
stage: test
variables:
BABEL_ENV: "coverage"
CHROME_LOG_FILE: "chrome_debug.log"
......@@ -456,6 +485,7 @@ karma:
codeclimate:
<<: *except-docs
<<: *pull-cache
before_script: []
image: docker:latest
stage: test
......@@ -471,10 +501,11 @@ codeclimate:
paths: [codeclimate.json]
coverage:
stage: post-test
services: []
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: post-test
services: []
variables:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true"
......@@ -491,6 +522,7 @@ coverage:
lint:javascript:report:
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
stage: post-test
before_script: []
script:
......@@ -503,9 +535,10 @@ lint:javascript:report:
- eslint-report.html
pages:
<<: *dedicated-runner
<<: *pull-cache
before_script: []
stage: pages
<<: *dedicated-runner
dependencies:
- coverage
- karma
......@@ -529,6 +562,7 @@ pages:
# rubygems.org in the future.
cache gems:
<<: *dedicated-runner
<<: *pull-cache
only:
- tags
variables:
......@@ -543,8 +577,9 @@ cache gems:
- master@gitlab-org/gitlab-ee
gitlab_git_test:
<<: *pull-cache
<<: *except-docs
variables:
SETUP_DB: "false"
script:
- spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
<<: *except-docs
......@@ -37,11 +37,13 @@ gem 'omniauth-saml', '~> 1.7.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'gssapi', group: :kerberos
gem 'omniauth-authentiq', '~> 0.3.0'
gem 'omniauth-authentiq', '~> 0.3.1'
gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt', '~> 1.5.6'
# Kerberos authentication. EE-only
gem 'gssapi', group: :kerberos
# Spam and anti-bot protection
gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
gem 'akismet', '~> 2.0'
......@@ -400,7 +402,7 @@ gem 'sys-filesystem', '~> 1.1.6'
gem 'net-ntp'
# Gitaly GRPC client
gem 'gitaly', '~> 0.14.0'
gem 'gitaly', '~> 0.17.0'
gem 'toml-rb', '~> 0.3.15', require: false
......
......@@ -293,7 +293,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly (0.14.0)
gitaly (0.17.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -517,7 +517,7 @@ GEM
rack (>= 1.0, < 3)
omniauth-auth0 (1.4.1)
omniauth-oauth2 (~> 1.1)
omniauth-authentiq (0.3.0)
omniauth-authentiq (0.3.1)
omniauth-oauth2 (~> 1.3, >= 1.3.1)
omniauth-azure-oauth2 (0.0.6)
jwt (~> 1.0)
......@@ -1006,7 +1006,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly (~> 0.14.0)
gitaly (~> 0.17.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0)
......@@ -1054,7 +1054,7 @@ DEPENDENCIES
oj (~> 2.17.4)
omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1)
omniauth-authentiq (~> 0.3.0)
omniauth-authentiq (~> 0.3.1)
omniauth-azure-oauth2 (~> 0.0.6)
omniauth-cas3 (~> 1.1.2)
omniauth-facebook (~> 4.0.0)
......
......@@ -2,6 +2,8 @@
/* global dateFormat */
/* global Pikaday */
import DateFix from './lib/utils/datefix';
class DueDateSelect {
constructor({ $dropdown, $loading } = {}) {
const $dropdownParent = $dropdown.closest('.dropdown');
......@@ -43,14 +45,13 @@ class DueDateSelect {
initDatePicker() {
const $dueDateInput = $(`input[name='${this.fieldName}']`);
const dateFix = DateFix.dashedFix($dueDateInput.val());
const calendar = new Pikaday({
field: $dueDateInput.get(0),
theme: 'gitlab-theme',
format: 'yyyy-mm-dd',
onSelect: (dateText) => {
const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd');
$dueDateInput.val(formattedDate);
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
......@@ -62,7 +63,7 @@ class DueDateSelect {
}
});
calendar.setDate(new Date($dueDateInput.val()));
calendar.setDate(dateFix);
this.$datePicker.append(calendar.el);
this.$datePicker.data('pikaday', calendar);
}
......@@ -168,6 +169,7 @@ class DueDateSelectors {
initMilestoneDatePicker() {
$('.datepicker').each(function() {
const $datePicker = $(this);
const dateFix = DateFix.dashedFix($datePicker.val());
const calendar = new Pikaday({
field: $datePicker.get(0),
theme: 'gitlab-theme animate-picker',
......@@ -177,7 +179,8 @@ class DueDateSelectors {
$datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
}
});
calendar.setDate(new Date($datePicker.val()));
calendar.setDate(dateFix);
$datePicker.data('pikaday', calendar);
});
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, vars-on-top, max-len */
import _ from 'underscore';
import Cookies from 'js-cookie';
import NewNavSidebar from './new_sidebar';
(function() {
var hideEndFade;
......@@ -53,6 +55,11 @@ import _ from 'underscore';
}
$(() => {
if (Cookies.get('new_nav') === 'true') {
const newNavSidebar = new NewNavSidebar();
newNavSidebar.bindEvents();
}
$(window).on('scroll', _.throttle(applyScrollNavClass, 100));
});
}).call(window);
const DateFix = {
dashedFix(val) {
const [y, m, d] = val.split('-');
return new Date(y, m - 1, d);
},
};
export default DateFix;
export default class NewNavSidebar {
constructor() {
this.initDomElements();
}
initDomElements() {
this.$sidebar = $('.nav-sidebar');
this.$overlay = $('.mobile-overlay');
this.$openSidebar = $('.toggle-mobile-nav');
this.$closeSidebar = $('.close-nav-button');
}
bindEvents() {
this.$openSidebar.on('click', () => this.toggleSidebarNav(true));
this.$closeSidebar.on('click', () => this.toggleSidebarNav(false));
this.$overlay.on('click', () => this.toggleSidebarNav(false));
}
toggleSidebarNav(show) {
this.$sidebar.toggleClass('nav-sidebar-expanded', show);
this.$overlay.toggleClass('mobile-nav-open', show);
}
}
......@@ -116,9 +116,12 @@
blockquote p {
color: $gl-grayish-blue !important;
margin: 0;
font-size: inherit;
line-height: 1.5;
&:last-child {
margin: 0;
}
}
p {
......
......@@ -275,8 +275,6 @@ header.navbar-gitlab-new {
.breadcrumbs {
display: flex;
min-height: 60px;
padding-top: $gl-padding-top;
padding-bottom: $gl-padding-top;
color: $gl-text-color;
border-bottom: 1px solid $border-color;
......@@ -300,6 +298,7 @@ header.navbar-gitlab-new {
display: flex;
width: 100%;
position: relative;
align-items: center;
.dropdown-menu-projects {
margin-top: -$gl-padding;
......
......@@ -26,6 +26,9 @@ $new-sidebar-width: 220px;
}
.context-header {
position: relative;
a {
border-bottom: 1px solid $border-color;
font-weight: 600;
display: flex;
......@@ -33,9 +36,8 @@ $new-sidebar-width: 220px;
padding: 10px 16px 10px 10px;
color: $gl-text-color;
.avatar-container {
flex: 0 0 40px;
background-color: $white-light;
@media (max-width: $screen-xs-max) {
padding-right: 30px;
}
&:hover {
......@@ -55,12 +57,44 @@ $new-sidebar-width: 220px;
}
}
}
}
.avatar-container {
flex: 0 0 40px;
background-color: $white-light;
}
.project-title,
.group-title {
overflow: hidden;
text-overflow: ellipsis;
}
&:hover {
.close-nav-button {
color: $white-light;
}
}
.close-nav-button {
display: none;
position: absolute;
top: 0;
right: 0;
height: 100%;
background-color: transparent;
border: 0;
padding: 0 10px;
@media (max-width: $screen-xs-max) {
display: block;
}
&:hover {
color: $gl-text-color;
}
}
}
.settings-avatar {
......@@ -79,7 +113,7 @@ $new-sidebar-width: 220px;
position: fixed;
z-index: 400;
width: $new-sidebar-width;
transition: width $sidebar-transition-duration;
transition: left $sidebar-transition-duration;
top: 50px;
bottom: 0;
left: 0;
......@@ -87,6 +121,10 @@ $new-sidebar-width: 220px;
background-color: $gray-normal;
box-shadow: inset -2px 0 0 $border-color;
&.nav-sidebar-expanded {
left: 0;
}
a {
transition: none;
text-decoration: none;
......@@ -117,7 +155,7 @@ $new-sidebar-width: 220px;
}
@media (max-width: $screen-xs-max) {
width: 0;
left: (-$new-sidebar-width);
}
}
......@@ -183,6 +221,38 @@ $new-sidebar-width: 220px;
}
}
.toggle-mobile-nav {
display: none;
background-color: transparent;
border: 0;
padding: 6px 16px;
margin: 0 16px 0 -15px;
height: 46px;
border-right: 1px solid $gl-text-color-quaternary;
i {
font-size: 20px;
color: $gl-text-color-secondary;
}
@media (max-width: $screen-xs-max) {
display: inline-block;
}
}
.mobile-overlay {
display: none;
&.mobile-nav-open {
display: block;
position: fixed;
background-color: $black-transparent;
height: 100%;
width: 100%;
z-index: 300;
}
}
// Make issue boards full-height now that sub-nav is gone
......
......@@ -376,3 +376,18 @@ table.u2f-registrations {
}
}
}
.nav-wip {
border: 1px solid $blue-500;
background: $blue-25;
padding: $gl-padding;
margin-bottom: $gl-padding;
a {
color: $blue-500;
}
p:last-child {
margin-bottom: 0;
}
}
......@@ -10,9 +10,9 @@ class Admin::HookLogsController < Admin::ApplicationController
end
def retry
status, message = hook.execute(hook_log.request_data, hook_log.trigger)
result = hook.execute(hook_log.request_data, hook_log.trigger)
set_hook_execution_notice(status, message)
set_hook_execution_notice(result)
redirect_to edit_admin_hook_path(@hook)
end
......
......@@ -38,9 +38,9 @@ class Admin::HooksController < Admin::ApplicationController
end
def test
status, message = hook.execute(sample_hook_data, 'system_hooks')
result = TestHooks::SystemService.new(hook, current_user, params[:trigger]).execute
set_hook_execution_notice(status, message)
set_hook_execution_notice(result)
redirect_back_or_default
end
......@@ -66,15 +66,4 @@ class Admin::HooksController < Admin::ApplicationController
:url
)
end
def sample_hook_data
{
event_name: "project_create",
name: "Ruby",
path: "ruby",
project_id: 1,
owner_name: "Someone",
owner_email: "example@gitlabhq.com"
}
end
end
......@@ -3,11 +3,14 @@ module HooksExecution
private
def set_hook_execution_notice(status, message)
if status && status >= 200 && status < 400
flash[:notice] = "Hook executed successfully: HTTP #{status}"
elsif status
flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}"
def set_hook_execution_notice(result)
http_status = result[:http_status]
message = result[:message]
if http_status && http_status >= 200 && http_status < 400
flash[:notice] = "Hook executed successfully: HTTP #{http_status}"
elsif http_status
flash[:alert] = "Hook executed successfully but returned HTTP #{http_status} #{message}"
else
flash[:alert] = "Hook execution failed: #{message}"
end
......
class Groups::HooksController < Groups::ApplicationController
include HooksExecution
# Authorize
before_action :group
before_action :authorize_admin_group!
......@@ -27,13 +29,11 @@ class Groups::HooksController < Groups::ApplicationController
def test
if @group.first_non_empty_project
status, message = TestHookService.new.execute(hook, current_user)
service = TestHooks::ProjectService.new(hook, current_user, 'push_events')
service.project = @group.first_non_empty_project
result = service.execute
if status
flash[:notice] = 'Hook successfully executed.'
else
flash[:alert] = "Hook execution failed: #{message}"
end
set_hook_execution_notice(result)
else
flash[:alert] = 'Hook execution failed. Ensure the group has a project with commits.'
end
......
......@@ -14,9 +14,9 @@ class Projects::HookLogsController < Projects::ApplicationController
end
def retry
status, message = hook.execute(hook_log.request_data, hook_log.trigger)
result = hook.execute(hook_log.request_data, hook_log.trigger)
set_hook_execution_notice(status, message)
set_hook_execution_notice(result)
redirect_to edit_project_hook_path(@project, @hook)
end
......
......@@ -9,6 +9,10 @@ class Projects::HooksController < Projects::ApplicationController
layout "project_settings"
def index
redirect_to project_settings_integrations_path(@project)
end
def create
@hook = @project.hooks.new(hook_params)
@hook.save
......@@ -33,13 +37,9 @@ class Projects::HooksController < Projects::ApplicationController
end
def test
if !@project.empty_repo?
status, message = TestHookService.new.execute(hook, current_user)
result = TestHooks::ProjectService.new(hook, current_user, params[:trigger]).execute
set_hook_execution_notice(status, message)
else
flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
end
set_hook_execution_notice(result)
redirect_back_or_default(default: { action: 'index' })
end
......
module HooksHelper
def link_to_test_hook(hook, trigger)
path = case hook
when ProjectHook
project = hook.project
test_project_hook_path(project, hook, trigger: trigger)
when SystemHook
test_admin_hook_path(hook, trigger: trigger)
end
trigger_human_name = trigger.to_s.tr('_', ' ').camelize
link_to path, rel: 'nofollow' do
content_tag(:span, trigger_human_name)
end
end
end
......@@ -100,6 +100,14 @@ module Ci
BuildSuccessWorker.perform_async(id)
end
end
before_transition any => [:failed] do |build|
next if build.retries_max.zero?
if build.retries_count < build.retries_max
Ci::Build.retry(build, build.user)
end
end
end
def detailed_status(current_user)
......@@ -134,6 +142,14 @@ module Ci
success? || failed? || canceled?
end
def retries_count
pipeline.builds.retried.where(name: self.name).count
end
def retries_max
self.options.fetch(:retry, 0).to_i
end
def latest?
!retried?
end
......
......@@ -4,4 +4,8 @@ module Editable
def is_edited?
last_edited_at.present? && last_edited_at != created_at
end
def last_edited_by
super || User.ghost
end
end
......@@ -4,4 +4,7 @@ class GroupHook < ProjectHook
self.singular_route_key = :hook
belongs_to :group
clear_validators!
validates :url, presence: true, url: true
end
......@@ -3,13 +3,22 @@ class ProjectHook < WebHook
self.singular_route_key = :hook
belongs_to :project
TRIGGERS = {
push_hooks: :push_events,
tag_push_hooks: :tag_push_events,
issue_hooks: :issues_events,
confidential_issue_hooks: :confidential_issues_events,
note_hooks: :note_events,
merge_request_hooks: :merge_requests_events,
job_hooks: :job_events,
pipeline_hooks: :pipeline_events,
wiki_page_hooks: :wiki_page_events
}.freeze
TRIGGERS.each do |trigger, event|
scope trigger, -> { where(event => true) }
end
scope :issue_hooks, -> { where(issues_events: true) }
scope :confidential_issue_hooks, -> { where(confidential_issues_events: true) }
scope :note_hooks, -> { where(note_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) }
scope :job_hooks, -> { where(job_events: true) }
scope :pipeline_hooks, -> { where(pipeline_events: true) }
scope :wiki_page_hooks, -> { where(wiki_page_events: true) }
belongs_to :project
validates :project, presence: true
end
class ServiceHook < WebHook
belongs_to :service
validates :service, presence: true
def execute(data, hook_name = 'service_hook')
WebHookService.new(self, data, hook_name).execute
......
class SystemHook < WebHook
scope :repository_update_hooks, -> { where(repository_update_events: true) }
TRIGGERS = {
repository_update_hooks: :repository_update_events,
push_hooks: :push_events,
tag_push_hooks: :tag_push_events
}.freeze
TRIGGERS.each do |trigger, event|
scope trigger, -> { where(event => true) }
end
default_value_for :push_events, false
default_value_for :repository_update_events, true
......
class WebHook < ActiveRecord::Base
include Sortable
default_value_for :push_events, true
default_value_for :issues_events, false
default_value_for :confidential_issues_events, false
default_value_for :note_events, false
default_value_for :merge_requests_events, false
default_value_for :tag_push_events, false
default_value_for :job_events, false
default_value_for :pipeline_events, false
default_value_for :repository_update_events, false
default_value_for :enable_ssl_verification, true
has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
scope :push_hooks, -> { where(push_events: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true) }
validates :url, presence: true, url: true
def execute(data, hook_name)
......
......@@ -35,13 +35,13 @@ class JenkinsService < CiService
def test(data)
begin
code, message = execute(data)
return { success: false, result: message } if code != 200
result = execute(data)
return { success: false, result: result[:message] } if result[:http_status] != 200
rescue StandardError => error
return { success: false, result: error }
end
{ success: true, result: message }
{ success: true, result: result[:message] }
end
def hook_url
......
......@@ -404,9 +404,11 @@ class User < ActiveRecord::Base
# Return (create if necessary) the ghost user. The ghost user
# owns records previously belonging to deleted users.
def ghost
unique_internal(where(ghost: true), 'ghost', 'ghost%s@example.com') do |u|
email = 'ghost%s@example.com'
unique_internal(where(ghost: true), 'ghost', email) do |u|
u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
u.name = 'Ghost User'
u.notification_email = email
end
end
end
......
......@@ -145,7 +145,7 @@ module Ci
end
def pipeline_created_counter
@pipeline_created_counter ||= Gitlab::Metrics.counter(:pipelines_created_count, "Pipelines created count")
@pipeline_created_counter ||= Gitlab::Metrics.counter(:pipelines_created_total, "Counter of pipelines created")
end
end
end
......@@ -4,7 +4,7 @@ class SystemHooksService
end
def execute_hooks(data, hooks_scope = :all)
SystemHook.send(hooks_scope).each do |hook|
SystemHook.public_send(hooks_scope).find_each do |hook|
hook.async_execute(data, 'system_hooks')
end
end
......
class TestHookService
def execute(hook, current_user)
data = Gitlab::DataBuilder::Push.build_sample(project(hook), current_user)
hook.execute(data, 'push_hooks')
end
private
def project(hook)
if hook.is_a? GroupHook
hook.group.first_non_empty_project
else
hook.project
end
end
end
module TestHooks
class BaseService
attr_accessor :hook, :current_user, :trigger
def initialize(hook, current_user, trigger)
@hook = hook
@current_user = current_user
@trigger = trigger
end
def execute
trigger_data_method = "#{trigger}_data"
if !self.respond_to?(trigger_data_method, true) ||
!hook.class::TRIGGERS.value?(trigger.to_sym)
return error('Testing not available for this hook')
end
error_message = catch(:validation_error) do
sample_data = self.__send__(trigger_data_method)
return hook.execute(sample_data, trigger)
end
error(error_message)
end
private
def error(message, http_status = nil)
result = {
message: message,
status: :error
}
result[:http_status] = http_status if http_status
result
end
end
end
module TestHooks
class ProjectService < TestHooks::BaseService
attr_writer :project
def project
@project ||= hook.project
end
private
def push_events_data
throw(:validation_error, 'Ensure the project has at least one commit.') if project.empty_repo?
Gitlab::DataBuilder::Push.build_sample(project, current_user)
end
alias_method :tag_push_events_data, :push_events_data
def note_events_data
note = project.notes.first
throw(:validation_error, 'Ensure the project has notes.') unless note.present?
Gitlab::DataBuilder::Note.build(note, current_user)
end
def issues_events_data
issue = project.issues.first
throw(:validation_error, 'Ensure the project has issues.') unless issue.present?
issue.to_hook_data(current_user)
end
alias_method :confidential_issues_events_data, :issues_events_data
def merge_requests_events_data
merge_request = project.merge_requests.first
throw(:validation_error, 'Ensure the project has merge requests.') unless merge_request.present?
merge_request.to_hook_data(current_user)
end
def job_events_data
build = project.builds.first
throw(:validation_error, 'Ensure the project has CI jobs.') unless build.present?
Gitlab::DataBuilder::Build.build(build)
end
def pipeline_events_data
pipeline = project.pipelines.first
throw(:validation_error, 'Ensure the project has CI pipelines.') unless pipeline.present?
Gitlab::DataBuilder::Pipeline.build(pipeline)
end
def wiki_page_events_data
page = project.wiki.pages.first
if !project.wiki_enabled? || page.blank?
throw(:validation_error, 'Ensure the wiki is enabled and has pages.')
end
Gitlab::DataBuilder::WikiPage.build(page, current_user, 'create')
end
end
end
module TestHooks
class SystemService < TestHooks::BaseService
private
def project
@project ||= begin
project = Project.first
throw(:validation_error, 'Ensure that at least one project exists.') unless project
project
end
end
def push_events_data
if project.empty_repo?
throw(:validation_error, "Ensure project \"#{project.human_name}\" has commits.")
end
Gitlab::DataBuilder::Push.build_sample(project, current_user)
end
def tag_push_events_data
if project.repository.tags.empty?
throw(:validation_error, "Ensure project \"#{project.human_name}\" has tags.")
end
Gitlab::DataBuilder::Push.build_sample(project, current_user)
end
def repository_update_events_data
commit = project.commit
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
unless commit
throw(:validation_error, "Ensure project \"#{project.human_name}\" has commits.")
end
change = Gitlab::DataBuilder::Repository.single_change(
commit.parent_id || Gitlab::Git::BLANK_SHA,
commit.id,
ref
)
Gitlab::DataBuilder::Repository.update(project, current_user, [change], [ref])
end
end
end
......@@ -50,10 +50,12 @@ module Users
def migrate_issues
user.issues.update_all(author_id: ghost_user.id)
Issue.where(last_edited_by_id: user.id).update_all(last_edited_by_id: ghost_user.id)
end
def migrate_merge_requests
user.merge_requests.update_all(author_id: ghost_user.id)
MergeRequest.where(merge_user_id: user.id).update_all(merge_user_id: ghost_user.id)
end
def migrate_notes
......
......@@ -39,7 +39,11 @@ class WebHookService
execution_duration: Time.now - start_time
)
[response.code, response.to_s]
{
status: :success,
http_status: response.code,
message: response.to_s
}
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
log_execution(
trigger: hook_name,
......@@ -52,7 +56,10 @@ class WebHookService
Rails.logger.error("WebHook Error => #{e}")
[nil, e.to_s]
{
status: :error,
message: e.to_s
}
end
def async_execute
......
......@@ -2,24 +2,10 @@ module WikiPages
class BaseService < ::BaseService
prepend EE::WikiPages::BaseService
def hook_data(page, action)
hook_data = {
object_kind: page.class.name.underscore,
user: current_user.hook_attrs,
project: @project.hook_attrs,
wiki: @project.wiki.hook_attrs,
object_attributes: page.hook_attrs
}
page_url = Gitlab::UrlBuilder.build(page)
hook_data[:object_attributes].merge!(url: page_url, action: action)
hook_data
end
private
def execute_hooks(page, action = 'create')
page_data = hook_data(page, action)
page_data = Gitlab::DataBuilder::WikiPage.build(page, current_user, action)
@project.execute_hooks(page_data, :wiki_page_hooks)
@project.execute_services(page_data, :wiki_page_hooks)
end
......
......@@ -12,7 +12,7 @@
= render partial: 'form', locals: { form: f, hook: @hook }
.form-actions
= f.submit 'Save changes', class: 'btn btn-create'
= link_to 'Test hook', test_admin_hook_path(@hook), class: 'btn btn-default'
= render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: @hook
= link_to 'Remove', admin_hook_path(@hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
%hr
......
......@@ -22,12 +22,12 @@
- @hooks.each do |hook|
%li
.controls
= link_to 'Test hook', test_admin_hook_path(hook), class: 'btn btn-sm'
= render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: hook, button_class: 'btn-small'
= link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm'
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm'
.monospace= hook.url
%div
- %w(repository_update_events push_events tag_push_events issues_events note_events merge_requests_events job_events).each do |trigger|
- if hook.send(trigger)
%span.label.label-gray= trigger.titleize
- SystemHook::TRIGGERS.each_value do |event|
- if hook.public_send(event)
%span.label.label-gray= event.to_s.titleize
%span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
......@@ -10,6 +10,8 @@
- if content_for?(:sub_nav)
= yield :sub_nav
.content-wrapper{ class: "#{(layout_nav_class unless show_new_nav?)}" }
- if show_new_nav?
.mobile-overlay
.alert-wrapper
= render "layouts/broadcast"
- if show_new_nav?
......
......@@ -3,6 +3,10 @@
%nav.breadcrumbs{ role: "navigation" }
.breadcrumbs-container{ class: [container_class, @content_class] }
- if defined?(@new_sidebar)
= button_tag class: 'toggle-mobile-nav', type: 'button' do
%span.sr-only Open sidebar
= icon ('bars')
.breadcrumbs-links.js-title-container
- unless hide_top_links
.title
......
.nav-sidebar
= link_to admin_root_path, title: 'Admin Overview', class: 'context-header' do
.context-header
= link_to admin_root_path, title: 'Admin Overview' do
.avatar-container.s40.settings-avatar
= icon('wrench')
.project-title Admin Area
= button_tag class: 'close-nav-button', type: 'button' do
%span.sr-only Close sidebar
= icon ('times')
%ul.sidebar-top-level-items
= nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
......
.nav-sidebar
= link_to group_path(@group), title: @group.name, class: 'context-header' do
.context-header
= link_to group_path(@group), title: @group.name do
.avatar-container.s40.group-avatar
= image_tag group_icon(@group), class: "avatar s40 avatar-tile"
.group-title
= @group.name
= button_tag class: 'close-nav-button', type: 'button' do
%span.sr-only Close sidebar
= icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
= link_to group_path(@group), title: 'About group' do
......
.nav-sidebar
= link_to profile_path, title: 'Profile Settings', class: 'context-header' do
.context-header
= link_to profile_path, title: 'Profile Settings' do
.avatar-container.s40.settings-avatar
= icon('user')
.project-title User Settings
= button_tag class: 'close-nav-button', type: 'button' do
%span.sr-only Close sidebar
= icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
......
.nav-sidebar
- can_edit = can?(current_user, :admin_project, @project)
= link_to project_path(@project), title: @project.name, class: 'context-header' do
.context-header
= link_to project_path(@project), title: @project.name do
.avatar-container.s40.project-avatar
= project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile')
.project-title
= @project.name
= button_tag class: 'close-nav-button', type: 'button' do
%span.sr-only Close sidebar
= icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
= link_to project_path(@project), title: 'About project', class: 'shortcuts-project' do
......
......@@ -24,6 +24,12 @@
%p
This setting allows you to turn on or off the new upcoming navigation concept.
.col-lg-8.syntax-theme
.nav-wip
%p
The new navigation is currently a work-in-progress concept and is currently only usable on wide-screens. There are a number of improvements that we are working on in order to further refine our navigation.
%p
%a{ href: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/32794', target: 'blank' } Learn more
about the improvements that are coming soon!
= label_tag do
.preview= image_tag "old_nav.png"
%input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? }
......
......@@ -7,6 +7,7 @@
%div{ class: container_class }
.top-area.adjust
- if can?(current_user, :admin_project, @project)
.nav-text
Protected branches can be managed in
= link_to 'project settings', project_protected_branches_path(@project)
......
......@@ -13,9 +13,10 @@
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
= f.submit 'Save changes', class: 'btn btn-create'
= link_to 'Test hook', test_project_hook_path(@project, @hook), class: 'btn btn-default'
= render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: @hook
= link_to 'Remove', project_hook_path(@project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
%hr
= render partial: 'projects/hook_logs/index', locals: { hook: @hook, hook_logs: @hook_logs, project: @project }
- show_controls = local_assigns.fetch(:show_controls, true)
- pipeline = @build.pipeline
.content-block.build-header.top-area
.content-block.build-header.top-area.page-content-header
.header-content
= render 'ci/status/badge', status: @build.detailed_status(current_user), link: false, title: @build.status_title
%strong
......
......@@ -3,14 +3,14 @@
.col-md-8.col-lg-7
%strong.light-header= hook.url
%div
- %w(push_events tag_push_events issues_events confidential_issues_events note_events merge_requests_events job_events pipeline_events wiki_page_events).each do |trigger|
- if hook.send(trigger)
%span.label.label-gray.deploy-project-label= trigger.titleize
- ProjectHook::TRIGGERS.each_value do |event|
- if hook.public_send(event)
%span.label.label-gray.deploy-project-label= event.to_s.titleize
.col-md-4.col-lg-5.text-right-lg.prepend-top-5
%span.append-right-10.inline
SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
= link_to "Edit", edit_project_hook_path(@project, hook), class: "btn btn-sm"
= link_to "Test", test_project_hook_path(@project, hook), class: "btn btn-sm"
= link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do
SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
= link_to 'Edit', edit_project_hook_path(@project, hook), class: 'btn btn-sm'
= render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: hook, button_class: 'btn-small'
= link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-transparent' do
%span.sr-only Remove
= icon('trash')
......@@ -16,6 +16,7 @@
Also, issues are searchable and filterable.
- if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue'
- else
= link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
- else
.text-center
......
- triggers = local_assigns.fetch(:triggers)
- button_class = local_assigns.fetch(:button_class, '')
- hook = local_assigns.fetch(:hook)
.hook-test-button.dropdown.inline
%button.btn{ 'data-toggle' => 'dropdown', class: button_class }
Test
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
- triggers.each_value do |event|
%li
= link_to_test_hook(hook, event)
---
title: Replaces dashboard/event_filters.feature spinach with rspec
merge_request: 12651
author: Alexander Randa (@randaalex)
---
title: Replaces dashboard/dashboard.feature spinach with rspec
merge_request: 12876
author: Alexander Randa (@randaalex)
---
title: Respect blockquote line breaks in markdown
merge_request:
author:
---
title: Add French translations of Commits Page
merge_request: 12409
author: Huang Tao
---
title: Add GitHub imported projects count to usage data
merge_request:
author:
---
title: Use Ghost user for last_edited_by and merge_user when original user is deleted
merge_request: 12933
author:
---
title: Add wip message to new navigation preference section
merge_request:
author:
---
title: Hide description about protected branches to non-member
merge_request: 12945
author: Takuya Noguchi
---
title: Allow testing any events for project hooks and system hooks
merge_request: 11728
author: Alexander Randa (@randaalex)
---
title: Allow to configure automatic retry of a failed CI/CD job
merge_request: 12909
author:
---
title: Fix docker tag reference routing constraints
merge_request: 12961
author:
......@@ -478,13 +478,13 @@ production: &base
# service_validate_url: '/cas/p3/serviceValidate',
# logout_url: '/cas/logout'} }
# - { name: 'authentiq',
# # for client credentials (client ID and secret), go to https://www.authentiq.com/
# # for client credentials (client ID and secret), go to https://www.authentiq.com/developers
# app_id: 'YOUR_CLIENT_ID',
# app_secret: 'YOUR_CLIENT_SECRET',
# args: {
# scope: 'aq:name email~rs address aq:push'
# # redirect_uri parameter is optional except when 'gitlab.host' in this file is set to 'localhost'
# # redirect_uri: 'YOUR_REDIRECT_URI'
# # callback_url parameter is optional except when 'gitlab.host' in this file is set to 'localhost'
# # callback_url: 'YOUR_CALLBACK_URL'
# }
# }
# - { name: 'github',
......
......@@ -307,7 +307,7 @@ constraints(ProjectUrlConstrainer.new) do
namespace :registry do
resources :repository, only: [] do
resources :tags, only: [:destroy],
constraints: { id: Gitlab::Regex.container_registry_reference_regex }
constraints: { id: Gitlab::Regex.container_registry_tag_regex }
end
end
......
class CleanStageIdReferenceMigration < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
##
# `MigrateStageIdReferenceInBackground` background migration cleanup.
#
def up
Gitlab::BackgroundMigration.steal('MigrateBuildStageIdReference')
end
def down
# noop
end
end
......@@ -32,7 +32,7 @@ Authentiq will generate a Client ID and the accompanying Client Secret for you t
"app_id" => "YOUR_CLIENT_ID",
"app_secret" => "YOUR_CLIENT_SECRET",
"args" => {
scope: 'aq:name email~rs aq:push'
"scope": 'aq:name email~rs address aq:push'
}
}
]
......@@ -45,21 +45,20 @@ Authentiq will generate a Client ID and the accompanying Client Secret for you t
app_id: 'YOUR_CLIENT_ID',
app_secret: 'YOUR_CLIENT_SECRET',
args: {
scope: 'aq:name email~rs aq:push'
scope: 'aq:name email~rs address aq:push'
}
}
```
5. The `scope` is set to request the user's name, email (required and signed), and permission to send push notifications to sign in on subsequent visits.
See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq#scopes-and-redirect-uri-configuration) for more information on scopes and modifiers.
See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq/wiki/Scopes,-callback-url-configuration-and-responses) for more information on scopes and modifiers.
6. Change `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` to the Client credentials you received in step 1.
7. Save the configuration file.
8. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source)
for the changes to take effect if you installed GitLab via Omnibus or from source respectively.
8. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect if you installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be an Authentiq icon below the regular sign in form. Click the icon to begin the authentication process.
......
......@@ -26,24 +26,25 @@ server, because the embedded server configuration is overwritten once every
In this experimental phase, only a few metrics are available:
| Metric | Type | Description |
| --------------------------------- | --------- | ----------- |
| db_ping_timeout | Gauge | Whether or not the last database ping timed out |
| db_ping_success | Gauge | Whether or not the last database ping succeeded |
| db_ping_latency_seconds | Gauge | Round trip time of the database ping |
| filesystem_access_latency_seconds | Gauge | Latency in accessing a specific filesystem |
| filesystem_accessible | Gauge | Whether or not a specific filesystem is accessible |
| filesystem_write_latency_seconds | Gauge | Write latency of a specific filesystem |
| filesystem_writable | Gauge | Whether or not the filesystem is writable |
| filesystem_read_latency_seconds | Gauge | Read latency of a specific filesystem |
| filesystem_readable | Gauge | Whether or not the filesystem is readable |
| http_requests_total | Counter | Rack request count |
| http_request_duration_seconds | Histogram | HTTP response time from rack middleware |
| rack_uncaught_errors_total | Counter | Rack connections handling uncaught errors count |
| redis_ping_timeout | Gauge | Whether or not the last redis ping timed out |
| redis_ping_success | Gauge | Whether or not the last redis ping succeeded |
| redis_ping_latency_seconds | Gauge | Round trip time of the redis ping |
| user_session_logins_total | Counter | Counter of how many users have logged in |
| Metric | Type | Since | Description |
|:--------------------------------- |:--------- |:----- |:----------- |
| db_ping_timeout | Gauge | 9.4 | Whether or not the last database ping timed out |
| db_ping_success | Gauge | 9.4 | Whether or not the last database ping succeeded |
| db_ping_latency_seconds | Gauge | 9.4 | Round trip time of the database ping |
| filesystem_access_latency_seconds | Gauge | 9.4 | Latency in accessing a specific filesystem |
| filesystem_accessible | Gauge | 9.4 | Whether or not a specific filesystem is accessible |
| filesystem_write_latency_seconds | Gauge | 9.4 | Write latency of a specific filesystem |
| filesystem_writable | Gauge | 9.4 | Whether or not the filesystem is writable |
| filesystem_read_latency_seconds | Gauge | 9.4 | Read latency of a specific filesystem |
| filesystem_readable | Gauge | 9.4 | Whether or not the filesystem is readable |
| http_requests_total | Counter | 9.4 | Rack request count |
| http_request_duration_seconds | Histogram | 9.4 | HTTP response time from rack middleware |
| pipelines_created_total | Counter | 9.4 | Counter of pipelines created |
| rack_uncaught_errors_total | Counter | 9.4 | Rack connections handling uncaught errors count |
| redis_ping_timeout | Gauge | 9.4 | Whether or not the last redis ping timed out |
| redis_ping_success | Gauge | 9.4 | Whether or not the last redis ping succeeded |
| redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping |
| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in |
[← Back to the main Prometheus page](index.md)
......
......@@ -1267,17 +1267,21 @@ endpoint can be accessed without authentication if the project is publicly
accessible.
```
GET /projects/search/:query
GET /projects
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `query` | string | yes | A string contained in the project name |
| `search` | string | yes | A string contained in the project name |
| `order_by` | string | no | Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects?search=test
```
## Start the Housekeeping task for a Project
>**Note:** This feature was introduced in GitLab 9.0
......
......@@ -395,6 +395,7 @@ job_name:
| after_script | no | Override a set of commands that are executed after job |
| environment | no | Defines a name of environment to which deployment is done by this job |
| coverage | no | Define code coverage settings for a given job |
| retry | no | Define how many times a job can be auto-retried in case of a failure |
### script
......@@ -1129,9 +1130,33 @@ A simple example:
```yaml
job1:
script: rspec
coverage: '/Code coverage: \d+\.\d+/'
```
### retry
**Notes:**
- [Introduced][ce-3442] in GitLab 9.5.
`retry` allows you to configure how many times a job is going to be retried in
case of a failure.
When a job fails, and has `retry` configured it is going to be processed again
up to the amount of times specified by the `retry` keyword.
If `retry` is set to 2, and a job succeeds in a second run (first retry), it won't be retried
again. `retry` value has to be a positive integer, equal or larger than 0, but
lower or equal to 2 (two retries maximum, three runs in total).
A simple example:
```yaml
test:
script: rspec
retry: 2
```
## Git Strategy
> Introduced in GitLab 8.9 as an experimental feature. May change or be removed
......@@ -1506,3 +1531,4 @@ CI with various languages.
[variables]: ../variables/README.md
[ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983
[ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447
[ce-3442]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3442
......@@ -1039,6 +1039,13 @@ X-Gitlab-Event: Build Hook
}
```
## Testing webhooks
You can trigger the webhook manually. Sample data from the project will be used.Sample data will take from the project.
> For example: for triggering `Push Events` your project should have at least one commit.
![Webhook testing](img/webhook_testing.png)
## Troubleshoot webhooks
Gitlab stores each perform of the webhook.
......@@ -1081,7 +1088,7 @@ Pick an unused port (e.g. 8000) and start the script: `ruby print_http_body.rb
8000`. Then add your server as a webhook receiver in GitLab as
`http://my.host:8000/`.
When you press 'Test Hook' in GitLab, you should see something like this in the
When you press 'Test' in GitLab, you should see something like this in the
console:
```
......
@dashboard
Feature: Dashboard
Background:
Given I sign in as a user
And I own project "Shop"
And project "Shop" has push event
And project "Shop" has CI enabled
And project "Shop" has CI build
And project "Shop" has labels: "bug", "feature", "enhancement"
And project "Shop" has issue: "bug report"
And I visit dashboard page
Scenario: I should see projects list
Then I should see "New Project" link
Then I should see "Shop" project link
Then I should see "Shop" project CI status
@javascript
Scenario: I should see activity list
And I visit dashboard activity page
Then I should see project "Shop" activity feed
Scenario: I should see groups list
Given I have group with projects
And I visit dashboard page
Then I should see groups list
@javascript
Scenario: I should see last push widget
Then I should see last push widget
And I click "Create Merge Request" link
Then I see prefilled new Merge Request page
@javascript
Scenario: Sorting Issues
Given I visit dashboard issues page
And I sort the list by "Oldest updated"
And I visit dashboard activity page
And I visit dashboard issues page
Then The list should be sorted by "Oldest updated"
@javascript
Scenario: Filtering Issues by label
Given project "Shop" has issue "Bugfix1" with label "feature"
When I visit dashboard issues page
And I filter the list by label "feature"
Then I should see "Bugfix1" in issues list
@javascript
Scenario: Visiting Project's issues after sorting
Given I visit dashboard issues page
And I sort the list by "Oldest updated"
And I visit project "Shop" issues page
Then The list should be sorted by "Oldest updated"
@javascript
Scenario: Sorting Merge Requests
Given I visit dashboard merge requests page
And I sort the list by "Oldest updated"
And I visit dashboard activity page
And I visit dashboard merge requests page
Then The list should be sorted by "Oldest updated"
@javascript
Scenario: Visiting Project's merge requests after sorting
Given project "Shop" has a "Bugfix MR" merge request open
And I visit dashboard merge requests page
And I sort the list by "Oldest updated"
And I visit project "Shop" merge requests page
Then The list should be sorted by "Oldest updated"
@dashboard
Feature: Event Filters
Background:
Given I sign in as a user
And I own a project
And this project has push event
And this project has new member event
And this project has merge request event
And I visit dashboard activity page
@javascript
Scenario: I should see all events
Then I should see push event
And I should see new member event
And I should see merge request event
@javascript
Scenario: I should see only pushed events
When I click "push" event filter
Then I should see push event
And I should not see new member event
And I should not see merge request event
@javascript
Scenario: I should see only joined events
When I click "team" event filter
Then I should see new member event
And I should not see push event
And I should not see merge request event
@javascript
Scenario: I should see only merged events
When I click "merge" event filter
Then I should see merge request event
And I should not see push event
And I should not see new member event
@javascript
Scenario: I should see only selected events while page reloaded
When I click "push" event filter
And I visit dashboard activity page
Then I should see push event
And I should not see new member event
When I click "team" event filter
And I visit dashboard activity page
Then I should not see push event
And I should see new member event
And I should not see merge request event
When I click "push" event filter
And I visit dashboard activity page
Then I should see push event
And I should not see new member event
And I should not see merge request event
When I click "merge" event filter
And I visit dashboard activity page
Then I should see merge request event
And I should not see push event
And I should not see new member event
class Spinach::Features::Dashboard < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
include SharedIssuable
step 'I should see "New Project" link' do
expect(page).to have_link "New project"
end
step 'I should see "Shop" project link' do
expect(page).to have_link "Shop"
end
step 'I should see "Shop" project CI status' do
expect(page).to have_link "Commit: skipped"
end
step 'I should see last push widget' do
expect(page).to have_content "You pushed to fix"
expect(page).to have_link "Create merge request"
end
step 'I click "Create merge request" link' do
find_link("Create merge request", visible: false).trigger('click')
end
step 'I see prefilled new Merge Request page' do
expect(page).to have_selector('.merge-request-form')
expect(current_path).to eq project_new_merge_request_path(@project)
expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s
expect(find("input#merge_request_source_branch").value).to eq "fix"
expect(find("input#merge_request_target_branch").value).to eq "master"
end
step 'I have group with projects' do
@group = create(:group)
@project = create(:empty_project, namespace: @group)
@event = create(:closed_issue_event, project: @project)
@project.team << [current_user, :master]
end
step 'I should see projects list' do
@user.authorized_projects.all.each do |project|
expect(page).to have_link project.name_with_namespace
end
end
step 'I should see groups list' do
Group.all.each do |group|
expect(page).to have_link group.name
end
end
step 'group has a projects that does not belongs to me' do
@forbidden_project1 = create(:empty_project, group: @group)
@forbidden_project2 = create(:empty_project, group: @group)
end
step 'I should see 1 project at group list' do
expect(find('span.last_activity/span')).to have_content('1')
end
step 'I filter the list by label "feature"' do
page.within ".labels-filter" do
find('.dropdown').click
click_link "feature"
end
end
step 'I should see "Bugfix1" in issues list' do
page.within "ul.content-list" do
expect(page).to have_content "Bugfix1"
end
end
step 'project "Shop" has issue "Bugfix1" with label "feature"' do
project = Project.find_by(name: "Shop")
issue = create(:issue, title: "Bugfix1", project: project, assignees: [current_user])
issue.labels << project.labels.find_by(title: 'feature')
end
end
class Spinach::Features::EventFilters < Spinach::FeatureSteps
include WaitForRequests
include SharedAuthentication
include SharedPaths
include SharedProject
step 'I should see push event' do
expect(page).to have_selector('span.pushed')
end
step 'I should not see push event' do
expect(page).not_to have_selector('span.pushed')
end
step 'I should see new member event' do
expect(page).to have_selector('span.joined')
end
step 'I should not see new member event' do
expect(page).not_to have_selector('span.joined')
end
step 'I should see merge request event' do
expect(page).to have_selector('span.accepted')
end
step 'I should not see merge request event' do
expect(page).not_to have_selector('span.accepted')
end
step 'this project has push event' do
data = {
before: Gitlab::Git::BLANK_SHA,
after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
ref: "refs/heads/new_design",
user_id: @user.id,
user_name: @user.name,
repository: {
name: @project.name,
url: "localhost/rubinius",
description: "",
homepage: "localhost/rubinius",
private: true
}
}
@event = Event.create(
project: @project,
action: Event::PUSHED,
data: data,
author_id: @user.id
)
end
step 'this project has new member event' do
user = create(:user, { name: "John Doe" })
Event.create(
project: @project,
author_id: user.id,
action: Event::JOINED
)
end
step 'this project has merge request event' do
merge_request = create :merge_request, author: @user, source_project: @project, target_project: @project
Event.create(
project: @project,
action: Event::MERGED,
target_id: merge_request.id,
target_type: "MergeRequest",
author_id: @user.id
)
end
When 'I click "push" event filter' do
wait_for_requests
click_link("Push events")
wait_for_requests
end
When 'I click "team" event filter' do
wait_for_requests
click_link("Team")
wait_for_requests
end
When 'I click "merge" event filter' do
wait_for_requests
click_link("Merge events")
wait_for_requests
end
end
......@@ -55,7 +55,7 @@ class Spinach::Features::GroupHooks < Spinach::FeatureSteps
step 'hook should be triggered' do
expect(current_path).to eq group_hooks_path(@group)
expect(page).to have_selector '.flash-notice',
text: 'Hook successfully executed.'
text: 'Hook executed successfully: HTTP 200'
end
step 'I should see hook error message' do
......
......@@ -239,11 +239,6 @@ module SharedProject
create(:label, project: project, title: 'enhancement')
end
step 'project "Shop" has issue: "bug report"' do
project = Project.find_by(name: "Shop")
create(:issue, project: project, title: "bug report")
end
step 'project "Shop" has CI enabled' do
project = Project.find_by(name: "Shop")
project.enable_ci
......
......@@ -83,7 +83,8 @@ module Ci
before_script: job[:before_script],
script: job[:script],
after_script: job[:after_script],
environment: job[:environment]
environment: job[:environment],
retry: job[:retry]
}.compact }
end
......
......@@ -26,7 +26,7 @@ module Gitlab
next unless migration_class == steal_class
begin
perform(migration_class, migration_args, retries: 3) if job.delete
perform(migration_class, migration_args) if job.delete
rescue Exception # rubocop:disable Lint/RescueException
BackgroundMigrationWorker # enqueue this migration again
.perform_async(migration_class, migration_args)
......
......@@ -11,7 +11,7 @@ module Gitlab
ALLOWED_KEYS = %i[tags script only except type image services allow_failure
type stage when artifacts cache dependencies before_script
after_script variables environment coverage].freeze
after_script variables environment coverage retry].freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
......@@ -23,6 +23,9 @@ module Gitlab
with_options allow_nil: true do
validates :tags, array_of_strings: true
validates :allow_failure, boolean: true
validates :retry, numericality: { only_integer: true,
greater_than_or_equal_to: 0,
less_than_or_equal_to: 2 }
validates :when,
inclusion: { in: %w[on_success on_failure always manual],
message: 'should be on_success, on_failure, ' \
......@@ -76,9 +79,9 @@ module Gitlab
helpers :before_script, :script, :stage, :type, :after_script,
:cache, :image, :services, :only, :except, :variables,
:artifacts, :commands, :environment, :coverage
:artifacts, :commands, :environment, :coverage, :retry
attributes :script, :tags, :allow_failure, :when, :dependencies
attributes :script, :tags, :allow_failure, :when, :dependencies, :retry
def compose!(deps = nil)
super do
......@@ -142,6 +145,7 @@ module Gitlab
environment: environment_defined? ? environment_value : nil,
environment_name: environment_defined? ? environment_value[:name] : nil,
coverage: coverage_defined? ? coverage_value : nil,
retry: retry_defined? ? retry_value.to_i : nil,
artifacts: artifacts_value,
after_script: after_script_value,
ignore: ignored? }
......
......@@ -74,6 +74,8 @@ module Gitlab
build(project, user, commits.last&.id, commits.first&.id, ref, commits)
end
private
def checkout_sha(repository, newrev, ref)
# Checkout sha is nil when we remove branch or tag
return if Gitlab::Git.blank_ref?(newrev)
......
module Gitlab
module DataBuilder
module WikiPage
extend self
def build(wiki_page, user, action)
wiki = wiki_page.wiki
{
object_kind: wiki_page.class.name.underscore,
user: user.hook_attrs,
project: wiki.project.hook_attrs,
wiki: wiki.hook_attrs,
object_attributes: wiki_page.hook_attrs.merge(
url: Gitlab::UrlBuilder.build(wiki_page),
action: action
)
}
end
end
end
end
......@@ -234,6 +234,8 @@ module Gitlab
@new_file = diff.from_id == BLANK_SHA
@renamed_file = diff.from_path != diff.to_path
@deleted_file = diff.to_id == BLANK_SHA
collapse! if diff.respond_to?(:collapsed) && diff.collapsed
end
def prune_diff_if_eligible
......
......@@ -7,16 +7,28 @@ module Gitlab
DEFAULT_LIMITS = { max_files: 100, max_lines: 5000 }.freeze
attr_reader :limits
delegate :max_files, :max_lines, :max_bytes, :safe_max_files, :safe_max_lines, :safe_max_bytes, to: :limits
def self.collection_limits(options = {})
limits = {}
limits[:max_files] = options.fetch(:max_files, DEFAULT_LIMITS[:max_files])
limits[:max_lines] = options.fetch(:max_lines, DEFAULT_LIMITS[:max_lines])
limits[:max_bytes] = limits[:max_files] * 5.kilobytes # Average 5 KB per file
limits[:safe_max_files] = [limits[:max_files], DEFAULT_LIMITS[:max_files]].min
limits[:safe_max_lines] = [limits[:max_lines], DEFAULT_LIMITS[:max_lines]].min
limits[:safe_max_bytes] = limits[:safe_max_files] * 5.kilobytes # Average 5 KB per file
OpenStruct.new(limits)
end
def initialize(iterator, options = {})
@iterator = iterator
@max_files = options.fetch(:max_files, DEFAULT_LIMITS[:max_files])
@max_lines = options.fetch(:max_lines, DEFAULT_LIMITS[:max_lines])
@max_bytes = @max_files * 5.kilobytes # Average 5 KB per file
@safe_max_files = [@max_files, DEFAULT_LIMITS[:max_files]].min
@safe_max_lines = [@max_lines, DEFAULT_LIMITS[:max_lines]].min
@safe_max_bytes = @safe_max_files * 5.kilobytes # Average 5 KB per file
@limits = self.class.collection_limits(options)
@enforce_limits = !!options.fetch(:limits, true)
@expanded = !!options.fetch(:expanded, true)
@from_gitaly = options.fetch(:from_gitaly, false)
@line_count = 0
@byte_count = 0
......@@ -26,11 +38,25 @@ module Gitlab
end
def each(&block)
Gitlab::GitalyClient.migrate(:commit_raw_diffs) do
each_patch(&block)
@array.each(&block)
return if @overflow
return if @iterator.nil?
Gitlab::GitalyClient.migrate(:commit_raw_diffs) do |is_enabled|
if is_enabled && @from_gitaly
each_gitaly_patch(&block)
else
each_rugged_patch(&block)
end
end
@populated = true
# Allow iterator to be garbage-collected. It cannot be reused anyway.
@iterator = nil
end
def empty?
any? # Make sure the iterator has been exercised
@empty
......@@ -74,23 +100,32 @@ module Gitlab
end
def over_safe_limits?(files)
files >= @safe_max_files || @line_count > @safe_max_lines || @byte_count >= @safe_max_bytes
files >= safe_max_files || @line_count > safe_max_lines || @byte_count >= safe_max_bytes
end
def each_patch
i = 0
@array.each do |diff|
yield diff
def each_gitaly_patch
i = @array.length
@iterator.each do |raw|
diff = Gitlab::Git::Diff.new(raw, expanded: !@enforce_limits || @expanded)
if raw.overflow_marker
@overflow = true
break
end
yield @array[i] = diff
i += 1
end
end
return if @overflow
return if @iterator.nil?
def each_rugged_patch
i = @array.length
@iterator.each do |raw|
@empty = false
if @enforce_limits && i >= @max_files
if @enforce_limits && i >= max_files
@overflow = true
break
end
......@@ -106,7 +141,7 @@ module Gitlab
@line_count += diff.line_count
@byte_count += diff.diff.bytesize
if @enforce_limits && (@line_count >= @max_lines || @byte_count >= @max_bytes)
if @enforce_limits && (@line_count >= max_lines || @byte_count >= max_bytes)
# This last Diff instance pushes us over the lines limit. We stop and
# discard it.
@overflow = true
......@@ -116,11 +151,6 @@ module Gitlab
yield @array[i] = diff
i += 1
end
@populated = true
# Allow iterator to be garbage-collected. It cannot be reused anyway.
@iterator = nil
end
end
end
......
......@@ -86,8 +86,8 @@ module Gitlab
feature.enabled?
end
def self.migrate(feature)
is_enabled = feature_enabled?(feature)
def self.migrate(feature, status: MigrationStatus::OPT_IN)
is_enabled = feature_enabled?(feature, status: status)
metric_name = feature.to_s
metric_name += "_gitaly" if is_enabled
......
......@@ -23,9 +23,13 @@ module Gitlab
def diff_from_parent(commit, options = {})
request_params = commit_diff_request_params(commit, options)
request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false)
request_params[:enforce_limits] = options.fetch(:limits, true)
request_params[:collapse_diffs] = request_params[:enforce_limits] || !options.fetch(:expanded, true)
request_params.merge!(Gitlab::Git::DiffCollection.collection_limits(options).to_h)
request = Gitaly::CommitDiffRequest.new(request_params)
response = GitalyClient.call(@repository.storage, :diff_service, :commit_diff, request)
Gitlab::Git::DiffCollection.new(GitalyClient::DiffStitcher.new(response), options)
Gitlab::Git::DiffCollection.new(GitalyClient::DiffStitcher.new(response), options.merge(from_gitaly: true))
end
def commit_deltas(commit)
......
module Gitlab
module GitalyClient
class Diff
FIELDS = %i(from_path to_path old_mode new_mode from_id to_id patch).freeze
FIELDS = %i(from_path to_path old_mode new_mode from_id to_id patch overflow_marker collapsed).freeze
attr_accessor(*FIELDS)
......
......@@ -20,17 +20,23 @@ module Gitlab
"It must start with letter, digit, emoji or '_'."
end
def container_registry_reference_regex
Gitlab::PathRegex.git_reference_regex
end
##
# Docker Distribution Registry 2.4.1 repository name rules
# Docker Distribution Registry repository / tag name rules
#
# See https://github.com/docker/distribution/blob/master/reference/regexp.go.
#
def container_repository_name_regex
@container_repository_regex ||= %r{\A[a-z0-9]+(?:[-._/][a-z0-9]+)*\Z}
end
##
# We do not use regexp anchors here because these are not allowed when
# used as a routing constraint.
#
def container_registry_tag_regex
@container_registry_tag_regex ||= /[\w][\w.-]{0,127}/
end
def environment_name_regex_chars
'a-zA-Z0-9_/\\$\\{\\}\\. -'
end
......
......@@ -43,6 +43,7 @@ module Gitlab
notes: Note.count,
pages_domains: PagesDomain.count,
projects: Project.count,
projects_imported_from_github: Project.where(import_type: 'github').count,
projects_prometheus_active: PrometheusService.active.count,
protected_branches: ProtectedBranch.count,
releases: Release.count,
......
......@@ -89,12 +89,12 @@ module Gitlab
end
def level_name(level)
level_name = 'Unknown'
level_name = N_('VisibilityLevel|Unknown')
options.each do |name, lvl|
level_name = name if lvl == level.to_i
end
level_name
s_(level_name)
end
def level_value(level)
......
......@@ -4,11 +4,11 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-28 13:32+0200\n"
"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-07-05 08:18-0400\n"
"PO-Revision-Date: 2017-07-13 08:13-0400\n"
"Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n"
"Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n"
"Language: bg\n"
......@@ -641,6 +641,12 @@ msgstr "Всички"
msgid "PipelineSchedules|Inactive"
msgstr "Неактивно"
msgid "PipelineSchedules|Input variable key"
msgstr "Въведете ключ за променливата"
msgid "PipelineSchedules|Input variable value"
msgstr "Въведете стойността на променливата"
msgid "PipelineSchedules|Next Run"
msgstr "Следващо изпълнение"
......@@ -650,12 +656,18 @@ msgstr "Нищо"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "Въведете кратко описание за тази схема"
msgid "PipelineSchedules|Remove variable row"
msgstr "Премахване на реда за променлива"
msgid "PipelineSchedules|Take ownership"
msgstr "Поемане на собствеността"
msgid "PipelineSchedules|Target"
msgstr "Цел"
msgid "PipelineSchedules|Variables"
msgstr "Променливи"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "собствен"
......@@ -1148,6 +1160,15 @@ msgstr "Няма достатъчно данни за този етап."
msgid "Withdraw Access Request"
msgstr "Оттегляне на заявката за достъп"
msgid ""
"You are going to remove %{group_name}.\n"
"Removed groups CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
msgstr ""
"На път сте да премахнете „%{group_name}“.\n"
"Ако я премахнете, групата НЕ може да бъде възстановена!\n"
"НАИСТИНА ли искате това?"
msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
......
......@@ -4,11 +4,11 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-28 13:32+0200\n"
"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-07-05 08:18-0400\n"
"PO-Revision-Date: 2017-07-13 08:46-0400\n"
"Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n"
"Language-Team: Esperanto (https://translate.zanata.org/project/view/GitLab)\n"
"Language: eo\n"
......@@ -642,6 +642,12 @@ msgstr "Ĉiuj"
msgid "PipelineSchedules|Inactive"
msgstr "Malŝaltitaj"
msgid "PipelineSchedules|Input variable key"
msgstr "Entajpu ŝlosilon por la variablo"
msgid "PipelineSchedules|Input variable value"
msgstr "Entajpu la valoron de la variablo"
msgid "PipelineSchedules|Next Run"
msgstr "Sekvanta plenumo"
......@@ -651,12 +657,18 @@ msgstr "Nenio"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "Entajpu mallongan priskribon pri ĉi tiu ĉenstablo"
msgid "PipelineSchedules|Remove variable row"
msgstr "Forigi la variablan linion"
msgid "PipelineSchedules|Take ownership"
msgstr "Akiri posedon"
msgid "PipelineSchedules|Target"
msgstr "Celo"
msgid "PipelineSchedules|Variables"
msgstr "Variabloj"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Propra"
......@@ -1150,6 +1162,15 @@ msgstr "Ne estas sufiĉe da datenoj por montri ĉi tiun etapon."
msgid "Withdraw Access Request"
msgstr "Nuligi la peton pri atingeblo"
msgid ""
"You are going to remove %{group_name}.\n"
"Removed groups CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
msgstr ""
"Vi forigos „%{group_name}“.\n"
"Oni NE POVAS malfari la forigon de grupo!\n"
"Ĉu vi estas ABSOLUTE certa?"
msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2017-07-12 12:35-0500\n"
"PO-Revision-Date: 2017-07-13 12:10-0500\n"
"Language-Team: Spanish\n"
"Language: es\n"
"MIME-Version: 1.0\n"
......@@ -1059,6 +1059,9 @@ msgstr "Privado"
msgid "VisibilityLevel|Public"
msgstr "Público"
msgid "VisibilityLevel|Unknown"
msgstr "Desconocido"
msgid "Want to see the data? Please ask an administrator for access."
msgstr "¿Quieres ver los datos? Por favor pide acceso al administrador."
......
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the gitlab package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# Dremor <egeorget@opmbx.org>, 2017. #zanata
# Huang Tao <htve@outlook.com>, 2017. #zanata
# Rémy Coutable <remy@rymai.me>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-06-14 04:21-0400\n"
"PO-Revision-Date: 2017-07-19 09:45-0400\n"
"Last-Translator: Dremor <egeorget@opmbx.org>\n"
"Language-Team: French (https://www.transifex.com/gitlab-fr/teams/75145/fr/)\n"
"Language-Team: French (https://translate.zanata.org/project/view/GitLab)\n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural ""
"%s additional commits have been omitted to prevent performance issues."
msgstr[0] ""
"%s validation supplémentaire a été masquée afin d'éviter de créer de "
"problèmes de performances."
msgstr[1] ""
"%s validations supplémentaires ont été masquées afin d'éviter de créer de "
"problèmes de performances."
msgid "%d commit"
msgid_plural "%d commits"
msgstr[0] "%d validation"
msgstr[1] "%d validations"
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "%{commit_author_link} a validé %{commit_timeago}"
msgid "1 pipeline"
msgid_plural "%d pipelines"
msgstr[0] "1 pipeline"
msgstr[1] "%d pipelines"
msgid "A collection of graphs regarding Continuous Integration"
msgstr "Un ensemble de graphiques concernant l’Intégration Continue (CI)"
msgid "About auto deploy"
msgstr "A propos de l'auto-déploiement"
msgid "Active"
msgstr "Actif"
msgid "Activity"
msgstr "Activité"
msgid "Add Changelog"
msgstr "Ajouter un journal des modifications"
msgid "Add Contribution guide"
msgstr "Ajouter un guide de contribution"
msgid "Add License"
msgstr "Ajouter une licence"
msgid "Add an SSH key to your profile to pull or push via SSH."
msgstr ""
"Ajoutez une clef SSH à votre profil pour pouvoir récupérer et pousser par "
"SSH."
msgid "Add new directory"
msgstr "Ajouter un nouveau dossier"
msgid "Archived project! Repository is read-only"
msgstr "Projet archivé ! Le dépôt est en lecture seule"
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "Êtes-vous sûr de vouloir supprimer ce pipeline programmé"
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Attachez un fichier par glisser &amp; déposer ou %{upload_link}"
msgid "Branch"
msgid_plural "Branches"
msgstr[0] "Branche"
msgstr[1] "Branches"
msgid ""
"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
"choose a GitLab CI Yaml template and commit your changes. "
"%{link_to_autodeploy_doc}"
msgstr ""
"La branche <strong>%{branch_name}</strong> a été crée. Pour mettre en place "
"le déploiement automatisé, sélectionnez un modèle de fichier Yaml pour "
"l'intégration continue (CI) de GitLab, et validez les modifications. "
"%{link_to_autodeploy_doc}"
msgid "BranchSwitcherPlaceholder|Search branches"
msgstr "Rechercher la branche"
msgid "BranchSwitcherTitle|Switch branch"
msgstr "Changer de branche"
msgid "Branches"
msgstr "Branches"
msgid "Browse Directory"
msgstr "Parcourir le dossier"
msgid "Browse File"
msgstr "Parcourir le fichier"
msgid "Browse Files"
msgstr "Parcourir les fichiers"
msgid "Browse files"
msgstr "Parcourir les fichiers"
msgid "ByAuthor|by"
msgstr "par"
msgid "CI configuration"
msgstr "Configuration de l'intégration continue (CI)"
msgid "Cancel"
msgstr "Annuler"
msgid "ChangeTypeActionLabel|Pick into branch"
msgstr "Sélectionner dans la branche"
msgid "ChangeTypeActionLabel|Revert in branch"
msgstr "Annuler dans la branche"
msgid "ChangeTypeAction|Cherry-pick"
msgstr "Sélectionner"
msgid "ChangeTypeAction|Revert"
msgstr "Annuler"
msgid "Changelog"
msgstr "Journal des modifications"
msgid "Charts"
msgstr "Graphiques"
msgid "Cherry-pick this commit"
msgstr "Sélectionner cette validation"
msgid "Cherry-pick this merge request"
msgstr "Sélectionner cette demande de fusion"
msgid "CiStatusLabel|canceled"
msgstr "annulé"
msgid "CiStatusLabel|created"
msgstr "créé"
msgid "CiStatusLabel|failed"
msgstr "échoué"
msgid "CiStatusLabel|manual action"
msgstr "action manuelle"
msgid "CiStatusLabel|passed"
msgstr "passé"
msgid "CiStatusLabel|passed with warnings"
msgstr "passé avec des avertissements"
msgid "CiStatusLabel|pending"
msgstr "en attente"
msgid "CiStatusLabel|skipped"
msgstr "ignoré"
msgid "CiStatusLabel|waiting for manual action"
msgstr "en attente d'action manuelle"
msgid "CiStatusText|blocked"
msgstr "bloqué"
msgid "CiStatusText|canceled"
msgstr "annulé "
msgid "CiStatusText|created"
msgstr "créé"
msgid "CiStatusText|failed"
msgstr "échoué"
msgid "CiStatusText|manual"
msgstr "manuel"
msgid "CiStatusText|passed"
msgstr "passé"
msgid "CiStatusText|pending"
msgstr "en attente"
msgid "CiStatusText|skipped"
msgstr "ignoré"
msgid "CiStatus|running"
msgstr "en cours"
msgid "Commit"
msgid_plural "Commits"
msgstr[0] "Validation"
msgstr[1] "Validations"
msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
msgstr "L’analyseur de cycle permet d’avoir une vue d’ensemble du temps nécessaire pour aller d’une idée à sa mise en production pour votre projet."
msgid "Commit duration in minutes for last 30 commits"
msgstr "Durée des 30 derniers pipelines en minutes"
msgid "Commit message"
msgstr "Message de validation"
msgid "CommitBoxTitle|Commit"
msgstr "Validation"
msgid "CommitMessage|Add %{file_name}"
msgstr "Ajout de %{file_name}"
msgid "Commits"
msgstr "Validations"
msgid "Commits feed"
msgstr "Flux de validations"
msgid "Commits|History"
msgstr "Historique"
msgid "Committed by"
msgstr "Validé par"
msgid "Compare"
msgstr "Comparer"
msgid "Contribution guide"
msgstr "Guilde de contribution"
msgid "Contributors"
msgstr "Contributeurs"
msgid "Copy URL to clipboard"
msgstr "Copier l'URL dans le presse-papier"
msgid "Copy commit SHA to clipboard"
msgstr "Copier le SHA de la validation"
msgid "Create New Directory"
msgstr "Créer un nouveau dossier"
msgid ""
"Create a personal access token on your account to pull or push via "
"%{protocol}."
msgstr ""
"Créer un jeton d’accès personnel pour votre compte afin de récupérer ou "
"pousser par %{protocol}."
msgid "Create directory"
msgstr "Créer un dossier"
msgid "Create empty bare repository"
msgstr "Créer un dépôt vide"
msgid "Create merge request"
msgstr "Créer une demande de fusion"
msgid "Create new..."
msgstr "Créer nouveau..."
msgid "CreateNewFork|Fork"
msgstr "Fourcher"
msgid "CreateTag|Tag"
msgstr "Étiquette"
msgid "CreateTokenToCloneLink|create a personal access token"
msgstr "Créer un jeton d'accès personnel"
msgid "Cron Timezone"
msgstr "Fuseau horaire de Cron"
msgid "Cron syntax"
msgstr "Syntaxe Cron"
msgid "Custom notification events"
msgstr "Événements de notification personnalisés"
msgid ""
"Custom notification levels are the same as participating levels. With custom "
"notification levels you will also receive notifications for select events. "
"To find out more, check out %{notification_link}."
msgstr ""
"Le niveau de notification Personnalisé est similaire au niveau Participation."
" Cependant, il permet également de recevoir des notifications pour des "
"événements sélectionnés. Pour plus d’information, vous pouvez consulter "
"%{notification_link}."
msgid "Cycle Analytics"
msgstr "Analyseur de cycle"
msgid ""
"Cycle Analytics gives an overview of how much time it takes to go from idea "
"to production in your project."
msgstr ""
"L’analyseur de cycle permet d’avoir une vue d’ensemble du temps nécessaire "
"pour aller d’une idée à sa mise en production pour votre projet."
msgid "CycleAnalyticsStage|Code"
msgstr "Code"
......@@ -49,31 +325,169 @@ msgstr "Pré-production"
msgid "CycleAnalyticsStage|Test"
msgstr "Test"
msgid "Define a custom pattern with cron syntax"
msgstr "Définir un schéma personnalisé avec une syntaxe Cron"
msgid "Delete"
msgstr "Supprimer"
msgid "Deploy"
msgid_plural "Deploys"
msgstr[0] "Déploiement"
msgstr[1] "Déploiements"
msgid "Description"
msgstr "Description"
msgid "Directory name"
msgstr "Nom du dossier"
msgid "Don't show again"
msgstr "Ne plus montrer"
msgid "Download"
msgstr "Télécharger"
msgid "Download tar"
msgstr "Télécharger tar"
msgid "Download tar.bz2"
msgstr "Télécharger tar.bz2"
msgid "Download tar.gz"
msgstr "Télécharger tar.gz"
msgid "Download zip"
msgstr "Télécharger zip"
msgid "DownloadArtifacts|Download"
msgstr "Télécharger"
msgid "DownloadCommit|Email Patches"
msgstr "Patch email"
msgid "DownloadCommit|Plain Diff"
msgstr "Diff simple"
msgid "DownloadSource|Download"
msgstr "Télécharger"
msgid "Edit"
msgstr "Éditer"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Éditer le pipeline programmé %{id}"
msgid "Every day (at 4:00am)"
msgstr "Chaque jour (à 4:00 du matin)"
msgid "Every month (on the 1st at 4:00am)"
msgstr "Chaque mois (le 1er à 4:00 du matin)"
msgid "Every week (Sundays at 4:00am)"
msgstr "Chaque semaine (dimanche à 4:00 du matin)"
msgid "Failed to change the owner"
msgstr "Échec du changement de propriétaire"
msgid "Failed to remove the pipeline schedule"
msgstr "Échec de la suppression du pipeline programmé"
msgid "Files"
msgstr "Fichiers"
msgid "Filter by commit message"
msgstr "Filtrer par message de validation"
msgid "Find by path"
msgstr "Rechercher par chemin"
msgid "Find file"
msgstr "Rechercher un fichier"
msgid "FirstPushedBy|First"
msgstr "En premier"
msgid "FirstPushedBy|pushed by"
msgstr "poussé par"
msgid "Fork"
msgid_plural "Forks"
msgstr[0] "Fourche"
msgstr[1] "Fourches"
msgid "ForkedFromProjectPath|Forked from"
msgstr "Fouché depuis"
msgid "From issue creation until deploy to production"
msgstr "Depuis la création de l'incident jusqu'au déploiement en production"
msgid "From merge request merge until deploy to production"
msgstr "Depuis la fusion de la demande de fusion jusqu'au déploiement en production"
msgstr ""
"Depuis la fusion de la demande de fusion jusqu'au déploiement en production"
msgid "Go to your fork"
msgstr "Aller à votre fourche"
msgid "GoToYourFork|Fork"
msgstr "Fourche"
msgid "Home"
msgstr "Accueil"
msgid "Housekeeping successfully started"
msgstr "Maintenance démarrée avec succès"
msgid "Import repository"
msgstr "Importer un dépôt"
msgid "Interval Pattern"
msgstr "Schéma d’intervalle"
msgid "Introducing Cycle Analytics"
msgstr "Introduction à l'analyseur de cycle"
msgid "Jobs for last month"
msgstr "Tâches pour le mois dernier"
msgid "Jobs for last week"
msgstr "Tâches pour la semaine dernière"
msgid "Jobs for last year"
msgstr "Tâches pour l'année dernière"
msgid "LFSStatus|Disabled"
msgstr "Désactivé"
msgid "LFSStatus|Enabled"
msgstr "Activé"
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "Le dernier %d jour"
msgstr[1] "Les derniers %d jours"
msgid "Last Pipeline"
msgstr "Dernier pipeline"
msgid "Last Update"
msgstr "Dernière mise à jour"
msgid "Last commit"
msgstr "Dernière validation"
msgid "Learn more in the"
msgstr "En apprendre plus dans le"
msgid "Learn more in the|pipeline schedules documentation"
msgstr "documentation concernant la programmation de pipeline"
msgid "Leave group"
msgstr "Quitter le groupe"
msgid "Leave project"
msgstr "Quitter le projet"
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Limiter l'affichage au plus à %d évènement"
......@@ -82,29 +496,276 @@ msgstr[1] "Limiter l'affichage au plus à %d évènements"
msgid "Median"
msgstr "Médian"
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "ajouter une clef SSH"
msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] "Nouvel incident"
msgstr[1] "Nouveaux incidents"
msgid "New Pipeline Schedule"
msgstr "Nouveau pipeline programmé"
msgid "New branch"
msgstr "Nouvelle branche"
msgid "New directory"
msgstr "Nouveau dossier"
msgid "New file"
msgstr "Nouveau Fichier"
msgid "New issue"
msgstr "Nouvel incident"
msgid "New merge request"
msgstr "Nouvelle demande de fusion"
msgid "New schedule"
msgstr "Nouveau programme"
msgid "New snippet"
msgstr "Nouvel extrait de code"
msgid "New tag"
msgstr "Nouvelle étiquette"
msgid "No repository"
msgstr "Pas de dépôt"
msgid "No schedules"
msgstr "Aucun programme"
msgid "Not available"
msgstr "Indisponible"
msgid "Not enough data"
msgstr "Données insuffisantes"
msgid "Notification events"
msgstr "Événement de notifications"
msgid "NotificationEvent|Close issue"
msgstr "Clore l'incident"
msgid "NotificationEvent|Close merge request"
msgstr "Clore la demande de fusion"
msgid "NotificationEvent|Failed pipeline"
msgstr "Pipeline échoué"
msgid "NotificationEvent|Merge merge request"
msgstr "Fusionner le demande de fusion"
msgid "NotificationEvent|New issue"
msgstr "Nouvel incident"
msgid "NotificationEvent|New merge request"
msgstr "Nouvelle demande de fusion"
msgid "NotificationEvent|New note"
msgstr "Nouvelle note"
msgid "NotificationEvent|Reassign issue"
msgstr "Réassigner l'incident"
msgid "NotificationEvent|Reassign merge request"
msgstr "Réassigner la demande de fusion"
msgid "NotificationEvent|Reopen issue"
msgstr "Ré-ouvrir l'incident"
msgid "NotificationEvent|Successful pipeline"
msgstr "Pipeline réussi"
msgid "NotificationLevel|Custom"
msgstr "Personnalisé"
msgid "NotificationLevel|Disabled"
msgstr "Désactivé"
msgid "NotificationLevel|Global"
msgstr "Global"
msgid "NotificationLevel|On mention"
msgstr "En cas de mention"
msgid "NotificationLevel|Participate"
msgstr "Participation"
msgid "NotificationLevel|Watch"
msgstr "Surveillé"
msgid "OfSearchInADropdown|Filter"
msgstr "Filtre"
msgid "OpenedNDaysAgo|Opened"
msgstr "Ouvert"
msgid "Options"
msgstr "Options"
msgid "Owner"
msgstr "Propriétaire"
msgid "Pipeline"
msgstr "Pipeline"
msgid "Pipeline Health"
msgstr "Santé du Pipeline"
msgid "Pipeline Schedule"
msgstr "Programmation de pipeline"
msgid "Pipeline Schedules"
msgstr "Programmations de pipeline"
msgid "PipelineCharts|Failed:"
msgstr "Échecs : "
msgid "PipelineCharts|Overall statistics"
msgstr "Statistiques générales"
msgid "PipelineCharts|Success ratio:"
msgstr "Ratio de succès : "
msgid "PipelineCharts|Successful:"
msgstr "Succès :"
msgid "PipelineCharts|Total:"
msgstr "Total :"
msgid "PipelineSchedules|Activated"
msgstr "Activé"
msgid "PipelineSchedules|Active"
msgstr "Actif"
msgid "PipelineSchedules|All"
msgstr "Tous"
msgid "PipelineSchedules|Inactive"
msgstr "Inactif"
msgid "PipelineSchedules|Input variable key"
msgstr "Nom de la variable"
msgid "PipelineSchedules|Input variable value"
msgstr "Valeur de la variable"
msgid "PipelineSchedules|Next Run"
msgstr "Prochaine exécution"
msgid "PipelineSchedules|None"
msgstr "Aucune"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "Indiquez une courte description"
msgid "PipelineSchedules|Remove variable row"
msgstr "Supprimer la variable"
msgid "PipelineSchedules|Take ownership"
msgstr "S’approprier"
msgid "PipelineSchedules|Target"
msgstr "Cible"
msgid "PipelineSchedules|Variables"
msgstr "Variables"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Personnalisé"
msgid "Pipelines"
msgstr "Pipelines"
msgid "Pipelines charts"
msgstr "Graphique des pipelines"
msgid "Pipeline|all"
msgstr "Tous"
msgid "Pipeline|success"
msgstr "Succès"
msgid "Pipeline|with stage"
msgstr "avec l'étape"
msgid "Pipeline|with stages"
msgstr "avec les étapes"
msgid "Project '%{project_name}' queued for deletion."
msgstr "Projet '%{project_name}' en attente de suppression."
msgid "Project '%{project_name}' was successfully created."
msgstr "Projet '%{project_name}' créé avec succès."
msgid "Project '%{project_name}' was successfully updated."
msgstr "Projet '%{project_name}' mis à jour avec succès."
msgid "Project '%{project_name}' will be deleted."
msgstr "Le projet '%{project_name}' sera supprimé."
msgid "Project access must be granted explicitly to each user."
msgstr ""
"L’accès au projet doit être explicitement accordé à chaque utilisateur."
msgid "Project export could not be deleted."
msgstr "L'export du projet n'a pas pu être supprimé."
msgid "Project export has been deleted."
msgstr "L'export du projet a été supprimé."
msgid ""
"Project export link has expired. Please generate a new export from your "
"project settings."
msgstr ""
"Le lien de l’export du projet a expiré. Merci de générer un nouvel export "
"depuis les paramètres du projet."
msgid "Project export started. A download link will be sent by email."
msgstr ""
"L'export du projet a débuté. Un lien de téléchargement sera envoyé par "
"courriel."
msgid "Project home"
msgstr "Accueil du projet"
msgid "ProjectFeature|Disabled"
msgstr "Désactivé"
msgid "ProjectFeature|Everyone with access"
msgstr "Toute personne ayant accès"
msgid "ProjectFeature|Only team members"
msgstr "Seulement les membres de l'équipe"
msgid "ProjectFileTree|Name"
msgstr "Nom"
msgid "ProjectLastActivity|Never"
msgstr "Jamais"
msgid "ProjectLifecycle|Stage"
msgstr "Étape"
msgid "ProjectNetworkGraph|Graph"
msgstr "Graphique "
msgid "Read more"
msgstr "Lire plus"
msgid "Readme"
msgstr "LisezMoi"
msgid "RefSwitcher|Branches"
msgstr "Branches"
msgid "RefSwitcher|Tags"
msgstr "Étiquettes"
msgid "Related Commits"
msgstr "Validations liés"
......@@ -123,43 +784,201 @@ msgstr "Demandes de fusion liées"
msgid "Related Merged Requests"
msgstr "Demandes fusionnées liées"
msgid "Remind later"
msgstr "Me le rappeler ultérieurement"
msgid "Remove project"
msgstr "Supprimer le projet"
msgid "Request Access"
msgstr "Demander l'accès"
msgid "Revert this commit"
msgstr "Annuler cette validation"
msgid "Revert this merge request"
msgstr "Annuler cette demande de fusion"
msgid "Save pipeline schedule"
msgstr "Sauvegarder le pipeline programmé"
msgid "Schedule a new pipeline"
msgstr "Programmer un nouveau pipeline"
msgid "Scheduling Pipelines"
msgstr "Programmer des pipelines"
msgid "Search branches and tags"
msgstr "Rechercher dans les branches et les étiquettes"
msgid "Select Archive Format"
msgstr "Sélectionnez le format de l'archive"
msgid "Select a timezone"
msgstr "Sélectionnez un fuseau horaire"
msgid "Select target branch"
msgstr "Sélectionnez une branche cible"
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
"Définissez un mot de passe pour votre compte pour pouvoir tirer ou pousser "
"par %{protocol}."
msgid "Set up CI"
msgstr "Mettre en place l'intégration continue (CI)"
msgid "Set up Koding"
msgstr "Mettre en place Koding"
msgid "Set up auto deploy"
msgstr "Mettre en place l’auto-déploiement"
msgid "SetPasswordToCloneLink|set a password"
msgstr "définir un mot de passe"
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "Affichage de %d évènement"
msgstr[1] "Affichage de %d évènements"
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
msgstr "L’étape de développement montre le temps entre la première validation et la création de la demande de fusion. Les données seront automatiquement ajoutées ici une fois que vous aurez créé votre première demande de fusion."
msgid "Source code"
msgstr "Code source"
msgid "StarProject|Star"
msgstr "S'abonner"
msgid "Start a %{new_merge_request} with these changes"
msgstr "Créer une %{new_merge_request} avec ces changements"
msgid "Switch branch/tag"
msgstr "Changer de branche / d'étiquette"
msgid "Tag"
msgid_plural "Tags"
msgstr[0] "Étiquette"
msgstr[1] "Étiquettes"
msgid "Tags"
msgstr "Étiquettes"
msgid "Target Branch"
msgstr "Branche cible"
msgid ""
"The coding stage shows the time from the first commit to creating the merge "
"request. The data will automatically be added here once you create your "
"first merge request."
msgstr ""
"L’étape de développement montre le temps entre la première validation et la "
"création de la demande de fusion. Les données seront automatiquement "
"ajoutées ici une fois que vous aurez créé votre première demande de fusion."
msgid "The collection of events added to the data gathered for that stage."
msgstr "L’ensemble d’évènements ajoutés aux données récupérées pour cette étape."
msgstr ""
"L’ensemble d’évènements ajoutés aux données récupérées pour cette étape."
msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
msgstr "L'étape des incidents montre le temps nécessaire entre la création d'un incident et son assignation à un jalon, ou son ajout à une liste d'un tableau d'incident. Débutez à créer des incidents pour voir des données pour cette étape."
msgid "The fork relationship has been removed."
msgstr "La relation de fourche a été supprimée."
msgid ""
"The issue stage shows the time it takes from creating an issue to assigning "
"the issue to a milestone, or add the issue to a list on your Issue Board. "
"Begin creating issues to see data for this stage."
msgstr ""
"L'étape des incidents montre le temps nécessaire entre la création d'un "
"incident et son assignation à un jalon, ou son ajout à une liste d'un "
"tableau d'incidents. Débutez à créer des incidents pour voir des données "
"pour cette étape."
msgid "The phase of the development lifecycle."
msgstr "Les étapes du cycle de développement."
msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
msgstr "L’étape de planification montre le temps entre l’étape précédente et l’envoi de votre première validation. Ce temps sera automatiquement ajouté quand vous pousserez votre première validation."
msgid ""
"The pipelines schedule runs pipelines in the future, repeatedly, for "
"specific branches or tags. Those scheduled pipelines will inherit limited "
"project access based on their associated user."
msgstr ""
"Les pipelines programmés exécutent des pipelines dans le futur, de façon "
"répétée, pour les branches et étiquettes spécifiées. Ces pipelines "
"programmés héritent d’un accès partiel au projet basé sur l’utilisateur qui "
"leurs est associé."
msgid ""
"The planning stage shows the time from the previous step to pushing your "
"first commit. This time will be added automatically once you push your first "
"commit."
msgstr ""
"L’étape de planification montre le temps entre l’étape précédente et l’envoi "
"de votre première validation. Ce temps sera automatiquement ajouté quand "
"vous pousserez votre première validation."
msgid ""
"The production stage shows the total time it takes between creating an issue "
"and deploying the code to production. The data will be automatically added "
"once you have completed the full idea to production cycle."
msgstr ""
"L’étape de mise en production montre le temps nécessaire entre la création "
"d’un incident et le déploiement du code en production. Les données seront "
"automatiquement ajoutées une fois que vous aurez complété le cycle complet, "
"depuis l’idée jusqu’à la mise en production."
msgid "The project can be accessed by any logged in user."
msgstr ""
"Votre projet peut être accédé par n’importe quel utilisateur authentifié"
msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr "L’étape de mise en production montre le temps nécessaire entre la création d’un incident et le déploiement du code en production. Les données seront automatiquement ajoutées une fois que vous aurez complété le cycle complet, depuis l’idée jusqu’à la mise en production."
msgid "The project can be accessed without any authentication."
msgstr "Votre projet peut être accédé sans aucune authentification."
msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
msgstr "L’étape d’évaluation montre le temps entre la création de la demande de fusion et la fusion effective de celle-ci. Ces données seront automatiquement ajoutées après que vous ayez fusionné votre première demande de fusion."
msgid "The repository for this project does not exist."
msgstr "Le dépôt pour ce projet n'existe pas."
msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
msgstr "L’étape de pré-production indique le temps entre la fusion de la RF et le déploiement du code dans l’environnent de production. Les données seront automatiquement ajoutées une fois que vous déploierez en production pour la première fois."
msgid ""
"The review stage shows the time from creating the merge request to merging "
"it. The data will automatically be added after you merge your first merge "
"request."
msgstr ""
"L’étape d’évaluation montre le temps entre la création de la demande de "
"fusion et la fusion effective de celle-ci. Ces données seront "
"automatiquement ajoutées après que vous ayez fusionné votre première demande "
"de fusion."
msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
msgstr "L’étape de test montre le temps que le CI de GitLab met pour exécuter chaque pipeline liés à la demande de fusion. Les données seront automatiquement ajoutées après que votre premier pipeline s’achèvera."
msgid ""
"The staging stage shows the time between merging the MR and deploying code "
"to the production environment. The data will be automatically added once you "
"deploy to production for the first time."
msgstr ""
"L’étape de pré-production indique le temps entre la fusion de la DF et le "
"déploiement du code dans l’environnent de production. Les données seront "
"automatiquement ajoutées une fois que vous déploierez en production pour la "
"première fois."
msgid ""
"The testing stage shows the time GitLab CI takes to run every pipeline for "
"the related merge request. The data will automatically be added after your "
"first pipeline finishes running."
msgstr ""
"L’étape de test montre le temps que le CI de GitLab met pour exécuter chaque "
"pipeline liés à la demande de fusion. Les données seront automatiquement "
"ajoutées après que votre premier pipeline s’achèvera."
msgid "The time taken by each data entry gathered by that stage."
msgstr "Le temps pris par chaque entrée récoltée durant cette étape."
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr "La valeur située au point médian d’une série de valeur observée. C.à.d., entre 3, 5, 9, le médian est 5. Entre 3, 5, 7, 8, le médian est (5+7)/2 = 6."
msgid ""
"The value lying at the midpoint of a series of observed values. E.g., "
"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
" 6."
msgstr ""
"La valeur située au point médian d’une série de valeur observée. C.à.d., "
"entre 3, 5, 9, le médian est 5. Entre 3, 5, 7, 8, le médian est (5+7)/2 = 6."
msgid ""
"This means you can not push code until you create an empty repository or "
"import existing one."
msgstr ""
"Cela signifie que vous ne pouvez pas pousser du code tant que vous ne créez "
"pas un dépôt vide, ou importez une dépôt existant."
msgid "Time before an issue gets scheduled"
msgstr "Temps avant qu’un incident ne soit planifié"
......@@ -173,6 +992,129 @@ msgstr "Temps entre la création d'une demande de fusion et sa fusion/clôture"
msgid "Time until first merge request"
msgstr "Temps jusqu’à la première demande de fusion"
msgid "Timeago|%s days ago"
msgstr "Il y a %s jours"
msgid "Timeago|%s days remaining"
msgstr "Il reste %s jours"
msgid "Timeago|%s hours remaining"
msgstr "Il reste %s heures"
msgid "Timeago|%s minutes ago"
msgstr "Il y a %s minutes"
msgid "Timeago|%s minutes remaining"
msgstr "Il reste %s minutes"
msgid "Timeago|%s months ago"
msgstr "Il y a %s mois"
msgid "Timeago|%s months remaining"
msgstr "Il reste %s mois"
msgid "Timeago|%s seconds remaining"
msgstr "Il reste %s secondes"
msgid "Timeago|%s weeks ago"
msgstr "Il y a %s semaines"
msgid "Timeago|%s weeks remaining"
msgstr "Il reste %s semaines"
msgid "Timeago|%s years ago"
msgstr "Il y a %s ans"
msgid "Timeago|%s years remaining"
msgstr "Il reste %s ans"
msgid "Timeago|1 day remaining"
msgstr "Il reste un jour"
msgid "Timeago|1 hour remaining"
msgstr "Il reste une heure"
msgid "Timeago|1 minute remaining"
msgstr "Il reste une minute"
msgid "Timeago|1 month remaining"
msgstr "Il reste un mois"
msgid "Timeago|1 week remaining"
msgstr "Il reste une semaine"
msgid "Timeago|1 year remaining"
msgstr "Il reste un an"
msgid "Timeago|Past due"
msgstr "En retard"
msgid "Timeago|a day ago"
msgstr "Il y a un jour"
msgid "Timeago|a month ago"
msgstr "Il y a un mois"
msgid "Timeago|a week ago"
msgstr "Il y a une semaine"
msgid "Timeago|a while"
msgstr "Il y a un moment"
msgid "Timeago|a year ago"
msgstr "Il y a un an"
msgid "Timeago|about %s hours ago"
msgstr "Il y a environ %s heures"
msgid "Timeago|about a minute ago"
msgstr "Il y a environ une minute"
msgid "Timeago|about an hour ago"
msgstr "Il y a environ une heure"
msgid "Timeago|in %s days"
msgstr "Dans %s jours"
msgid "Timeago|in %s hours"
msgstr "Dans %s heures"
msgid "Timeago|in %s minutes"
msgstr "Dans %s minutes"
msgid "Timeago|in %s months"
msgstr "Dans %s mois"
msgid "Timeago|in %s seconds"
msgstr "Dans %s secondes"
msgid "Timeago|in %s weeks"
msgstr "Dans %s semaines"
msgid "Timeago|in %s years"
msgstr "Dans %s années"
msgid "Timeago|in 1 day"
msgstr "Dans 1 jour"
msgid "Timeago|in 1 hour"
msgstr "Dans 1 heure"
msgid "Timeago|in 1 minute"
msgstr "Dans 1 minute"
msgid "Timeago|in 1 month"
msgstr "Dans 1 mois"
msgid "Timeago|in 1 week"
msgstr "Dans 1 semaine"
msgid "Timeago|in 1 year"
msgstr "Dans 1 an"
msgid "Timeago|less than a minute ago"
msgstr "il y a moins d'une minute"
msgid "Time|hr"
msgid_plural "Time|hrs"
msgstr[0] "hr"
......@@ -192,16 +1134,141 @@ msgstr "Temps total"
msgid "Total test time for all commits/merges"
msgstr "Temps total de test pour toutes les validations/fusions"
msgid "Unstar"
msgstr "Se désabonner"
msgid "Upload New File"
msgstr "Téléverser un nouveau fichier"
msgid "Upload file"
msgstr "Téléverser un fichier"
msgid "UploadLink|click to upload"
msgstr "Cliquez pour envoyer"
msgid "Use your global notification setting"
msgstr "Utiliser vos paramètres de notification globaux"
msgid "View open merge request"
msgstr "Afficher la demande de fusion"
msgid "VisibilityLevel|Internal"
msgstr "Interne"
msgid "VisibilityLevel|Private"
msgstr "Privé"
msgid "VisibilityLevel|Public"
msgstr "Public"
msgid "Want to see the data? Please ask an administrator for access."
msgstr "Vous voulez voir les données ? Merci de contacter un administrateur pour en obtenir l’accès."
msgstr ""
"Vous voulez voir les données ? Merci de contacter un administrateur pour en "
"obtenir l’accès."
msgid "We don't have enough data to show this stage."
msgstr "Nous n'avons pas suffisamment de données pour afficher cette étape."
msgid "Withdraw Access Request"
msgstr "Retirer la demande d'accès"
msgid ""
"You are going to remove %{group_name}.\n"
"Removed groups CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
msgstr ""
"Vous êtes sur le point de supprimer %{group_name}. Les groupes supprimés NE "
"PEUVENT PAS être restaurés ! Êtes vous ABSOLUMENT sûr ?"
msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
msgstr ""
"Vous êtes sur le point de supprimer %{project_name_with_namespace}.\n"
"Les projets supprimés NE PEUVENT PAS être restaurés !\n"
"Êtes vous ABSOLUMENT sûr ? "
msgid ""
"You are going to remove the fork relationship to source project "
"%{forked_from_project}. Are you ABSOLUTELY sure?"
msgstr ""
"Vous allez supprimer la relation de fourche avec le projet source "
"%{forked_from_project}. Êtes-vous VRAIMENT sûr."
msgid ""
"You are going to transfer %{project_name_with_namespace} to another owner. "
"Are you ABSOLUTELY sure?"
msgstr ""
"Vous allez transférer %{project_name_with_namespace} à un nouveau "
"propriétaire. Êtes vous VRAIMENT sûr ?"
msgid "You can only add files when you are on a branch"
msgstr "Vous ne pouvez ajouter de fichier que dans une branche"
msgid "You have reached your project limit"
msgstr "Vous avez atteint votre limite de projet"
msgid "You must sign in to star a project"
msgstr "Vous devez vous identifier pour vous abonner à un projet"
msgid "You need permission."
msgstr "Vous avez besoin d’une autorisation."
msgid "You will not get any notifications via email"
msgstr "Vous ne recevrez aucune notification par courriel"
msgid "You will only receive notifications for the events you choose"
msgstr ""
"Vous ne recevrez de notification que pour les évènements que vous aurez "
"choisis"
msgid ""
"You will only receive notifications for threads you have participated in"
msgstr ""
"Vous ne recevrez de notification que pour les sujets auxquels vous avez "
"participé"
msgid "You will receive notifications for any activity"
msgstr "Vous recevrez des notifications pour n’importe quelles activités"
msgid ""
"You will receive notifications only for comments in which you were "
"@mentioned"
msgstr ""
"Vous ne recevrez de notifications que pour les commentaires où vous êtes "
"@mentionné"
msgid ""
"You won't be able to pull or push project code via %{protocol} until you "
"%{set_password_link} on your account"
msgstr ""
"Vous ne pourrez pas récupérer ou pousser de code par %{protocol} tant que "
"vous n'aurez pas %{set_password_link} pour votre compte"
msgid ""
"You won't be able to pull or push project code via SSH until you "
"%{add_ssh_key_link} to your profile"
msgstr ""
"Vous ne pourrez pas récupérer ou pousser de code par SSH tant que vous "
"n’aurez pas %{add_ssh_key_link} dans votre profil"
msgid "Your name"
msgstr "Votre nom"
msgid "day"
msgid_plural "days"
msgstr[0] "jour"
msgstr[1] "jours"
msgid "new merge request"
msgstr "nouvelle demande de fusion"
msgid "notification emails"
msgstr "courriels de notification"
msgid "parent"
msgid_plural "parents"
msgstr[0] "parent"
msgstr[1] "parents"
......@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-12 12:31-0500\n"
"PO-Revision-Date: 2017-07-12 12:31-0500\n"
"POT-Creation-Date: 2017-07-13 12:07-0500\n"
"PO-Revision-Date: 2017-07-13 12:07-0500\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
......@@ -1060,6 +1060,9 @@ msgstr ""
msgid "VisibilityLevel|Public"
msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
......
......@@ -3,7 +3,7 @@
# Kohei Ota <inductor@kela.jp>, 2017. #zanata
# Taisuke Inoue <taisuke.inoue.jp@gmail.com>, 2017. #zanata
# Takuya Noguchi <takninnovationresearch@gmail.com>, 2017. #zanata
# YANO TETTER <tetuyano+zana@gmail.com>, 2017. #zanata
# YANO Tethurou <tetuyano+zana@gmail.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
......@@ -12,9 +12,9 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language-Team: Japanese (https://translate.zanata.org/project/view/GitLab)\n"
"PO-Revision-Date: 2017-07-18 09:27-0400\n"
"Last-Translator: YANO TETTER <tetuyano+zana@gmail.com>\n"
"PO-Revision-Date: 2017-07-19 09:45-0400\n"
"Last-Translator: YANO Tethurou <tetuyano+zana@gmail.com>\n"
"Language: ja\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
......@@ -628,6 +628,12 @@ msgstr "全件"
msgid "PipelineSchedules|Inactive"
msgstr "無効"
msgid "PipelineSchedules|Input variable key"
msgstr "変数の名前を入力"
msgid "PipelineSchedules|Input variable value"
msgstr "変数の値を入力"
msgid "PipelineSchedules|Next Run"
msgstr "次の実行"
......@@ -637,12 +643,18 @@ msgstr "なし"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "このパイプラインについて簡単に記述してください。"
msgid "PipelineSchedules|Remove variable row"
msgstr "変数を削除"
msgid "PipelineSchedules|Take ownership"
msgstr "権限を取得する"
msgid "PipelineSchedules|Target"
msgstr "ターゲット"
msgid "PipelineSchedules|Variables"
msgstr "変数"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "カスタム"
......@@ -1103,6 +1115,14 @@ msgstr "データ不足のため、このステージの表示はできません
msgid "Withdraw Access Request"
msgstr "アクセスリクエストを取り消す"
msgid ""
"You are going to remove %{group_name}.\n"
"Removed groups CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
msgstr "%{group_name} グループを削除しようとしています。\n"
"削除されたグループは絶対に元に戻せません!\n"
"本当によろしいですか?"
msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
......
......@@ -4,11 +4,11 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-28 13:32+0200\n"
"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-07-10 09:58-0400\n"
"PO-Revision-Date: 2017-07-12 06:23-0400\n"
"Last-Translator: Huang Tao <htve@outlook.com>\n"
"Language-Team: Chinese (China) (https://translate.zanata.org/project/view/GitLab)\n"
"Language: zh-CN\n"
......@@ -621,6 +621,12 @@ msgstr "所有"
msgid "PipelineSchedules|Inactive"
msgstr "未启用"
msgid "PipelineSchedules|Input variable key"
msgstr "输入变量名"
msgid "PipelineSchedules|Input variable value"
msgstr "输入变量值"
msgid "PipelineSchedules|Next Run"
msgstr "下次运行时间"
......@@ -630,12 +636,18 @@ msgstr "无"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "为此流水线提供简短描述"
msgid "PipelineSchedules|Remove variable row"
msgstr "删除变量"
msgid "PipelineSchedules|Take ownership"
msgstr "取得所有"
msgstr "取得所有"
msgid "PipelineSchedules|Target"
msgstr "目标"
msgid "PipelineSchedules|Variables"
msgstr "变量"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "自定义"
......@@ -1085,6 +1097,14 @@ msgstr "该阶段的数据不足,无法显示。"
msgid "Withdraw Access Request"
msgstr "取消权限申请"
msgid ""
"You are going to remove %{group_name}.\n"
"Removed groups CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
msgstr "即将删除 %{group_name}。\n"
"已删除的群组无法恢复!\n"
"确定继续吗?"
msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
......
......@@ -3,13 +3,13 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-28 13:32+0200\n"
"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-07-06 11:26-0400\n"
"PO-Revision-Date: 2017-07-12 06:32-0400\n"
"Last-Translator: Huang Tao <htve@outlook.com>\n"
"Language-Team: Chinese (Hong Kong SAR China)\n"
"Language-Team: Chinese (Hong Kong SAR China) (https://translate.zanata.org/project/view/GitLab)\n"
"Language: zh-HK\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
......@@ -620,6 +620,12 @@ msgstr "所有"
msgid "PipelineSchedules|Inactive"
msgstr "未啟用"
msgid "PipelineSchedules|Input variable key"
msgstr "輸入變量名"
msgid "PipelineSchedules|Input variable value"
msgstr "輸入變量值"
msgid "PipelineSchedules|Next Run"
msgstr "下次運行時間"
......@@ -629,12 +635,18 @@ msgstr "無"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "為此流水線提供簡短描述"
msgid "PipelineSchedules|Remove variable row"
msgstr "刪除變量"
msgid "PipelineSchedules|Take ownership"
msgstr "取得所有"
msgstr "取得所有"
msgid "PipelineSchedules|Target"
msgstr "目標"
msgid "PipelineSchedules|Variables"
msgstr "變量"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "自定義"
......@@ -1084,6 +1096,14 @@ msgstr "該階段的數據不足,無法顯示。"
msgid "Withdraw Access Request"
msgstr "取消權限申请"
msgid ""
"You are going to remove %{group_name}.\n"
"Removed groups CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
msgstr "即將刪除 %{group_name}。\n"
"已刪除的群組無法恢復!\n"
"確定繼續嗎?"
msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
......
......@@ -29,7 +29,7 @@ describe Profiles::AccountsController do
end
end
[:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0].each do |provider|
[:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq].each do |provider|
describe "#{provider} provider" do
let(:user) { create(:omniauth_user, provider: provider.to_s) }
......
require 'spec_helper'
describe Projects::HooksController do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
before do
project.team << [user, :master]
sign_in(user)
end
describe '#index' do
it 'redirects to settings/integrations page' do
get(:index, namespace_id: project.namespace, project_id: project)
expect(response).to redirect_to(
project_settings_integrations_path(project)
)
end
end
end
......@@ -516,6 +516,36 @@ describe Projects::IssuesController do
end
end
describe 'GET #realtime_changes' do
it_behaves_like 'restricted action', success: 200
def go(id:)
get :realtime_changes,
namespace_id: project.namespace.to_param,
project_id: project,
id: id
end
context 'when an issue was edited by a deleted user' do
let(:deleted_user) { create(:user) }
before do
project.team << [user, :developer]
issue.update!(last_edited_by: deleted_user, last_edited_at: Time.now)
deleted_user.destroy
sign_in(user)
end
it 'returns 200' do
go(id: issue.iid)
expect(response).to have_http_status(200)
end
end
end
describe 'GET #edit' do
it_behaves_like 'restricted action', success: 200
......
require 'spec_helper'
describe Projects::Registry::TagsController do
let(:user) { create(:user) }
let(:project) { create(:empty_project, :private) }
before do
sign_in(user)
stub_container_registry_config(enabled: true)
end
context 'when user has access to registry' do
before do
project.add_developer(user)
end
describe 'POST destroy' do
context 'when there is matching tag present' do
before do
stub_container_registry_tags(repository: /image/, tags: %w[rc1 test.])
end
let(:repository) do
create(:container_repository, name: 'image', project: project)
end
it 'makes it possible to delete regular tag' do
expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete)
destroy_tag('rc1')
end
it 'makes it possible to delete a tag that ends with a dot' do
expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete)
destroy_tag('test.')
end
end
end
end
def destroy_tag(name)
post :destroy, namespace_id: project.namespace,
project_id: project,
repository_id: repository,
id: name
end
end
......@@ -84,6 +84,10 @@ FactoryGirl.define do
success
end
trait :retried do
retried true
end
trait :cancelable do
pending
end
......@@ -106,7 +110,7 @@ FactoryGirl.define do
end
after(:build) do |build, evaluator|
build.project = build.pipeline.project
build.project ||= build.pipeline.project
end
factory :ci_not_started_build do
......
......@@ -2,6 +2,7 @@ FactoryGirl.define do
factory :project_hook do
url { generate(:url) }
enable_ssl_verification false
project factory: :empty_project
trait :token do
token { SecureRandom.hex(10) }
......
......@@ -74,11 +74,13 @@ describe 'Admin::Hooks', feature: true do
end
end
describe 'Test' do
describe 'Test', js: true do
before do
WebMock.stub_request(:post, @system_hook.url)
visit admin_hooks_path
click_link 'Test hook'
find('.hook-test-button.dropdown').click
click_link 'Push events'
end
it { expect(current_path).to eq(admin_hooks_path) }
......
require 'spec_helper'
RSpec.describe 'Dashboard Activity', feature: true do
feature 'Dashboard > Activity' do
let(:user) { create(:user) }
before do
sign_in(user)
end
context 'rss' do
before do
visit activity_dashboard_path
end
it_behaves_like "it has an RSS button with current_user's RSS token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
context 'event filters', :js do
let(:project) { create(:empty_project) }
let(:merge_request) do
create(:merge_request, author: user, source_project: project, target_project: project)
end
let(:push_event_data) do
{
before: Gitlab::Git::BLANK_SHA,
after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e',
ref: 'refs/heads/new_design',
user_id: user.id,
user_name: user.name,
repository: {
name: project.name,
url: 'localhost/rubinius',
description: '',
homepage: 'localhost/rubinius',
private: true
}
}
end
let(:note) { create(:note, project: project, noteable: merge_request) }
let!(:push_event) do
create(:event, :pushed, data: push_event_data, project: project, author: user)
end
let!(:merged_event) do
create(:event, :merged, project: project, target: merge_request, author: user)
end
let!(:joined_event) do
create(:event, :joined, project: project, author: user)
end
let!(:closed_event) do
create(:event, :closed, project: project, target: merge_request, author: user)
end
let!(:comments_event) do
create(:event, :commented, project: project, target: note, author: user)
end
before do
project.add_master(user)
visit activity_dashboard_path
wait_for_requests
end
scenario 'user should see all events' do
within '.content_list' do
expect(page).to have_content('pushed new branch')
expect(page).to have_content('joined')
expect(page).to have_content('accepted')
expect(page).to have_content('closed')
expect(page).to have_content('commented on')
end
end
scenario 'user should see only pushed events' do
click_link('Push events')
wait_for_requests
within '.content_list' do
expect(page).to have_content('pushed new branch')
expect(page).not_to have_content('joined')
expect(page).not_to have_content('accepted')
expect(page).not_to have_content('closed')
expect(page).not_to have_content('commented on')
end
end
scenario 'user should see only merged events' do
click_link('Merge events')
wait_for_requests
within '.content_list' do
expect(page).not_to have_content('pushed new branch')
expect(page).not_to have_content('joined')
expect(page).to have_content('accepted')
expect(page).not_to have_content('closed')
expect(page).not_to have_content('commented on')
end
end
scenario 'user should see only issues events' do
click_link('Issue events')
wait_for_requests
within '.content_list' do
expect(page).not_to have_content('pushed new branch')
expect(page).not_to have_content('joined')
expect(page).not_to have_content('accepted')
expect(page).to have_content('closed')
expect(page).not_to have_content('commented on')
end
end
scenario 'user should see only comments events' do
click_link('Comments')
wait_for_requests
within '.content_list' do
expect(page).not_to have_content('pushed new branch')
expect(page).not_to have_content('joined')
expect(page).not_to have_content('accepted')
expect(page).not_to have_content('closed')
expect(page).to have_content('commented on')
end
end
scenario 'user should see only joined events' do
click_link('Team')
wait_for_requests
within '.content_list' do
expect(page).not_to have_content('pushed new branch')
expect(page).to have_content('joined')
expect(page).not_to have_content('accepted')
expect(page).not_to have_content('closed')
expect(page).not_to have_content('commented on')
end
end
scenario 'user see selected event after page reloading' do
click_link('Push events')
wait_for_requests
visit activity_dashboard_path
wait_for_requests
within '.content_list' do
expect(page).to have_content('pushed new branch')
expect(page).not_to have_content('joined')
expect(page).not_to have_content('accepted')
expect(page).not_to have_content('closed')
expect(page).not_to have_content('commented on')
end
end
end
end
require 'spec_helper'
describe 'Dashboard Groups page', js: true, feature: true do
feature 'Dashboard Groups page', :js do
let!(:user) { create :user }
let!(:group) { create(:group) }
let!(:nested_group) { create(:group, :nested) }
......@@ -41,7 +41,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
fill_in 'filter_groups', with: group.name
wait_for_requests
fill_in 'filter_groups', with: ""
fill_in 'filter_groups', with: ''
wait_for_requests
expect(page).to have_content(group.full_name)
......
require 'spec_helper'
describe "Dashboard Issues filtering", feature: true, js: true do
feature 'Dashboard Issues filtering', js: true do
include SortingHelper
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:milestone) { create(:milestone, project: project) }
context 'filtering by milestone' do
let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
let!(:issue2) { create(:issue, project: project, author: user, assignees: [user], milestone: milestone) }
before do
project.team << [user, :master]
project.add_master(user)
sign_in(user)
create(:issue, project: project, author: user, assignees: [user])
create(:issue, project: project, author: user, assignees: [user], milestone: milestone)
visit_issues
end
context 'filtering by milestone' do
it 'shows all issues with no milestone' do
show_milestone_dropdown
......@@ -62,6 +64,46 @@ describe "Dashboard Issues filtering", feature: true, js: true do
end
end
context 'filtering by label' do
let(:label) { create(:label, project: project) }
let!(:label_link) { create(:label_link, label: label, target: issue) }
it 'shows all issues without filter' do
page.within 'ul.content-list' do
expect(page).to have_content issue.title
expect(page).to have_content issue2.title
end
end
it 'shows all issues with the selected label' do
page.within '.labels-filter' do
find('.dropdown').click
click_link label.title
end
page.within 'ul.content-list' do
expect(page).to have_content issue.title
expect(page).not_to have_content issue2.title
end
end
end
context 'sorting' do
it 'shows sorted issues' do
sorting_by('Oldest updated')
visit_issues
expect(find('.issues-filters')).to have_content('Oldest updated')
end
it 'keeps sorting issues after visiting Projects Issues page' do
sorting_by('Oldest updated')
visit project_issues_path(project)
expect(find('.issues-filters')).to have_content('Oldest updated')
end
end
def show_milestone_dropdown
click_button 'Milestone'
expect(page).to have_selector('.dropdown-content', visible: true)
......
......@@ -2,6 +2,7 @@ require 'spec_helper'
feature 'Dashboard Merge Requests' do
include FilterItemSelectHelper
include SortingHelper
let(:current_user) { create :user }
let(:project) { create(:empty_project) }
......@@ -109,5 +110,21 @@ feature 'Dashboard Merge Requests' do
expect(page).to have_content(assigned_merge_request_from_fork.title)
expect(page).to have_content(other_merge_request.title)
end
it 'shows sorted merge requests' do
sorting_by('Oldest updated')
visit merge_requests_dashboard_path(assignee_id: current_user.id)
expect(find('.issues-filters')).to have_content('Oldest updated')
end
it 'keeps sorting merge requests after visiting Projects MR page' do
sorting_by('Oldest updated')
visit project_merge_requests_path(project)
expect(find('.issues-filters')).to have_content('Oldest updated')
end
end
end
......@@ -61,7 +61,7 @@ feature 'Dashboard Projects' do
end
end
describe "with a pipeline", clean_gitlab_redis_shared_state: true do
describe 'with a pipeline', clean_gitlab_redis_shared_state: true do
let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) }
before do
......@@ -74,7 +74,50 @@ feature 'Dashboard Projects' do
it 'shows that the last pipeline passed' do
visit dashboard_projects_path
page.within('.controls') do
expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit)}']")
expect(page).to have_css('.ci-status-link')
expect(page).to have_css('.ci-status-icon-success')
expect(page).to have_link('Commit: passed')
end
end
end
context 'last push widget' do
let(:push_event_data) do
{
before: Gitlab::Git::BLANK_SHA,
after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e',
ref: 'refs/heads/feature',
user_id: user.id,
user_name: user.name,
repository: {
name: project.name,
url: 'localhost/rubinius',
description: '',
homepage: 'localhost/rubinius',
private: true
}
}
end
let!(:push_event) { create(:event, :pushed, data: push_event_data, project: project, author: user) }
before do
visit dashboard_projects_path
end
scenario 'shows "Create merge request" button' do
expect(page).to have_content 'You pushed to feature'
within('#content-body') do
find_link('Create merge request', visible: false).click
end
expect(page).to have_selector('.merge-request-form')
expect(current_path).to eq project_new_merge_request_path(project)
expect(find('#merge_request_target_project_id').value).to eq project.id.to_s
expect(find('input#merge_request_source_branch').value).to eq 'feature'
expect(find('input#merge_request_target_branch').value).to eq 'master'
end
end
end
require 'rails_helper'
feature 'Issue Detail', js: true, feature: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project, author: user) }
context 'when user displays the issue' do
before do
visit project_issue_path(project, issue)
wait_for_requests
end
it 'shows the issue' do
page.within('.issuable-details') do
expect(find('h2')).to have_content(issue.title)
end
end
end
context 'when edited by a user who is later deleted' do
before do
sign_in(user)
visit project_issue_path(project, issue)
wait_for_requests
click_link 'Edit'
fill_in 'issue-title', with: 'issue title'
click_button 'Save'
visit profile_account_path
click_link 'Delete account'
visit project_issue_path(project, issue)
end
it 'shows the issue' do
page.within('.issuable-details') do
expect(find('h2')).to have_content(issue.reload.title)
end
end
end
end
......@@ -132,19 +132,13 @@ describe 'Filter merge requests', feature: true do
end
end
describe 'for assignee and label from issues#index' do
describe 'for assignee and label from mr#index' do
let(:search_query) { "assignee:@#{user.username} label:~#{label.title}" }
before do
input_filtered_search("assignee:@#{user.username}")
expect_mr_list_count(1)
expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
expect_filtered_search_input_empty
input_filtered_search_keys("label:~#{label.title}")
input_filtered_search(search_query)
expect_mr_list_count(1)
expect_mr_list_count(0)
end
context 'assignee and label', js: true do
......
......@@ -20,7 +20,6 @@ describe 'Branches', feature: true do
repository.branches_sorted_by(:name).first(20).each do |branch|
expect(page).to have_content("#{branch.name}")
end
expect(page).to have_content("Protected branches can be managed in project settings")
end
it 'sorts the branches by name' do
......@@ -128,6 +127,14 @@ describe 'Branches', feature: true do
project.team << [user, :master]
end
describe 'Initial branches page' do
it 'shows description for admin' do
visit project_branches_path(project)
expect(page).to have_content("Protected branches can be managed in project settings")
end
end
describe 'Delete protected branch' do
before do
visit project_protected_branches_path(project)
......
require 'spec_helper'
feature 'Merge Request button', feature: true do
feature 'Merge Request button' do
shared_examples 'Merge request button only shown when allowed' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
......@@ -10,16 +10,14 @@ feature 'Merge Request button', feature: true do
it 'does not show Create merge request button' do
visit url
within("#content-body") do
expect(page).not_to have_link(label)
end
end
end
context 'logged in as developer' do
before do
sign_in(user)
project.team << [user, :developer]
project.add_developer(user)
end
it 'shows Create merge request button' do
......@@ -29,7 +27,7 @@ feature 'Merge Request button', feature: true do
visit url
within("#content-body") do
within('#content-body') do
expect(page).to have_link(label, href: href)
end
end
......@@ -42,7 +40,7 @@ feature 'Merge Request button', feature: true do
it 'does not show Create merge request button' do
visit url
within("#content-body") do
within('#content-body') do
expect(page).not_to have_link(label)
end
end
......@@ -57,7 +55,7 @@ feature 'Merge Request button', feature: true do
it 'does not show Create merge request button' do
visit url
within("#content-body") do
within('#content-body') do
expect(page).not_to have_link(label)
end
end
......
......@@ -36,14 +36,14 @@ feature 'Integration settings', feature: true do
expect(page.status_code).to eq(200)
expect(page).to have_content(hook.url)
expect(page).to have_content('SSL Verification: enabled')
expect(page).to have_content('Push Events')
expect(page).to have_content('Tag Push Events')
expect(page).to have_content('Issues Events')
expect(page).to have_content('Confidential Issues Events')
expect(page).to have_content('Note Events')
expect(page).to have_content('Merge Requests Events')
expect(page).to have_content('Pipeline Events')
expect(page).to have_content('Wiki Page Events')
expect(page).to have_content('Push events')
expect(page).to have_content('Tag push events')
expect(page).to have_content('Issues events')
expect(page).to have_content('Confidential issues events')
expect(page).to have_content('Note events')
expect(page).to have_content('Merge requests events')
expect(page).to have_content('Pipeline events')
expect(page).to have_content('Wiki page events')
end
scenario 'create webhook' do
......@@ -58,8 +58,8 @@ feature 'Integration settings', feature: true do
expect(page).to have_content(url)
expect(page).to have_content('SSL Verification: enabled')
expect(page).to have_content('Push Events')
expect(page).to have_content('Tag Push Events')
expect(page).to have_content('Push events')
expect(page).to have_content('Tag push events')
expect(page).to have_content('Job events')
end
......@@ -76,11 +76,12 @@ feature 'Integration settings', feature: true do
expect(page).to have_content(url)
end
scenario 'test existing webhook' do
scenario 'test existing webhook', js: true do
WebMock.stub_request(:post, hook.url)
visit integrations_path
click_link 'Test'
find('.hook-test-button.dropdown').click
click_link 'Push events'
expect(current_path).to eq(integrations_path)
end
......
......@@ -70,7 +70,7 @@ describe AuthHelper do
end
end
[:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0].each do |provider|
[:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq].each do |provider|
it "returns false if the provider is #{provider}" do
expect(helper.unlink_allowed?(provider)).to be true
end
......
require 'spec_helper'
describe HooksHelper do
let(:project) { create(:empty_project) }
let(:project_hook) { create(:project_hook, project: project) }
let(:system_hook) { create(:system_hook) }
let(:trigger) { 'push_events' }
describe '#link_to_test_hook' do
it 'returns project namespaced link' do
expect(helper.link_to_test_hook(project_hook, trigger))
.to include("href=\"#{test_project_hook_path(project, project_hook, trigger: trigger)}\"")
end
it 'returns admin namespaced link' do
expect(helper.link_to_test_hook(system_hook, trigger))
.to include("href=\"#{test_admin_hook_path(system_hook, trigger: trigger)}\"")
end
end
end
......@@ -244,5 +244,25 @@ describe IssuablesHelper do
it { expect(helper.updated_at_by(unedited_issuable)).to eq({}) }
it { expect(helper.updated_at_by(edited_issuable)).to eq(edited_updated_at_by) }
context 'when updated by a deleted user' do
let(:edited_updated_at_by) do
{
updatedAt: edited_issuable.updated_at.to_time.iso8601,
updatedBy: {
name: User.ghost.name,
path: user_path(User.ghost)
}
}
end
before do
user.destroy
end
it 'returns "Ghost user" as edited_by' do
expect(helper.updated_at_by(edited_issuable.reload)).to eq(edited_updated_at_by)
end
end
end
end
......@@ -32,6 +32,28 @@ module Ci
end
end
describe 'retry entry' do
context 'when retry count is specified' do
let(:config) do
YAML.dump(rspec: { script: 'rspec', retry: 1 })
end
it 'includes retry count in build options attribute' do
expect(subject[:options]).to include(retry: 1)
end
end
context 'when retry count is not specified' do
let(:config) do
YAML.dump(rspec: { script: 'rspec' })
end
it 'does not persist retry count in the database' do
expect(subject[:options]).not_to have_key(:retry)
end
end
end
describe 'allow failure entry' do
context 'when job is a manual action' do
context 'when allow_failure is defined' do
......
......@@ -25,7 +25,7 @@ describe Gitlab::BackgroundMigration do
expect(queue[0]).to receive(:delete).and_return(true)
expect(described_class).to receive(:perform)
.with('Foo', [10, 20], anything)
.with('Foo', [10, 20])
described_class.steal('Foo')
end
......@@ -93,9 +93,9 @@ describe Gitlab::BackgroundMigration do
it 'steals from the scheduled sets queue first' do
Sidekiq::Testing.disable! do
expect(described_class).to receive(:perform)
.with('Object', [1], anything).ordered
.with('Object', [1]).ordered
expect(described_class).to receive(:perform)
.with('Object', [2], anything).ordered
.with('Object', [2]).ordered
BackgroundMigrationWorker.perform_async('Object', [2])
BackgroundMigrationWorker.perform_in(10.minutes, 'Object', [1])
......
......@@ -80,6 +80,45 @@ describe Gitlab::Ci::Config::Entry::Job do
expect(entry.errors).to include "job script can't be blank"
end
end
context 'when retry value is not correct' do
context 'when it is not a numeric value' do
let(:config) { { retry: true } }
it 'returns error about invalid type' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job retry is not a number'
end
end
context 'when it is lower than zero' do
let(:config) { { retry: -1 } }
it 'returns error about value too low' do
expect(entry).not_to be_valid
expect(entry.errors)
.to include 'job retry must be greater than or equal to 0'
end
end
context 'when it is not an integer' do
let(:config) { { retry: 1.5 } }
it 'returns error about wrong value' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job retry must be an integer'
end
end
context 'when the value is too high' do
let(:config) { { retry: 10 } }
it 'returns error about value too high' do
expect(entry).not_to be_valid
expect(entry.errors).to include 'job retry must be less than or equal to 2'
end
end
end
end
end
......
require 'spec_helper'
describe Gitlab::DataBuilder::WikiPage do
let(:project) { create(:project, :repository) }
let(:wiki_page) { create(:wiki_page, wiki: project.wiki) }
let(:user) { create(:user) }
describe '.build' do
let(:data) { described_class.build(wiki_page, user, 'create') }
it { expect(data).to be_a(Hash) }
it { expect(data[:object_kind]).to eq('wiki_page') }
it { expect(data[:user]).to eq(user.hook_attrs) }
it { expect(data[:project]).to eq(project.hook_attrs) }
it { expect(data[:wiki]).to eq(project.wiki.hook_attrs) }
it { expect(data[:object_attributes]).to include(wiki_page.hook_attrs) }
it { expect(data[:object_attributes]).to include(url: Gitlab::UrlBuilder.build(wiki_page)) }
it { expect(data[:object_attributes]).to include(action: 'create') }
end
end
......@@ -484,6 +484,8 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
end
def each
return enum_for(:each) unless block_given?
loop do
break if @count.zero?
# It is critical to decrement before yielding. We may never reach the lines after 'yield'.
......
......@@ -12,7 +12,10 @@ describe Gitlab::GitalyClient::CommitService do
request = Gitaly::CommitDiffRequest.new(
repository: repository_message,
left_commit_id: 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660',
right_commit_id: commit.id
right_commit_id: commit.id,
collapse_diffs: true,
enforce_limits: true,
**Gitlab::Git::DiffCollection.collection_limits.to_h
)
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
......@@ -27,7 +30,10 @@ describe Gitlab::GitalyClient::CommitService do
request = Gitaly::CommitDiffRequest.new(
repository: repository_message,
left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
right_commit_id: initial_commit.id
right_commit_id: initial_commit.id,
collapse_diffs: true,
enforce_limits: true,
**Gitlab::Git::DiffCollection.collection_limits.to_h
)
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
......@@ -43,7 +49,7 @@ describe Gitlab::GitalyClient::CommitService do
end
it 'passes options to Gitlab::Git::DiffCollection' do
options = { max_files: 31, max_lines: 13 }
options = { max_files: 31, max_lines: 13, from_gitaly: true }
expect(Gitlab::Git::DiffCollection).to receive(:new).with(kind_of(Enumerable), options)
......
......@@ -38,4 +38,15 @@ describe Gitlab::Regex, lib: true do
it { is_expected.not_to match('9foo') }
it { is_expected.not_to match('foo-') }
end
describe '.container_repository_name_regex' do
subject { described_class.container_repository_name_regex }
it { is_expected.to match('image') }
it { is_expected.to match('my/image') }
it { is_expected.to match('my/awesome/image-1') }
it { is_expected.to match('my/awesome/image.test') }
it { is_expected.not_to match('.my/image') }
it { is_expected.not_to match('my/image.') }
end
end
......@@ -59,6 +59,7 @@ describe Gitlab::UsageData do
milestones
notes
projects
projects_imported_from_github
projects_prometheus_active
pages_domains
protected_branches
......
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20170710083355_clean_stage_id_reference_migration.rb')
describe CleanStageIdReferenceMigration, :migration, :sidekiq, :redis do
let(:migration_class) { 'MigrateBuildStageIdReference' }
let(:migration) { spy('migration') }
before do
allow(Gitlab::BackgroundMigration.const_get(migration_class))
.to receive(:new).and_return(migration)
end
context 'when there are pending background migrations' do
it 'processes pending jobs synchronously' do
Sidekiq::Testing.disable! do
BackgroundMigrationWorker.perform_in(2.minutes, migration_class, [1, 1])
BackgroundMigrationWorker.perform_async(migration_class, [1, 1])
migrate!
expect(migration).to have_received(:perform).with(1, 1).twice
end
end
end
context 'when there are no background migrations pending' do
it 'does nothing' do
Sidekiq::Testing.disable! do
migrate!
expect(migration).not_to have_received(:perform)
end
end
end
end
......@@ -821,6 +821,47 @@ describe Ci::Build, :models do
end
end
describe 'build auto retry feature' do
describe '#retries_count' do
subject { create(:ci_build, name: 'test', pipeline: pipeline) }
context 'when build has been retried several times' do
before do
create(:ci_build, :retried, name: 'test', pipeline: pipeline)
create(:ci_build, :retried, name: 'test', pipeline: pipeline)
end
it 'reports a correct retry count value' do
expect(subject.retries_count).to eq 2
end
end
context 'when build has not been retried' do
it 'returns zero' do
expect(subject.retries_count).to eq 0
end
end
end
describe '#retries_max' do
context 'when max retries value is defined' do
subject { create(:ci_build, options: { retry: 1 }) }
it 'returns a number of configured max retries' do
expect(subject.retries_max).to eq 1
end
end
context 'when max retries value is not defined' do
subject { create(:ci_build) }
it 'returns zero' do
expect(subject.retries_max).to eq 0
end
end
end
end
describe '#keep_artifacts!' do
let(:build) { create(:ci_build, artifacts_expire_at: Time.now + 7.days) }
......@@ -1602,7 +1643,7 @@ describe Ci::Build, :models do
end
end
describe 'State transition: any => [:pending]' do
describe 'state transition: any => [:pending]' do
let(:build) { create(:ci_build, :created) }
it 'queues BuildQueueWorker' do
......@@ -1612,37 +1653,34 @@ describe Ci::Build, :models do
end
end
describe '#has_codeclimate_json?' do
context 'valid build' do
let!(:build) do
create(
:ci_build,
:artifacts,
name: 'codeclimate',
pipeline: pipeline,
options: {
artifacts: {
paths: ['codeclimate.json']
}
}
)
end
describe 'state transition when build fails' do
context 'when build is configured to be retried' do
subject { create(:ci_build, :running, options: { retry: 3 }) }
it 'retries builds and assigns a same user to it' do
expect(described_class).to receive(:retry)
.with(subject, subject.user)
it { expect(build.has_codeclimate_json?).to be_truthy }
subject.drop!
end
end
context 'when build is not configured to be retried' do
subject { create(:ci_build, :running) }
context 'invalid build' do
let!(:build) do
create(
:ci_build,
:artifacts,
name: 'codeclimate',
pipeline: pipeline,
options: {}
)
it 'does not retry build' do
expect(described_class).not_to receive(:retry)
subject.drop!
end
it { expect(build.has_codeclimate_json?).to be_falsey }
it 'does not count retries when not necessary' do
expect(described_class).not_to receive(:retry)
expect_any_instance_of(described_class)
.not_to receive(:retries_count)
subject.drop!
end
end
end
end
......@@ -105,4 +105,38 @@ describe Ci::Build, models: true do
end
end
end
describe '#has_codeclimate_json?' do
context 'valid build' do
let!(:build) do
create(
:ci_build,
:artifacts,
name: 'codeclimate',
pipeline: pipeline,
options: {
artifacts: {
paths: ['codeclimate.json']
}
}
)
end
it { expect(build.has_codeclimate_json?).to be_truthy }
end
context 'invalid build' do
let!(:build) do
create(
:ci_build,
:artifacts,
name: 'codeclimate',
pipeline: pipeline,
options: {}
)
end
it { expect(build.has_codeclimate_json?).to be_falsey }
end
end
end
require 'spec_helper'
describe ProjectHook, models: true do
describe "Associations" do
describe 'associations' do
it { is_expected.to belong_to :project }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:project) }
end
describe '.push_hooks' do
it 'returns hooks for push events only' do
hook = create(:project_hook, push_events: true)
......
......@@ -5,6 +5,10 @@ describe ServiceHook, models: true do
it { is_expected.to belong_to :service }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:service) }
end
describe 'execute' do
let(:hook) { build(:service_hook) }
let(:data) { { key: 'value' } }
......
......@@ -7,8 +7,7 @@ describe SystemHook, models: true do
it 'sets defined default parameters' do
attrs = {
push_events: false,
repository_update_events: true,
enable_ssl_verification: true
repository_update_events: true
}
expect(system_hook).to have_attributes(attrs)
end
......
......@@ -112,7 +112,7 @@ describe MicrosoftTeamsService, models: true do
let(:wiki_page_sample_data) do
service = WikiPages::CreateService.new(project, user, opts)
wiki_page = service.execute
service.hook_data(wiki_page, 'create')
Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create')
end
it "calls Microsoft Teams API" do
......
......@@ -347,10 +347,14 @@ describe Project, models: true do
end
describe 'delegation' do
it { is_expected.to delegate_method(:add_guest).to(:team) }
it { is_expected.to delegate_method(:add_reporter).to(:team) }
it { is_expected.to delegate_method(:add_developer).to(:team) }
it { is_expected.to delegate_method(:add_master).to(:team) }
[:add_guest, :add_reporter, :add_developer, :add_master, :add_user, :add_users].each do |method|
it { is_expected.to delegate_method(method).to(:team) }
end
it { is_expected.to delegate_method(:empty_repo?).to(:repository) }
it { is_expected.to delegate_method(:members).to(:team).with_prefix(true) }
it { is_expected.to delegate_method(:count).to(:forks).with_prefix(true) }
it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
end
describe '#to_reference' do
......
......@@ -609,4 +609,26 @@ describe 'project routing' do
expect(get('/gitlab/gitlabhq/pages/domains/my.domain.com')).to route_to('projects/pages_domains#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'my.domain.com')
end
end
describe Projects::Registry::TagsController, :routing do
describe '#destroy' do
it 'correctly routes to a destroy action' do
expect(delete('/gitlab/gitlabhq/registry/repository/1/tags/rc1'))
.to route_to('projects/registry/tags#destroy',
namespace_id: 'gitlab',
project_id: 'gitlabhq',
repository_id: '1',
id: 'rc1')
end
it 'takes registry tag name constrains into account' do
expect(delete('/gitlab/gitlabhq/registry/repository/1/tags/-rc1'))
.not_to route_to('projects/registry/tags#destroy',
namespace_id: 'gitlab',
project_id: 'gitlabhq',
repository_id: '1',
id: '-rc1')
end
end
end
end
......@@ -40,7 +40,7 @@ describe Ci::CreatePipelineService, :services do
it 'increments the prometheus counter' do
expect(Gitlab::Metrics).to receive(:counter)
.with(:pipelines_created_count, "Pipelines created count")
.with(:pipelines_created_total, "Counter of pipelines created")
.and_call_original
pipeline
......@@ -320,5 +320,19 @@ describe Ci::CreatePipelineService, :services do
end.not_to change { Environment.count }
end
end
context 'when builds with auto-retries are configured' do
before do
config = YAML.dump(rspec: { script: 'rspec', retry: 2 })
stub_ci_pipeline_yaml_file(config)
end
it 'correctly creates builds with auto-retry value configured' do
pipeline = execute_service
expect(pipeline).to be_persisted
expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 2
end
end
end
end
......@@ -463,6 +463,35 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end
end
context 'when builds with auto-retries are configured' do
before do
create_build('build:1', stage_idx: 0, user: user, options: { retry: 2 })
create_build('test:1', stage_idx: 1, user: user, when: :on_failure)
create_build('test:2', stage_idx: 1, user: user, options: { retry: 1 })
end
it 'automatically retries builds in a valid order' do
expect(process_pipeline).to be_truthy
fail_running_or_pending
expect(builds_names).to eq %w[build:1 build:1]
expect(builds_statuses).to eq %w[failed pending]
succeed_running_or_pending
expect(builds_names).to eq %w[build:1 build:1 test:2]
expect(builds_statuses).to eq %w[failed success pending]
succeed_running_or_pending
expect(builds_names).to eq %w[build:1 build:1 test:2]
expect(builds_statuses).to eq %w[failed success success]
expect(pipeline.reload).to be_success
end
end
def process_pipeline
described_class.new(pipeline.project, user).execute(pipeline)
end
......
require 'spec_helper'
describe TestHookService, services: true do
let(:user) { create :user }
let(:group) { create :group }
let(:project) { create :project, :repository, group: group }
let(:project_hook) { create :project_hook, project: project }
let(:group_hook) { create :group_hook, group: group }
describe '#execute' do
it "successfully executes the project hook" do
stub_request(:post, project_hook.url).to_return(status: 200)
expect(TestHookService.new.execute(project_hook, user)).to be_truthy
end
it "successfully executes the group hook" do
project.reload
stub_request(:post, group_hook.url).to_return(status: 200)
expect(TestHookService.new.execute(group_hook, user)).to be_truthy
end
end
end
require 'spec_helper'
describe TestHooks::ProjectService do
let(:current_user) { create(:user) }
describe '#execute' do
let(:project) { create(:project, :repository) }
let(:hook) { create(:project_hook, project: project) }
let(:trigger) { 'not_implemented_events' }
let(:service) { described_class.new(hook, current_user, trigger) }
let(:sample_data) { { data: 'sample' } }
let(:success_result) { { status: :success, http_status: 200, message: 'ok' } }
it 'allows to set a custom project' do
project = double
service.project = project
expect(service.project).to eq(project)
end
context 'hook with not implemented test' do
it 'returns error message' do
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Testing not available for this hook' })
end
end
context 'push_events' do
let(:trigger) { 'push_events' }
it 'returns error message if not enough data' do
allow(project).to receive(:empty_repo?).and_return(true)
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Ensure the project has at least one commit.' })
end
it 'executes hook' do
allow(project).to receive(:empty_repo?).and_return(false)
allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
context 'tag_push_events' do
let(:trigger) { 'tag_push_events' }
it 'returns error message if not enough data' do
allow(project).to receive(:empty_repo?).and_return(true)
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Ensure the project has at least one commit.' })
end
it 'executes hook' do
allow(project).to receive(:empty_repo?).and_return(false)
allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
context 'note_events' do
let(:trigger) { 'note_events' }
it 'returns error message if not enough data' do
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Ensure the project has notes.' })
end
it 'executes hook' do
allow(project).to receive(:notes).and_return([Note.new])
allow(Gitlab::DataBuilder::Note).to receive(:build).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
context 'issues_events' do
let(:trigger) { 'issues_events' }
let(:issue) { build(:issue) }
it 'returns error message if not enough data' do
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Ensure the project has issues.' })
end
it 'executes hook' do
allow(project).to receive(:issues).and_return([issue])
allow(issue).to receive(:to_hook_data).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
context 'confidential_issues_events' do
let(:trigger) { 'confidential_issues_events' }
let(:issue) { build(:issue) }
it 'returns error message if not enough data' do
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Ensure the project has issues.' })
end
it 'executes hook' do
allow(project).to receive(:issues).and_return([issue])
allow(issue).to receive(:to_hook_data).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
context 'merge_requests_events' do
let(:trigger) { 'merge_requests_events' }
it 'returns error message if not enough data' do
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Ensure the project has merge requests.' })
end
it 'executes hook' do
create(:merge_request, source_project: project)
allow_any_instance_of(MergeRequest).to receive(:to_hook_data).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
context 'job_events' do
let(:trigger) { 'job_events' }
it 'returns error message if not enough data' do
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Ensure the project has CI jobs.' })
end
it 'executes hook' do
create(:ci_build, project: project)
allow(Gitlab::DataBuilder::Build).to receive(:build).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
context 'pipeline_events' do
let(:trigger) { 'pipeline_events' }
it 'returns error message if not enough data' do
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Ensure the project has CI pipelines.' })
end
it 'executes hook' do
create(:ci_empty_pipeline, project: project)
allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
context 'wiki_page_events' do
let(:trigger) { 'wiki_page_events' }
it 'returns error message if wiki disabled' do
allow(project).to receive(:wiki_enabled?).and_return(false)
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Ensure the wiki is enabled and has pages.' })
end
it 'returns error message if not enough data' do
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Ensure the wiki is enabled and has pages.' })
end
it 'executes hook' do
create(:wiki_page, wiki: project.wiki)
allow(Gitlab::DataBuilder::WikiPage).to receive(:build).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
end
end
require 'spec_helper'
describe TestHooks::SystemService do
let(:current_user) { create(:user) }
describe '#execute' do
let(:project) { create(:project, :repository) }
let(:hook) { create(:system_hook) }
let(:service) { described_class.new(hook, current_user, trigger) }
let(:sample_data) { { data: 'sample' }}
let(:success_result) { { status: :success, http_status: 200, message: 'ok' } }
before do
allow(Project).to receive(:first).and_return(project)
end
context 'hook with not implemented test' do
let(:trigger) { 'not_implemented_events' }
it 'returns error message' do
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Testing not available for this hook' })
end
end
context 'push_events' do
let(:trigger) { 'push_events' }
it 'returns error message if not enough data' do
allow(project).to receive(:empty_repo?).and_return(true)
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has commits." })
end
it 'executes hook' do
allow(project).to receive(:empty_repo?).and_return(false)
allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
context 'tag_push_events' do
let(:trigger) { 'tag_push_events' }
it 'returns error message if not enough data' do
allow(project.repository).to receive(:tags).and_return([])
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has tags." })
end
it 'executes hook' do
allow(project.repository).to receive(:tags).and_return(['tag'])
allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
context 'repository_update_events' do
let(:trigger) { 'repository_update_events' }
it 'returns error message if not enough data' do
allow(project).to receive(:commit).and_return(nil)
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has commits." })
end
it 'executes hook' do
allow(project).to receive(:empty_repo?).and_return(false)
allow(Gitlab::DataBuilder::Repository).to receive(:update).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
end
end
......@@ -7,16 +7,32 @@ describe Users::MigrateToGhostUserService, services: true do
context "migrating a user's associated records to the ghost user" do
context 'issues' do
include_examples "migrating a deleted user's associated records to the ghost user", Issue do
let(:created_record) { create(:issue, project: project, author: user) }
let(:assigned_record) { create(:issue, project: project, assignee: user) }
context 'deleted user is present as both author and edited_user' do
include_examples "migrating a deleted user's associated records to the ghost user", Issue, [:author, :last_edited_by] do
let(:created_record) do
create(:issue, project: project, author: user, last_edited_by: user)
end
end
end
context 'deleted user is present only as edited_user' do
include_examples "migrating a deleted user's associated records to the ghost user", Issue, [:last_edited_by] do
let(:created_record) { create(:issue, project: project, author: create(:user), last_edited_by: user) }
end
end
end
context 'merge requests' do
include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest do
let(:created_record) { create(:merge_request, source_project: project, author: user, target_branch: "first") }
let(:assigned_record) { create(:merge_request, source_project: project, assignee: user, target_branch: 'second') }
context 'deleted user is present as both author and merge_user' do
include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest, [:author, :merge_user] do
let(:created_record) { create(:merge_request, source_project: project, author: user, merge_user: user, target_branch: "first") }
end
end
context 'deleted user is present only as both merge_user' do
include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest, [:merge_user] do
let(:created_record) { create(:merge_request, source_project: project, merge_user: user, target_branch: "first") }
end
end
end
......@@ -33,9 +49,8 @@ describe Users::MigrateToGhostUserService, services: true do
end
context 'award emoji' do
include_examples "migrating a deleted user's associated records to the ghost user", AwardEmoji do
include_examples "migrating a deleted user's associated records to the ghost user", AwardEmoji, [:user] do
let(:created_record) { create(:award_emoji, user: user) }
let(:author_alias) { :user }
context "when the awardable already has an award emoji of the same name assigned to the ghost user" do
let(:awardable) { create(:issue) }
......
......@@ -58,7 +58,7 @@ describe WebHookService, services: true do
exception = exception_class.new('Exception message')
WebMock.stub_request(:post, project_hook.url).to_raise(exception)
expect(service_instance.execute).to eq([nil, exception.message])
expect(service_instance.execute).to eq({ status: :error, message: exception.message })
expect { service_instance.execute }.not_to raise_error
end
end
......@@ -66,13 +66,13 @@ describe WebHookService, services: true do
it 'handles 200 status code' do
WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success')
expect(service_instance.execute).to eq([200, 'Success'])
expect(service_instance.execute).to include({ status: :success, http_status: 200, message: 'Success' })
end
it 'handles 2xx status codes' do
WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: 'Success')
expect(service_instance.execute).to eq([201, 'Success'])
expect(service_instance.execute).to include({ status: :success, http_status: 201, message: 'Success' })
end
context 'execution logging' do
......
require "spec_helper"
shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class|
shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class, fields|
record_class_name = record_class.to_s.titleize.downcase
let(:project) { create(:project) }
......@@ -11,6 +11,7 @@ shared_examples "migrating a deleted user's associated records to the ghost user
context "for a #{record_class_name} the user has created" do
let!(:record) { created_record }
let(:migrated_fields) { fields || [:author] }
it "does not delete the #{record_class_name}" do
service.execute
......@@ -18,22 +19,20 @@ shared_examples "migrating a deleted user's associated records to the ghost user
expect(record_class.find_by_id(record.id)).to be_present
end
it "migrates the #{record_class_name} so that the 'Ghost User' is the #{record_class_name} owner" do
it "blocks the user before migrating #{record_class_name}s to the 'Ghost User'" do
service.execute
migrated_record = record_class.find_by_id(record.id)
if migrated_record.respond_to?(:author)
expect(migrated_record.author).to eq(User.ghost)
else
expect(migrated_record.send(author_alias)).to eq(User.ghost)
end
expect(user).to be_blocked
end
it "blocks the user before migrating #{record_class_name}s to the 'Ghost User'" do
it 'migrates all associated fields to te "Ghost user"' do
service.execute
expect(user).to be_blocked
migrated_record = record_class.find_by_id(record.id)
migrated_fields.each do |field|
expect(migrated_record.public_send(field)).to eq(User.ghost)
end
end
context "race conditions" do
......
......@@ -78,7 +78,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
wiki_page_service = WikiPages::CreateService.new(project, user, opts)
@wiki_page = wiki_page_service.execute
@wiki_page_sample_data = wiki_page_service.hook_data(@wiki_page, 'create')
@wiki_page_sample_data = Gitlab::DataBuilder::WikiPage.build(@wiki_page, user, 'create')
end
it "calls Slack/Mattermost API for push events" do
......
# Helper allows you to sort items
#
# Params
# value - value for sorting
#
# Usage:
# include SortingHelper
#
# sorting_by('Oldest updated')
#
module SortingHelper
def sorting_by(value)
find('button.dropdown-toggle').click
page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
click_link value
end
end
end
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