Commit 5caf1066 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge remote-tracking branch 'origin/master' into 18141-pipeline-graph

parents f52f62d7 d1da2e81
image: "ruby:2.1" image: "ruby:2.3.1"
cache: cache:
key: "ruby21" key: "ruby-231"
paths: paths:
- vendor/apt - vendor/apt
- vendor/ruby - vendor/ruby
...@@ -15,6 +15,7 @@ variables: ...@@ -15,6 +15,7 @@ variables:
USE_DB: "true" USE_DB: "true"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
GIT_DEPTH: "20" GIT_DEPTH: "20"
PHANTOMJS_VERSION: "2.1.1"
before_script: before_script:
- source ./scripts/prepare_build.sh - source ./scripts/prepare_build.sh
...@@ -138,57 +139,57 @@ spinach 7 10: *spinach-knapsack ...@@ -138,57 +139,57 @@ spinach 7 10: *spinach-knapsack
spinach 8 10: *spinach-knapsack spinach 8 10: *spinach-knapsack
spinach 9 10: *spinach-knapsack spinach 9 10: *spinach-knapsack
# Execute all testing suites against Ruby 2.3 # Execute all testing suites against Ruby 2.1
.ruby-23: &ruby-23 .ruby-21: &ruby-21
image: "ruby:2.3" image: "ruby:2.1"
<<: *use-db <<: *use-db
only: only:
- master - master
cache: cache:
key: "ruby-23" key: "ruby21"
paths: paths:
- vendor/apt - vendor/apt
- vendor/ruby - vendor/ruby
.rspec-knapsack-ruby23: &rspec-knapsack-ruby23 .rspec-knapsack-ruby21: &rspec-knapsack-ruby21
<<: *rspec-knapsack <<: *rspec-knapsack
<<: *ruby-23 <<: *ruby-21
.spinach-knapsack-ruby23: &spinach-knapsack-ruby23 .spinach-knapsack-ruby21: &spinach-knapsack-ruby21
<<: *spinach-knapsack <<: *spinach-knapsack
<<: *ruby-23 <<: *ruby-21
rspec 0 20 ruby23: *rspec-knapsack-ruby23 rspec 0 20 ruby21: *rspec-knapsack-ruby21
rspec 1 20 ruby23: *rspec-knapsack-ruby23 rspec 1 20 ruby21: *rspec-knapsack-ruby21
rspec 2 20 ruby23: *rspec-knapsack-ruby23 rspec 2 20 ruby21: *rspec-knapsack-ruby21
rspec 3 20 ruby23: *rspec-knapsack-ruby23 rspec 3 20 ruby21: *rspec-knapsack-ruby21
rspec 4 20 ruby23: *rspec-knapsack-ruby23 rspec 4 20 ruby21: *rspec-knapsack-ruby21
rspec 5 20 ruby23: *rspec-knapsack-ruby23 rspec 5 20 ruby21: *rspec-knapsack-ruby21
rspec 6 20 ruby23: *rspec-knapsack-ruby23 rspec 6 20 ruby21: *rspec-knapsack-ruby21
rspec 7 20 ruby23: *rspec-knapsack-ruby23 rspec 7 20 ruby21: *rspec-knapsack-ruby21
rspec 8 20 ruby23: *rspec-knapsack-ruby23 rspec 8 20 ruby21: *rspec-knapsack-ruby21
rspec 9 20 ruby23: *rspec-knapsack-ruby23 rspec 9 20 ruby21: *rspec-knapsack-ruby21
rspec 10 20 ruby23: *rspec-knapsack-ruby23 rspec 10 20 ruby21: *rspec-knapsack-ruby21
rspec 11 20 ruby23: *rspec-knapsack-ruby23 rspec 11 20 ruby21: *rspec-knapsack-ruby21
rspec 12 20 ruby23: *rspec-knapsack-ruby23 rspec 12 20 ruby21: *rspec-knapsack-ruby21
rspec 13 20 ruby23: *rspec-knapsack-ruby23 rspec 13 20 ruby21: *rspec-knapsack-ruby21
rspec 14 20 ruby23: *rspec-knapsack-ruby23 rspec 14 20 ruby21: *rspec-knapsack-ruby21
rspec 15 20 ruby23: *rspec-knapsack-ruby23 rspec 15 20 ruby21: *rspec-knapsack-ruby21
rspec 16 20 ruby23: *rspec-knapsack-ruby23 rspec 16 20 ruby21: *rspec-knapsack-ruby21
rspec 17 20 ruby23: *rspec-knapsack-ruby23 rspec 17 20 ruby21: *rspec-knapsack-ruby21
rspec 18 20 ruby23: *rspec-knapsack-ruby23 rspec 18 20 ruby21: *rspec-knapsack-ruby21
rspec 19 20 ruby23: *rspec-knapsack-ruby23 rspec 19 20 ruby21: *rspec-knapsack-ruby21
spinach 0 10 ruby23: *spinach-knapsack-ruby23 spinach 0 10 ruby21: *spinach-knapsack-ruby21
spinach 1 10 ruby23: *spinach-knapsack-ruby23 spinach 1 10 ruby21: *spinach-knapsack-ruby21
spinach 2 10 ruby23: *spinach-knapsack-ruby23 spinach 2 10 ruby21: *spinach-knapsack-ruby21
spinach 3 10 ruby23: *spinach-knapsack-ruby23 spinach 3 10 ruby21: *spinach-knapsack-ruby21
spinach 4 10 ruby23: *spinach-knapsack-ruby23 spinach 4 10 ruby21: *spinach-knapsack-ruby21
spinach 5 10 ruby23: *spinach-knapsack-ruby23 spinach 5 10 ruby21: *spinach-knapsack-ruby21
spinach 6 10 ruby23: *spinach-knapsack-ruby23 spinach 6 10 ruby21: *spinach-knapsack-ruby21
spinach 7 10 ruby23: *spinach-knapsack-ruby23 spinach 7 10 ruby21: *spinach-knapsack-ruby21
spinach 8 10 ruby23: *spinach-knapsack-ruby23 spinach 8 10 ruby21: *spinach-knapsack-ruby21
spinach 9 10 ruby23: *spinach-knapsack-ruby23 spinach 9 10 ruby21: *spinach-knapsack-ruby21
# Other generic tests # Other generic tests
...@@ -232,6 +233,13 @@ teaspoon: ...@@ -232,6 +233,13 @@ teaspoon:
paths: paths:
- coverage-javascript/default/ - coverage-javascript/default/
lint-doc:
stage: test
image: "phusion/baseimage:latest"
before_script: []
script:
- scripts/lint-doc.sh
bundler:audit: bundler:audit:
stage: test stage: test
<<: *ruby-static-analysis <<: *ruby-static-analysis
......
#
# This list is used by git-shortlog to make contributions from the
# same person appearing to be so.
#
Achilleas Pipinellis <axilleas@axilleas.me> <axilleas@archlinux.gr>
Achilleas Pipinellis <axilleas@axilleas.me> <axilleas@users.noreply.github.com>
Dmitriy Zaporozhets <dzaporozhets@gitlab.com> <dmitriy.zaporozhets@gmail.com>
Dmitriy Zaporozhets <dzaporozhets@gitlab.com> <dzaporozhets@sphereconsultinginc.com>
Douwe Maan <douwe@gitlab.com> <douwe@selenight.nl>
Douwe Maan <douwe@gitlab.com> <me@douwe.me>
Grzegorz Bizon <grzegorz@gitlab.com> <grzegorz.bizon@ntsn.pl>
Grzegorz Bizon <grzegorz@gitlab.com> <grzesiek.bizon@gmail.com>
Jacob Vosmaer <jacob@gitlab.com> <contact@jacobvosmaer.nl>
Jacob Vosmaer <jacob@gitlab.com> Jacob Vosmaer (GitLab) <jacob@gitlab.com>
Jacob Schatz <jschatz@gitlab.com> <jacobschatz@Jacobs-MacBook-Pro.local>
Jacob Schatz <jschatz@gitlab.com> <jacobschatz@Jacobs-MBP.fios-router.home>
Jacob Schatz <jschatz@gitlab.com> <jschatz1@gmail.com>
James Lopez <james@jameslopez.es> <james@gitlab.com>
James Lopez <james@jameslopez.es> <james.lopez@vodafone.com>
Kamil Trzciński <kamil@gitlab.com> <ayufan@ayufan.eu>
Marin Jankovski <maxlazio@gmail.com> <marin@gitlab.com>
Phil Hughes <me@iamphill.com> <theephil@gmail.com>
Rémy Coutable <remy@rymai.me> <remy@gitlab.com>
Robert Schilling <rschilling@student.tugraz.at> <Razer6@users.noreply.github.com>
Robert Schilling <rschilling@student.tugraz.at> <schilling.ro@gmail.com>
Robert Speicher <robert@gitlab.com> <rspeicher@gmail.com>
Stan Hu <stanhu@gmail.com> <stanhu@alum.mit.edu>
Stan Hu <stanhu@gmail.com> <stanhu@packetzoom.com>
Stan Hu <stanhu@gmail.com> <stanhu@users.noreply.github.com>
Stan Hu <stanhu@gmail.com> stanhu <stanhu@gmail.com>
Sytse Sijbrandij <sytse@gitlab.com> <sytse+admin@gitlab.com>
Sytse Sijbrandij <sytse@gitlab.com> <sytse@dosire.com>
Sytse Sijbrandij <sytse@gitlab.com> <sytses@gmail.com>
Sytse Sijbrandij <sytse@gitlab.com> dosire <sytse@gitlab.com>
This diff is collapsed.
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'rails', '4.2.7' gem 'rails', '4.2.7.1'
gem 'rails-deprecated_sanitizer', '~> 1.0.3' gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with # Responders respond_to and respond_with
...@@ -163,9 +163,6 @@ gem 'redis-rails', '~> 4.0.0' ...@@ -163,9 +163,6 @@ gem 'redis-rails', '~> 4.0.0'
gem 'redis', '~> 3.2' gem 'redis', '~> 3.2'
gem 'connection_pool', '~> 2.0' gem 'connection_pool', '~> 2.0'
# Campfire integration
gem 'tinder', '~> 1.10.0'
# HipChat integration # HipChat integration
gem 'hipchat', '~> 1.5.0' gem 'hipchat', '~> 1.5.0'
......
...@@ -3,34 +3,34 @@ GEM ...@@ -3,34 +3,34 @@ GEM
specs: specs:
RedCloth (4.3.2) RedCloth (4.3.2)
ace-rails-ap (4.0.2) ace-rails-ap (4.0.2)
actionmailer (4.2.7) actionmailer (4.2.7.1)
actionpack (= 4.2.7) actionpack (= 4.2.7.1)
actionview (= 4.2.7) actionview (= 4.2.7.1)
activejob (= 4.2.7) activejob (= 4.2.7.1)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.7) actionpack (4.2.7.1)
actionview (= 4.2.7) actionview (= 4.2.7.1)
activesupport (= 4.2.7) activesupport (= 4.2.7.1)
rack (~> 1.6) rack (~> 1.6)
rack-test (~> 0.6.2) rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.7) actionview (4.2.7.1)
activesupport (= 4.2.7) activesupport (= 4.2.7.1)
builder (~> 3.1) builder (~> 3.1)
erubis (~> 2.7.0) erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
activejob (4.2.7) activejob (4.2.7.1)
activesupport (= 4.2.7) activesupport (= 4.2.7.1)
globalid (>= 0.3.0) globalid (>= 0.3.0)
activemodel (4.2.7) activemodel (4.2.7.1)
activesupport (= 4.2.7) activesupport (= 4.2.7.1)
builder (~> 3.1) builder (~> 3.1)
activerecord (4.2.7) activerecord (4.2.7.1)
activemodel (= 4.2.7) activemodel (= 4.2.7.1)
activesupport (= 4.2.7) activesupport (= 4.2.7.1)
arel (~> 6.0) arel (~> 6.0)
activerecord-session_store (1.0.0) activerecord-session_store (1.0.0)
actionpack (>= 4.0, < 5.1) actionpack (>= 4.0, < 5.1)
...@@ -38,7 +38,7 @@ GEM ...@@ -38,7 +38,7 @@ GEM
multi_json (~> 1.11, >= 1.11.2) multi_json (~> 1.11, >= 1.11.2)
rack (>= 1.5.2, < 3) rack (>= 1.5.2, < 3)
railties (>= 4.0, < 5.1) railties (>= 4.0, < 5.1)
activesupport (4.2.7) activesupport (4.2.7.1)
i18n (~> 0.7) i18n (~> 0.7)
json (~> 1.7, >= 1.7.7) json (~> 1.7, >= 1.7.7)
minitest (~> 5.1) minitest (~> 5.1)
...@@ -289,7 +289,7 @@ GEM ...@@ -289,7 +289,7 @@ GEM
omniauth (~> 1.0) omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1) pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.3) rubyntlm (~> 0.3)
globalid (0.3.6) globalid (0.3.7)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
gollum-grit_adapter (1.0.1) gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1) gitlab-grit (~> 2.7, >= 2.7.1)
...@@ -335,11 +335,10 @@ GEM ...@@ -335,11 +335,10 @@ GEM
activesupport (>= 2) activesupport (>= 2)
nokogiri (~> 1.4) nokogiri (~> 1.4)
htmlentities (4.3.4) htmlentities (4.3.4)
http_parser.rb (0.5.3)
httparty (0.13.7) httparty (0.13.7)
json (~> 1.8) json (~> 1.8)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpclient (2.7.0.1) httpclient (2.8.2)
i18n (0.7.0) i18n (0.7.0)
ice_nine (0.11.1) ice_nine (0.11.1)
influxdb (0.2.3) influxdb (0.2.3)
...@@ -519,16 +518,16 @@ GEM ...@@ -519,16 +518,16 @@ GEM
rack rack
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rails (4.2.7) rails (4.2.7.1)
actionmailer (= 4.2.7) actionmailer (= 4.2.7.1)
actionpack (= 4.2.7) actionpack (= 4.2.7.1)
actionview (= 4.2.7) actionview (= 4.2.7.1)
activejob (= 4.2.7) activejob (= 4.2.7.1)
activemodel (= 4.2.7) activemodel (= 4.2.7.1)
activerecord (= 4.2.7) activerecord (= 4.2.7.1)
activesupport (= 4.2.7) activesupport (= 4.2.7.1)
bundler (>= 1.3.0, < 2.0) bundler (>= 1.3.0, < 2.0)
railties (= 4.2.7) railties (= 4.2.7.1)
sprockets-rails sprockets-rails
rails-deprecated_sanitizer (1.0.3) rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha) activesupport (>= 4.2.0.alpha)
...@@ -538,9 +537,9 @@ GEM ...@@ -538,9 +537,9 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1) rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3) rails-html-sanitizer (1.0.3)
loofah (~> 2.0) loofah (~> 2.0)
railties (4.2.7) railties (4.2.7.1)
actionpack (= 4.2.7) actionpack (= 4.2.7.1)
activesupport (= 4.2.7) activesupport (= 4.2.7.1)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.1.0) rainbow (2.1.0)
...@@ -672,7 +671,6 @@ GEM ...@@ -672,7 +671,6 @@ GEM
redis-namespace (>= 1.5.2) redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24) rufus-scheduler (>= 2.0.24)
sidekiq (>= 4.0.0) sidekiq (>= 4.0.0)
simple_oauth (0.1.9)
simplecov (0.12.0) simplecov (0.12.0)
docile (~> 1.1.0) docile (~> 1.1.0)
json (>= 1.8, < 3) json (>= 1.8, < 3)
...@@ -742,21 +740,8 @@ GEM ...@@ -742,21 +740,8 @@ GEM
tilt (2.0.5) tilt (2.0.5)
timecop (0.8.1) timecop (0.8.1)
timfel-krb5-auth (0.8.3) timfel-krb5-auth (0.8.3)
tinder (1.10.1)
eventmachine (~> 1.0)
faraday (~> 0.9.0)
faraday_middleware (~> 0.9)
hashie (>= 1.0)
json (~> 1.8.0)
mime-types
multi_json (~> 1.7)
twitter-stream (~> 0.1)
turbolinks (2.5.3) turbolinks (2.5.3)
coffee-rails coffee-rails
twitter-stream (0.1.16)
eventmachine (>= 0.12.8)
http_parser.rb (~> 0.5.1)
simple_oauth (~> 0.1.4)
tzinfo (1.2.2) tzinfo (1.2.2)
thread_safe (~> 0.1) thread_safe (~> 0.1)
u2f (0.2.1) u2f (0.2.1)
...@@ -929,7 +914,7 @@ DEPENDENCIES ...@@ -929,7 +914,7 @@ DEPENDENCIES
rack-attack (~> 4.3.1) rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
rails (= 4.2.7) rails (= 4.2.7.1)
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0) rainbow (~> 2.1.0)
rblineprof (~> 0.3.6) rblineprof (~> 0.3.6)
...@@ -981,7 +966,6 @@ DEPENDENCIES ...@@ -981,7 +966,6 @@ DEPENDENCIES
teaspoon-jasmine (~> 2.2.0) teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 0.4.2) test_after_commit (~> 0.4.2)
thin (~> 1.7.0) thin (~> 1.7.0)
tinder (~> 1.10.0)
turbolinks (~> 2.5.0) turbolinks (~> 2.5.0)
u2f (~> 0.2.1) u2f (~> 0.2.1)
uglifier (~> 2.7.2) uglifier (~> 2.7.2)
......
...@@ -9,10 +9,11 @@ ...@@ -9,10 +9,11 @@
licensePath: "/api/:version/licenses/:key", licensePath: "/api/:version/licenses/:key",
gitignorePath: "/api/:version/gitignores/:key", gitignorePath: "/api/:version/gitignores/:key",
gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key", gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key",
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
group: function(group_id, callback) { group: function(group_id, callback) {
var url; var url = Api.buildUrl(Api.groupPath)
url = Api.buildUrl(Api.groupPath); .replace(':id', group_id);
url = url.replace(':id', group_id);
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
...@@ -24,8 +25,7 @@ ...@@ -24,8 +25,7 @@
}); });
}, },
groups: function(query, skip_ldap, callback) { groups: function(query, skip_ldap, callback) {
var url; var url = Api.buildUrl(Api.groupsPath);
url = Api.buildUrl(Api.groupsPath);
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
...@@ -39,8 +39,7 @@ ...@@ -39,8 +39,7 @@
}); });
}, },
namespaces: function(query, callback) { namespaces: function(query, callback) {
var url; var url = Api.buildUrl(Api.namespacesPath);
url = Api.buildUrl(Api.namespacesPath);
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
...@@ -54,8 +53,7 @@ ...@@ -54,8 +53,7 @@
}); });
}, },
projects: function(query, order, callback) { projects: function(query, order, callback) {
var url; var url = Api.buildUrl(Api.projectsPath);
url = Api.buildUrl(Api.projectsPath);
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
...@@ -70,9 +68,8 @@ ...@@ -70,9 +68,8 @@
}); });
}, },
newLabel: function(project_id, data, callback) { newLabel: function(project_id, data, callback) {
var url; var url = Api.buildUrl(Api.labelsPath)
url = Api.buildUrl(Api.labelsPath); .replace(':id', project_id);
url = url.replace(':id', project_id);
data.private_token = gon.api_token; data.private_token = gon.api_token;
return $.ajax({ return $.ajax({
url: url, url: url,
...@@ -86,9 +83,8 @@ ...@@ -86,9 +83,8 @@
}); });
}, },
groupProjects: function(group_id, query, callback) { groupProjects: function(group_id, query, callback) {
var url; var url = Api.buildUrl(Api.groupProjectsPath)
url = Api.buildUrl(Api.groupProjectsPath); .replace(':id', group_id);
url = url.replace(':id', group_id);
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: {
...@@ -102,8 +98,8 @@ ...@@ -102,8 +98,8 @@
}); });
}, },
licenseText: function(key, data, callback) { licenseText: function(key, data, callback) {
var url; var url = Api.buildUrl(Api.licensePath)
url = Api.buildUrl(Api.licensePath).replace(':key', key); .replace(':key', key);
return $.ajax({ return $.ajax({
url: url, url: url,
data: data data: data
...@@ -112,19 +108,32 @@ ...@@ -112,19 +108,32 @@
}); });
}, },
gitignoreText: function(key, callback) { gitignoreText: function(key, callback) {
var url; var url = Api.buildUrl(Api.gitignorePath)
url = Api.buildUrl(Api.gitignorePath).replace(':key', key); .replace(':key', key);
return $.get(url, function(gitignore) { return $.get(url, function(gitignore) {
return callback(gitignore); return callback(gitignore);
}); });
}, },
gitlabCiYml: function(key, callback) { gitlabCiYml: function(key, callback) {
var url; var url = Api.buildUrl(Api.gitlabCiYmlPath)
url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key); .replace(':key', key);
return $.get(url, function(file) { return $.get(url, function(file) {
return callback(file); return callback(file);
}); });
}, },
issueTemplate: function(namespacePath, projectPath, key, type, callback) {
var url = Api.buildUrl(Api.issuableTemplatePath)
.replace(':key', key)
.replace(':type', type)
.replace(':project_path', projectPath)
.replace(':namespace_path', namespacePath);
$.ajax({
url: url,
dataType: 'json'
}).done(function(file) {
callback(null, file);
}).error(callback);
},
buildUrl: function(url) { buildUrl: function(url) {
if (gon.relative_url_root != null) { if (gon.relative_url_root != null) {
url = gon.relative_url_root + url; url = gon.relative_url_root + url;
......
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
/*= require date.format */ /*= require date.format */
/*= require_directory ./behaviors */ /*= require_directory ./behaviors */
/*= require_directory ./blob */ /*= require_directory ./blob */
/*= require_directory ./templates */
/*= require_directory ./commit */ /*= require_directory ./commit */
/*= require_directory ./extensions */ /*= require_directory ./extensions */
/*= require_directory ./lib/utils */ /*= require_directory ./lib/utils */
......
...@@ -161,21 +161,9 @@ ...@@ -161,21 +161,9 @@
$emojiButton = votesBlock.find("[data-emoji=" + mutualVote + "]").parent(); $emojiButton = votesBlock.find("[data-emoji=" + mutualVote + "]").parent();
isAlreadyVoted = $emojiButton.hasClass('active'); isAlreadyVoted = $emojiButton.hasClass('active');
if (isAlreadyVoted) { if (isAlreadyVoted) {
this.showEmojiLoader($emojiButton); this.addAward(votesBlock, awardUrl, mutualVote, false);
return this.addAward(votesBlock, awardUrl, mutualVote, false, function() {
return $emojiButton.removeClass('is-loading');
});
}
} }
};
AwardsHandler.prototype.showEmojiLoader = function($emojiButton) {
var $loader;
$loader = $emojiButton.find('.fa-spinner');
if (!$loader.length) {
$emojiButton.append('<i class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading"></i>');
} }
return $emojiButton.addClass('is-loading');
}; };
AwardsHandler.prototype.isActive = function($emojiButton) { AwardsHandler.prototype.isActive = function($emojiButton) {
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
} }
this.onClick = bind(this.onClick, this); this.onClick = bind(this.onClick, this);
this.dropdown = opts.dropdown, this.data = opts.data, this.pattern = opts.pattern, this.wrapper = opts.wrapper, this.editor = opts.editor, this.fileEndpoint = opts.fileEndpoint, this.$input = (ref = opts.$input) != null ? ref : $('#file_name'); this.dropdown = opts.dropdown, this.data = opts.data, this.pattern = opts.pattern, this.wrapper = opts.wrapper, this.editor = opts.editor, this.fileEndpoint = opts.fileEndpoint, this.$input = (ref = opts.$input) != null ? ref : $('#file_name');
this.dropdownIcon = $('.fa-chevron-down', this.dropdown);
this.buildDropdown(); this.buildDropdown();
this.bindEvents(); this.bindEvents();
this.onFilenameUpdate(); this.onFilenameUpdate();
...@@ -60,11 +61,26 @@ ...@@ -60,11 +61,26 @@
return this.requestFile(item); return this.requestFile(item);
}; };
TemplateSelector.prototype.requestFile = function(item) {}; TemplateSelector.prototype.requestFile = function(item) {
// This `requestFile` method is an abstract method that should
// be added by all subclasses.
};
TemplateSelector.prototype.requestFileSuccess = function(file) { TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) {
this.editor.setValue(file.content, 1); this.editor.setValue(file.content, 1);
return this.editor.focus(); if (!skipFocus) this.editor.focus();
};
TemplateSelector.prototype.startLoadingSpinner = function() {
this.dropdownIcon
.addClass('fa-spinner fa-spin')
.removeClass('fa-chevron-down');
};
TemplateSelector.prototype.stopLoadingSpinner = function() {
this.dropdownIcon
.addClass('fa-chevron-down')
.removeClass('fa-spinner fa-spin');
}; };
return TemplateSelector; return TemplateSelector;
......
...@@ -55,6 +55,7 @@ ...@@ -55,6 +55,7 @@
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new GLForm($('.issue-form')); new GLForm($('.issue-form'));
new IssuableForm($('.issue-form')); new IssuableForm($('.issue-form'));
new IssuableTemplateSelectors();
break; break;
case 'projects:merge_requests:new': case 'projects:merge_requests:new':
case 'projects:merge_requests:edit': case 'projects:merge_requests:edit':
...@@ -62,6 +63,7 @@ ...@@ -62,6 +63,7 @@
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new GLForm($('.merge-request-form')); new GLForm($('.merge-request-form'));
new IssuableForm($('.merge-request-form')); new IssuableForm($('.merge-request-form'));
new IssuableTemplateSelectors();
break; break;
case 'projects:tags:new': case 'projects:tags:new':
new ZenMode(); new ZenMode();
...@@ -186,6 +188,12 @@ ...@@ -186,6 +188,12 @@
break; break;
case 'projects': case 'projects':
new NamespaceSelects(); new NamespaceSelects();
break;
case 'labels':
switch (path[2]) {
case 'edit':
new Labels();
}
} }
break; break;
case 'dashboard': case 'dashboard':
...@@ -211,6 +219,7 @@ ...@@ -211,6 +219,7 @@
new ProjectNew(); new ProjectNew();
break; break;
case 'show': case 'show':
new Star();
new ProjectNew(); new ProjectNew();
new ProjectShow(); new ProjectShow();
new NotificationsDropdown(); new NotificationsDropdown();
......
/*= require markdown_preview */ /*= require preview_markdown */
(function() { (function() {
this.DropzoneInput = (function() { this.DropzoneInput = (function() {
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
this.render = bind(this.render, this); this.render = bind(this.render, this);
this.VIEW_TYPE = $('input#view[type=hidden]').val(); this.VIEW_TYPE = $('input#view[type=hidden]').val();
debounce = _.debounce(this.render, DEBOUNCE_TIMEOUT_DURATION); debounce = _.debounce(this.render, DEBOUNCE_TIMEOUT_DURATION);
$(document).off('mouseover', LINE_COLUMN_CLASSES).off('mouseleave', LINE_COLUMN_CLASSES).on('mouseover', LINE_COLUMN_CLASSES, debounce).on('mouseleave', LINE_COLUMN_CLASSES, this.destroy); $(this.filesContainerElement).off('mouseover', LINE_COLUMN_CLASSES).off('mouseleave', LINE_COLUMN_CLASSES).on('mouseover', LINE_COLUMN_CLASSES, debounce).on('mouseleave', LINE_COLUMN_CLASSES, this.destroy);
} }
FilesCommentButton.prototype.render = function(e) { FilesCommentButton.prototype.render = function(e) {
......
...@@ -5,13 +5,10 @@ ...@@ -5,13 +5,10 @@
this.Issuable = { this.Issuable = {
init: function() { init: function() {
if (!issuable_created) {
issuable_created = true;
Issuable.initTemplates(); Issuable.initTemplates();
Issuable.initSearch(); Issuable.initSearch();
Issuable.initChecks(); Issuable.initChecks();
return Issuable.initLabelFilterRemove(); return Issuable.initLabelFilterRemove();
}
}, },
initTemplates: function() { initTemplates: function() {
return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>'); return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>');
......
...@@ -70,13 +70,15 @@ ...@@ -70,13 +70,15 @@
name: newLabelField.val(), name: newLabelField.val(),
color: newColorField.val() color: newColorField.val()
}, function(label) { }, function(label) {
var errors;
$newLabelCreateButton.enable(); $newLabelCreateButton.enable();
if (label.message != null) { if (label.message != null) {
errors = _.map(label.message, function(value, key) { var errorText = label.message;
if (_.isObject(label.message)) {
errorText = _.map(label.message, function(value, key) {
return key + " " + value[0]; return key + " " + value[0];
}); }).join('<br/>');
return $newLabelError.html(errors.join("<br/>")).show(); }
return $newLabelError.html(errorText).show();
} else { } else {
return $('.dropdown-menu-back', $dropdown.parent()).trigger('click'); return $('.dropdown-menu-back', $dropdown.parent()).trigger('click');
} }
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
}; };
MarkdownPreview.prototype.renderMarkdown = function(text, success) { MarkdownPreview.prototype.renderMarkdown = function(text, success) {
if (!window.markdown_preview_path) { if (!window.preview_markdown_path) {
return; return;
} }
if (text === this.ajaxCache.text) { if (text === this.ajaxCache.text) {
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
} }
return $.ajax({ return $.ajax({
type: 'POST', type: 'POST',
url: window.markdown_preview_path, url: window.preview_markdown_path,
data: { data: {
text: text text: text
}, },
......
...@@ -44,8 +44,8 @@ ...@@ -44,8 +44,8 @@
// Enable submit button // Enable submit button
const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]'); const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_level_attributes][access_level]"]'); const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]');
const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_level_attributes][access_level]"]'); const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]');
if ($branchInput.val() && $allowedToMergeInput.val() && $allowedToPushInput.val()){ if ($branchInput.val() && $allowedToMergeInput.val() && $allowedToPushInput.val()){
this.$form.find('input[type="submit"]').removeAttr('disabled'); this.$form.find('input[type="submit"]').removeAttr('disabled');
......
...@@ -39,12 +39,14 @@ ...@@ -39,12 +39,14 @@
_method: 'PATCH', _method: 'PATCH',
id: this.$wrap.data('banchId'), id: this.$wrap.data('banchId'),
protected_branch: { protected_branch: {
merge_access_level_attributes: { merge_access_levels_attributes: [{
id: this.$allowedToMergeDropdown.data('access-level-id'),
access_level: $allowedToMergeInput.val() access_level: $allowedToMergeInput.val()
}, }],
push_access_level_attributes: { push_access_levels_attributes: [{
id: this.$allowedToPushDropdown.data('access-level-id'),
access_level: $allowedToPushInput.val() access_level: $allowedToPushInput.val()
} }]
} }
}, },
success: () => { success: () => {
......
/*= require ../blob/template_selector */
((global) => {
class IssuableTemplateSelector extends TemplateSelector {
constructor(...args) {
super(...args);
this.projectPath = this.dropdown.data('project-path');
this.namespacePath = this.dropdown.data('namespace-path');
this.issuableType = this.wrapper.data('issuable-type');
this.titleInput = $(`#${this.issuableType}_title`);
let initialQuery = {
name: this.dropdown.data('selected')
};
if (initialQuery.name) this.requestFile(initialQuery);
$('.reset-template', this.dropdown.parent()).on('click', () => {
if (this.currentTemplate) this.setInputValueToTemplateContent();
});
}
requestFile(query) {
this.startLoadingSpinner();
Api.issueTemplate(this.namespacePath, this.projectPath, query.name, this.issuableType, (err, currentTemplate) => {
this.currentTemplate = currentTemplate;
if (err) return; // Error handled by global AJAX error handler
this.stopLoadingSpinner();
this.setInputValueToTemplateContent();
});
return;
}
setInputValueToTemplateContent() {
// `this.requestFileSuccess` sets the value of the description input field
// to the content of the template selected.
if (this.titleInput.val() === '') {
// If the title has not yet been set, focus the title input and
// skip focusing the description input by setting `true` as the 2nd
// argument to `requestFileSuccess`.
this.requestFileSuccess(this.currentTemplate, true);
this.titleInput.focus();
} else {
this.requestFileSuccess(this.currentTemplate);
}
return;
}
}
global.IssuableTemplateSelector = IssuableTemplateSelector;
})(window);
((global) => {
class IssuableTemplateSelectors {
constructor(opts = {}) {
this.$dropdowns = opts.$dropdowns || $('.js-issuable-selector');
this.editor = opts.editor || this.initEditor();
this.$dropdowns.each((i, dropdown) => {
let $dropdown = $(dropdown);
new IssuableTemplateSelector({
pattern: /(\.md)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-issuable-selector-wrap'),
dropdown: $dropdown,
editor: this.editor
});
});
}
initEditor() {
let editor = $('.markdown-area');
// Proxy ace-editor's .setValue to jQuery's .val
editor.setValue = editor.val;
editor.getValue = editor.val;
return editor;
}
}
global.IssuableTemplateSelectors = IssuableTemplateSelectors;
})(window);
...@@ -164,6 +164,10 @@ ...@@ -164,6 +164,10 @@
@include btn-outline($white-light, $orange-normal, $orange-normal, $orange-light, $white-light, $orange-light); @include btn-outline($white-light, $orange-normal, $orange-normal, $orange-light, $white-light, $orange-light);
} }
&.btn-spam {
@include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light);
}
&.btn-danger, &.btn-danger,
&.btn-remove, &.btn-remove,
&.btn-red { &.btn-red {
......
...@@ -56,9 +56,13 @@ ...@@ -56,9 +56,13 @@
position: absolute; position: absolute;
top: 50%; top: 50%;
right: 6px; right: 6px;
margin-top: -4px; margin-top: -6px;
color: $dropdown-toggle-icon-color; color: $dropdown-toggle-icon-color;
font-size: 10px; font-size: 10px;
&.fa-spinner {
font-size: 16px;
margin-top: -8px;
}
} }
&:hover, { &:hover, {
...@@ -406,6 +410,7 @@ ...@@ -406,6 +410,7 @@
font-size: 14px; font-size: 14px;
a { a {
cursor: pointer;
padding-left: 10px; padding-left: 10px;
} }
} }
......
...@@ -6,11 +6,11 @@ ...@@ -6,11 +6,11 @@
table-layout: fixed; table-layout: fixed;
pre { pre {
padding: 10px; padding: 10px 0;
border: none; border: none;
border-radius: 0; border-radius: 0;
font-family: $monospace_font; font-family: $monospace_font;
font-size: $code_font_size !important; font-size: $code_font_size;
line-height: $code_line_height !important; line-height: $code_line_height !important;
margin: 0; margin: 0;
overflow: auto; overflow: auto;
...@@ -20,13 +20,20 @@ ...@@ -20,13 +20,20 @@
border-left: 1px solid; border-left: 1px solid;
code { code {
display: inline-block;
min-width: 100%;
font-family: $monospace_font; font-family: $monospace_font;
white-space: pre; white-space: normal;
word-wrap: normal; word-wrap: normal;
padding: 0; padding: 0;
.line { .line {
display: inline-block; display: block;
width: 100%;
min-height: 19px;
padding-left: 10px;
padding-right: 10px;
white-space: pre;
} }
} }
} }
......
.environments { .environments {
.commit-title { .commit-title {
margin: 0; margin: 0;
} }
.fa-play {
font-size: 14px;
}
.dropdown-new {
color: $table-text-gray;
}
.dropdown-menu {
.fa {
margin-right: 6px;
color: $table-text-gray;
}
}
.branch-name {
color: $gl-dark-link-color;
}
}
.table.builds.environments {
min-width: 500px;
.icon-container {
width: 20px;
text-align: center;
}
} }
...@@ -395,3 +395,12 @@ ...@@ -395,3 +395,12 @@
display: inline-block; display: inline-block;
line-height: 18px; line-height: 18px;
} }
.js-issuable-selector-wrap {
.js-issuable-selector {
width: 100%;
}
@media (max-width: $screen-sm-max) {
margin-bottom: $gl-padding;
}
}
...@@ -182,6 +182,17 @@ ...@@ -182,6 +182,17 @@
.btn { .btn {
color: inherit; color: inherit;
} }
a.btn {
padding: 0;
.has-tooltip {
top: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
line-height: 1.1;
}
}
} }
.label-options-toggle { .label-options-toggle {
......
...@@ -69,6 +69,10 @@ ...@@ -69,6 +69,10 @@
&.ci-success { &.ci-success {
color: $gl-success; color: $gl-success;
a.environment {
color: inherit;
}
} }
&.ci-success_with_warnings { &.ci-success_with_warnings {
...@@ -126,7 +130,6 @@ ...@@ -126,7 +130,6 @@
&.has-conflicts .fa-exclamation-triangle { &.has-conflicts .fa-exclamation-triangle {
color: $gl-warning; color: $gl-warning;
} }
} }
p:last-child { p:last-child {
......
...@@ -99,7 +99,7 @@ ...@@ -99,7 +99,7 @@
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 15px; margin-bottom: 15px;
max-width: 480px; max-width: 700px;
> p { > p {
margin-bottom: 0; margin-bottom: 0;
......
...@@ -20,10 +20,43 @@ ...@@ -20,10 +20,43 @@
} }
} }
.todo { .todos-list > .todo {
// workaround because we cannot use border-colapse
border-top: 1px solid transparent;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: row;
flex-direction: row;
&:hover { &:hover {
background-color: $row-hover;
border-color: $row-hover-border;
cursor: pointer; cursor: pointer;
} }
// overwrite border style of .content-list
&:last-child {
border-bottom: 1px solid transparent;
&:hover {
border-color: $row-hover-border;
}
}
.todo-actions {
display: -webkit-flex;
display: flex;
-webkit-justify-content: center;
justify-content: center;
-webkit-flex-direction: column;
flex-direction: column;
margin-left: 10px;
}
.todo-item {
-webkit-flex: auto;
flex: auto;
}
} }
.todo-item { .todo-item {
...@@ -43,8 +76,6 @@ ...@@ -43,8 +76,6 @@
} }
.todo-body { .todo-body {
margin-right: 174px;
.todo-note { .todo-note {
word-wrap: break-word; word-wrap: break-word;
...@@ -90,6 +121,12 @@ ...@@ -90,6 +121,12 @@
} }
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.todo {
.avatar {
display: none;
}
}
.todo-item { .todo-item {
.todo-title { .todo-title {
white-space: normal; white-space: normal;
...@@ -98,10 +135,6 @@ ...@@ -98,10 +135,6 @@
margin-bottom: 10px; margin-bottom: 10px;
} }
.avatar {
display: none;
}
.todo-body { .todo-body {
margin: 0; margin: 0;
border-left: 2px solid #ddd; border-left: 2px solid #ddd;
......
...@@ -48,9 +48,9 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -48,9 +48,9 @@ class Admin::GroupsController < Admin::ApplicationController
end end
def destroy def destroy
DestroyGroupService.new(@group, current_user).execute DestroyGroupService.new(@group, current_user).async_execute
redirect_to admin_groups_path, notice: 'Group was successfully deleted.' redirect_to admin_groups_path, alert: "Group '#{@group.name}' was scheduled for deletion."
end end
private private
......
...@@ -14,4 +14,14 @@ class Admin::SpamLogsController < Admin::ApplicationController ...@@ -14,4 +14,14 @@ class Admin::SpamLogsController < Admin::ApplicationController
head :ok head :ok
end end
end end
def mark_as_ham
spam_log = SpamLog.find(params[:id])
if HamService.new(spam_log).mark_as_ham!
redirect_to admin_spam_logs_path, notice: 'Spam log successfully submitted as ham.'
else
redirect_to admin_spam_logs_path, alert: 'Error with Akismet. Please check the logs for more info.'
end
end
end end
class AutocompleteController < ApplicationController class AutocompleteController < ApplicationController
skip_before_action :authenticate_user!, only: [:users] skip_before_action :authenticate_user!, only: [:users]
before_action :load_project, only: [:users]
before_action :find_users, only: [:users] before_action :find_users, only: [:users]
def users def users
...@@ -34,19 +35,13 @@ class AutocompleteController < ApplicationController ...@@ -34,19 +35,13 @@ class AutocompleteController < ApplicationController
def projects def projects
project = Project.find_by_id(params[:project_id]) project = Project.find_by_id(params[:project_id])
projects = projects_finder.execute(project, search: params[:search], offset_id: params[:offset_id])
projects = current_user.authorized_projects
projects = projects.search(params[:search]) if params[:search].present?
projects = projects.select do |project|
current_user.can?(:admin_issue, project)
end
no_project = { no_project = {
id: 0, id: 0,
name_with_namespace: 'No project', name_with_namespace: 'No project',
} }
projects.unshift(no_project) projects.unshift(no_project) unless params[:offset_id].present?
projects.delete(project)
render json: projects.to_json(only: [:id, :name_with_namespace], methods: :name_with_namespace) render json: projects.to_json(only: [:id, :name_with_namespace], methods: :name_with_namespace)
end end
...@@ -55,11 +50,8 @@ class AutocompleteController < ApplicationController ...@@ -55,11 +50,8 @@ class AutocompleteController < ApplicationController
def find_users def find_users
@users = @users =
if params[:project_id].present? if @project
project = Project.find(params[:project_id]) @project.team.users
return render_404 unless can?(current_user, :read_project, project)
project.team.users
elsif params[:group_id].present? elsif params[:group_id].present?
group = Group.find(params[:group_id]) group = Group.find(params[:group_id])
return render_404 unless can?(current_user, :read_group, group) return render_404 unless can?(current_user, :read_group, group)
...@@ -71,4 +63,18 @@ class AutocompleteController < ApplicationController ...@@ -71,4 +63,18 @@ class AutocompleteController < ApplicationController
User.none User.none
end end
end end
def load_project
@project ||= begin
if params[:project_id].present?
project = Project.find(params[:project_id])
return render_404 unless can?(current_user, :read_project, project)
project
end
end
end
def projects_finder
MoveToProjectFinder.new(current_user)
end
end end
...@@ -7,11 +7,16 @@ module ServiceParams ...@@ -7,11 +7,16 @@ module ServiceParams
:build_key, :server, :teamcity_url, :drone_url, :build_type, :build_key, :server, :teamcity_url, :drone_url, :build_type,
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels, :colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events, # We're using `issues_events` and `merge_requests_events`
:note_events, :build_events, :wiki_page_events, # in the view so we still need to explicitly state them
:notify_only_broken_builds, :add_pusher, # here. `Service#event_names` would only give
:send_from_committer_email, :disable_diffs, :external_wiki_url, # `issue_events` and `merge_request_events` (singular!)
:notify, :color, # See app/helpers/services_helper.rb for how we
# make those event names plural as special case.
:issues_events, :merge_requests_events,
:notify_only_broken_builds, :notify_only_broken_pipelines,
:add_pusher, :send_from_committer_email, :disable_diffs,
:external_wiki_url, :notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification, :server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
:jira_issue_transition_id] :jira_issue_transition_id]
...@@ -19,9 +24,7 @@ module ServiceParams ...@@ -19,9 +24,7 @@ module ServiceParams
FILTER_BLANK_PARAMS = [:password] FILTER_BLANK_PARAMS = [:password]
def service_params def service_params
dynamic_params = [] dynamic_params = @service.event_channel_names + @service.event_names
dynamic_params.concat(@service.event_channel_names)
service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params) service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params)
if service_params[:service].is_a?(Hash) if service_params[:service].is_a?(Hash)
......
module SpammableActions
extend ActiveSupport::Concern
included do
before_action :authorize_submit_spammable!, only: :mark_as_spam
end
def mark_as_spam
if SpamService.new(spammable).mark_as_spam!
redirect_to spammable, notice: "#{spammable.class.to_s} was submitted to Akismet successfully."
else
redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.'
end
end
private
def spammable
raise NotImplementedError, "#{self.class} does not implement #{__method__}"
end
def authorize_submit_spammable!
access_denied! unless current_user.admin?
end
end
...@@ -37,8 +37,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -37,8 +37,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def todos_counts def todos_counts
{ {
count: TodosFinder.new(current_user, state: :pending).execute.count, count: current_user.todos_pending_count,
done_count: TodosFinder.new(current_user, state: :done).execute.count done_count: current_user.todos_done_count
} }
end end
end end
...@@ -87,9 +87,9 @@ class GroupsController < Groups::ApplicationController ...@@ -87,9 +87,9 @@ class GroupsController < Groups::ApplicationController
end end
def destroy def destroy
DestroyGroupService.new(@group, current_user).execute DestroyGroupService.new(@group, current_user).async_execute
redirect_to root_path, alert: "Group '#{@group.name}' was successfully deleted." redirect_to root_path, alert: "Group '#{@group.name}' was scheduled for deletion."
end end
protected protected
......
class Import::GitlabProjectsController < Import::BaseController class Import::GitlabProjectsController < Import::BaseController
before_action :verify_gitlab_project_import_enabled before_action :verify_gitlab_project_import_enabled
before_action :authenticate_admin!
def new def new
@namespace_id = project_params[:namespace_id] @namespace_id = project_params[:namespace_id]
...@@ -47,4 +48,8 @@ class Import::GitlabProjectsController < Import::BaseController ...@@ -47,4 +48,8 @@ class Import::GitlabProjectsController < Import::BaseController
:path, :namespace_id, :file :path, :namespace_id, :file
) )
end end
def authenticate_admin!
render_404 unless current_user.is_admin?
end
end end
...@@ -4,12 +4,26 @@ class Projects::BadgesController < Projects::ApplicationController ...@@ -4,12 +4,26 @@ class Projects::BadgesController < Projects::ApplicationController
before_action :no_cache_headers, except: [:index] before_action :no_cache_headers, except: [:index]
def build def build
badge = Gitlab::Badge::Build.new(project, params[:ref]) build_status = Gitlab::Badge::Build::Status
.new(project, params[:ref])
render_badge build_status
end
def coverage
coverage_report = Gitlab::Badge::Coverage::Report
.new(project, params[:ref], params[:job])
render_badge coverage_report
end
private
def render_badge(badge)
respond_to do |format| respond_to do |format|
format.html { render_404 } format.html { render_404 }
format.svg do format.svg do
send_data(badge.data, type: badge.type, disposition: 'inline') render 'badge', locals: { badge: badge.template }
end end
end end
end end
......
...@@ -17,6 +17,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -17,6 +17,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action :require_branch_head, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff] before_action :editor_variables, except: [:show, :preview, :diff]
before_action :validate_diff_params, only: :diff before_action :validate_diff_params, only: :diff
before_action :set_last_commit_sha, only: [:edit, :update]
def new def new
commit unless @repository.empty? commit unless @repository.empty?
...@@ -33,7 +34,6 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -33,7 +34,6 @@ class Projects::BlobController < Projects::ApplicationController
end end
def edit def edit
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
blob.load_all_data!(@repository) blob.load_all_data!(@repository)
end end
...@@ -55,6 +55,10 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -55,6 +55,10 @@ class Projects::BlobController < Projects::ApplicationController
create_commit(Files::UpdateService, success_path: after_edit_path, create_commit(Files::UpdateService, success_path: after_edit_path,
failure_view: :edit, failure_view: :edit,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
rescue Files::UpdateService::FileChangedError
@conflict = true
render :edit
end end
def preview def preview
...@@ -152,7 +156,8 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -152,7 +156,8 @@ class Projects::BlobController < Projects::ApplicationController
file_path: @file_path, file_path: @file_path,
commit_message: params[:commit_message], commit_message: params[:commit_message],
file_content: params[:content], file_content: params[:content],
file_content_encoding: params[:encoding] file_content_encoding: params[:encoding],
last_commit_sha: params[:last_commit_sha]
} }
end end
...@@ -161,4 +166,9 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -161,4 +166,9 @@ class Projects::BlobController < Projects::ApplicationController
render nothing: true render nothing: true
end end
end end
def set_last_commit_sha
@last_commit_sha = Gitlab::Git::Commit.
last_for_path(@repository, @ref, @path).sha
end
end end
class Projects::BranchesController < Projects::ApplicationController class Projects::BranchesController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
include SortingHelper
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:new, :create, :destroy] before_action :authorize_push_code!, only: [:new, :create, :destroy]
def index def index
@sort = params[:sort].presence || 'name' @sort = params[:sort].presence || sort_value_name
@branches = BranchesFinder.new(@repository, params).execute @branches = BranchesFinder.new(@repository, params).execute
@branches = Kaminari.paginate_array(@branches).page(params[:page]) @branches = Kaminari.paginate_array(@branches).page(params[:page])
......
...@@ -6,7 +6,7 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -6,7 +6,7 @@ class Projects::BuildsController < Projects::ApplicationController
def index def index
@scope = params[:scope] @scope = params[:scope]
@all_builds = project.builds @all_builds = project.builds.relevant
@builds = @all_builds.order('created_at DESC') @builds = @all_builds.order('created_at DESC')
@builds = @builds =
case @scope case @scope
......
...@@ -134,8 +134,8 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -134,8 +134,8 @@ class Projects::CommitController < Projects::ApplicationController
end end
def define_status_vars def define_status_vars
@statuses = CommitStatus.where(pipeline: pipelines) @statuses = CommitStatus.where(pipeline: pipelines).relevant
@builds = Ci::Build.where(pipeline: pipelines) @builds = Ci::Build.where(pipeline: pipelines).relevant
end end
def assign_change_commit_vars(mr_source_branch) def assign_change_commit_vars(mr_source_branch)
......
# This file should be identical in GitLab Community Edition and Enterprise Edition
class Projects::GitHttpClientController < Projects::ApplicationController
include ActionController::HttpAuthentication::Basic
include KerberosSpnegoHelper
attr_reader :user
# Git clients will not know what authenticity token to send along
skip_before_action :verify_authenticity_token
skip_before_action :repository
before_action :authenticate_user
before_action :ensure_project_found!
private
def authenticate_user
if project && project.public? && download_request?
return # Allow access
end
if allow_basic_auth? && basic_auth_provided?
login, password = user_name_and_password(request)
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
if auth_result.type == :ci && download_request?
@ci = true
elsif auth_result.type == :oauth && !download_request?
# Not allowed
else
@user = auth_result.user
end
if ci? || user
return # Allow access
end
elsif allow_kerberos_spnego_auth? && spnego_provided?
@user = find_kerberos_user
if user
send_final_spnego_response
return # Allow access
end
end
send_challenges
render plain: "HTTP Basic: Access denied\n", status: 401
end
def basic_auth_provided?
has_basic_credentials?(request)
end
def send_challenges
challenges = []
challenges << 'Basic realm="GitLab"' if allow_basic_auth?
challenges << spnego_challenge if allow_kerberos_spnego_auth?
headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
end
def ensure_project_found!
render_not_found if project.blank?
end
def project
return @project if defined?(@project)
project_id, _ = project_id_with_suffix
if project_id.blank?
@project = nil
else
@project = Project.find_with_namespace("#{params[:namespace_id]}/#{project_id}")
end
end
# This method returns two values so that we can parse
# params[:project_id] (untrusted input!) in exactly one place.
def project_id_with_suffix
id = params[:project_id] || ''
%w[.wiki.git .git].each do |suffix|
if id.end_with?(suffix)
# Be careful to only remove the suffix from the end of 'id'.
# Accidentally removing it from the middle is how security
# vulnerabilities happen!
return [id.slice(0, id.length - suffix.length), suffix]
end
end
# Something is wrong with params[:project_id]; do not pass it on.
[nil, nil]
end
def repository
_, suffix = project_id_with_suffix
if suffix == '.wiki.git'
project.wiki.repository
else
project.repository
end
end
def render_not_found
render plain: 'Not Found', status: :not_found
end
def ci?
@ci.present?
end
end
# This file should be identical in GitLab Community Edition and Enterprise Edition # This file should be identical in GitLab Community Edition and Enterprise Edition
class Projects::GitHttpController < Projects::ApplicationController class Projects::GitHttpController < Projects::GitHttpClientController
include ActionController::HttpAuthentication::Basic
include KerberosSpnegoHelper
attr_reader :user
# Git clients will not know what authenticity token to send along
skip_before_action :verify_authenticity_token
skip_before_action :repository
before_action :authenticate_user
before_action :ensure_project_found!
# GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
def info_refs def info_refs
...@@ -46,81 +35,8 @@ class Projects::GitHttpController < Projects::ApplicationController ...@@ -46,81 +35,8 @@ class Projects::GitHttpController < Projects::ApplicationController
private private
def authenticate_user def download_request?
if project && project.public? && upload_pack? upload_pack?
return # Allow access
end
if allow_basic_auth? && basic_auth_provided?
login, password = user_name_and_password(request)
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
if auth_result.type == :ci && upload_pack?
@ci = true
elsif auth_result.type == :oauth && !upload_pack?
# Not allowed
else
@user = auth_result.user
end
if ci? || user
return # Allow access
end
elsif allow_kerberos_spnego_auth? && spnego_provided?
@user = find_kerberos_user
if user
send_final_spnego_response
return # Allow access
end
end
send_challenges
render plain: "HTTP Basic: Access denied\n", status: 401
end
def basic_auth_provided?
has_basic_credentials?(request)
end
def send_challenges
challenges = []
challenges << 'Basic realm="GitLab"' if allow_basic_auth?
challenges << spnego_challenge if allow_kerberos_spnego_auth?
headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
end
def ensure_project_found!
render_not_found if project.blank?
end
def project
return @project if defined?(@project)
project_id, _ = project_id_with_suffix
if project_id.blank?
@project = nil
else
@project = Project.find_with_namespace("#{params[:namespace_id]}/#{project_id}")
end
end
# This method returns two values so that we can parse
# params[:project_id] (untrusted input!) in exactly one place.
def project_id_with_suffix
id = params[:project_id] || ''
%w[.wiki.git .git].each do |suffix|
if id.end_with?(suffix)
# Be careful to only remove the suffix from the end of 'id'.
# Accidentally removing it from the middle is how security
# vulnerabilities happen!
return [id.slice(0, id.length - suffix.length), suffix]
end
end
# Something is wrong with params[:project_id]; do not pass it on.
[nil, nil]
end end
def upload_pack? def upload_pack?
...@@ -143,19 +59,6 @@ class Projects::GitHttpController < Projects::ApplicationController ...@@ -143,19 +59,6 @@ class Projects::GitHttpController < Projects::ApplicationController
render json: Gitlab::Workhorse.git_http_ok(repository, user) render json: Gitlab::Workhorse.git_http_ok(repository, user)
end end
def repository
_, suffix = project_id_with_suffix
if suffix == '.wiki.git'
project.wiki.repository
else
project.repository
end
end
def render_not_found
render plain: 'Not Found', status: :not_found
end
def render_http_not_allowed def render_http_not_allowed
render plain: access_check.message, status: :forbidden render plain: access_check.message, status: :forbidden
end end
...@@ -169,10 +72,6 @@ class Projects::GitHttpController < Projects::ApplicationController ...@@ -169,10 +72,6 @@ class Projects::GitHttpController < Projects::ApplicationController
end end
end end
def ci?
@ci.present?
end
def upload_pack_allowed? def upload_pack_allowed?
return false unless Gitlab.config.gitlab_shell.upload_pack return false unless Gitlab.config.gitlab_shell.upload_pack
......
...@@ -56,6 +56,7 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -56,6 +56,7 @@ class Projects::HooksController < Projects::ApplicationController
def hook_params def hook_params
params.require(:hook).permit( params.require(:hook).permit(
:build_events, :build_events,
:pipeline_events,
:enable_ssl_verification, :enable_ssl_verification,
:issues_events, :issues_events,
:merge_requests_events, :merge_requests_events,
......
...@@ -4,6 +4,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -4,6 +4,7 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuableActions include IssuableActions
include ToggleAwardEmoji include ToggleAwardEmoji
include IssuableCollections include IssuableCollections
include SpammableActions
before_action :redirect_to_external_issue_tracker, only: [:index, :new] before_action :redirect_to_external_issue_tracker, only: [:index, :new]
before_action :module_enabled before_action :module_enabled
...@@ -185,6 +186,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -185,6 +186,7 @@ class Projects::IssuesController < Projects::ApplicationController
alias_method :subscribable_resource, :issue alias_method :subscribable_resource, :issue
alias_method :issuable, :issue alias_method :issuable, :issue
alias_method :awardable, :issue alias_method :awardable, :issue
alias_method :spammable, :issue
def authorize_read_issue! def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue) return render_404 unless can?(current_user, :read_issue, @issue)
......
class Projects::LfsApiController < Projects::GitHttpClientController
include LfsHelper
before_action :require_lfs_enabled!
before_action :lfs_check_access!, except: [:deprecated]
def batch
unless objects.present?
render_lfs_not_found
return
end
if download_request?
render json: { objects: download_objects! }
elsif upload_request?
render json: { objects: upload_objects! }
else
raise "Never reached"
end
end
def deprecated
render(
json: {
message: 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.',
documentation_url: "#{Gitlab.config.gitlab.url}/help",
},
status: 501
)
end
private
def objects
@objects ||= (params[:objects] || []).to_a
end
def existing_oids
@existing_oids ||= begin
storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
end
end
def download_objects!
objects.each do |object|
if existing_oids.include?(object[:oid])
object[:actions] = download_actions(object)
else
object[:error] = {
code: 404,
message: "Object does not exist on the server or you don't have permissions to access it",
}
end
end
objects
end
def upload_objects!
objects.each do |object|
object[:actions] = upload_actions(object) unless existing_oids.include?(object[:oid])
end
objects
end
def download_actions(object)
{
download: {
href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}",
header: {
Authorization: request.headers['Authorization']
}.compact
}
}
end
def upload_actions(object)
{
upload: {
href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}/#{object[:size]}",
header: {
Authorization: request.headers['Authorization']
}.compact
}
}
end
def download_request?
params[:operation] == 'download'
end
def upload_request?
params[:operation] == 'upload'
end
end
class Projects::LfsStorageController < Projects::GitHttpClientController
include LfsHelper
before_action :require_lfs_enabled!
before_action :lfs_check_access!
def download
lfs_object = LfsObject.find_by_oid(oid)
unless lfs_object && lfs_object.file.exists?
render_lfs_not_found
return
end
send_file lfs_object.file.path, content_type: "application/octet-stream"
end
def upload_authorize
render(
json: {
StoreLFSPath: "#{Gitlab.config.lfs.storage_path}/tmp/upload",
LfsOid: oid,
LfsSize: size,
},
content_type: 'application/json; charset=utf-8'
)
end
def upload_finalize
unless tmp_filename
render_lfs_forbidden
return
end
if store_file(oid, size, tmp_filename)
head 200
else
render plain: 'Unprocessable entity', status: 422
end
end
private
def download_request?
action_name == 'download'
end
def upload_request?
%w[upload_authorize upload_finalize].include? action_name
end
def oid
params[:oid].to_s
end
def size
params[:size].to_i
end
def tmp_filename
name = request.headers['X-Gitlab-Lfs-Tmp']
return if name.include?('/')
return unless oid.present? && name.start_with?(oid)
name
end
def store_file(oid, size, tmp_file)
# Define tmp_file_path early because we use it in "ensure"
tmp_file_path = File.join("#{Gitlab.config.lfs.storage_path}/tmp/upload", tmp_file)
object = LfsObject.find_or_create_by(oid: oid, size: size)
file_exists = object.file.exists? || move_tmp_file_to_storage(object, tmp_file_path)
file_exists && link_to_project(object)
ensure
FileUtils.rm_f(tmp_file_path)
end
def move_tmp_file_to_storage(object, path)
File.open(path) do |f|
object.file = f
end
object.file.store!
object.save
end
def link_to_project(object)
if object && !object.projects.exists?(storage_project.id)
object.projects << storage_project
object.save
end
end
end
...@@ -160,7 +160,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -160,7 +160,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@diff_notes_disabled = true @diff_notes_disabled = true
@pipeline = @merge_request.pipeline @pipeline = @merge_request.pipeline
@statuses = @pipeline.statuses if @pipeline @statuses = @pipeline.statuses.relevant if @pipeline
@note_counts = Note.where(commit_id: @commits.map(&:id)). @note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count group(:commit_id).count
...@@ -362,7 +362,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -362,7 +362,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits_count = @merge_request.commits.count @commits_count = @merge_request.commits.count
@pipeline = @merge_request.pipeline @pipeline = @merge_request.pipeline
@statuses = @pipeline.statuses if @pipeline @statuses = @pipeline.statuses.relevant if @pipeline
if @merge_request.locked_long_ago? if @merge_request.locked_long_ago?
@merge_request.unlock_mr @merge_request.unlock_mr
......
...@@ -19,7 +19,7 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -19,7 +19,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end end
def create def create
@pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute @pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute(ignore_skip_ci: true, save_on_errors: false)
unless @pipeline.persisted? unless @pipeline.persisted?
render 'new' render 'new'
return return
......
...@@ -3,7 +3,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -3,7 +3,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def show def show
@ref = params[:ref] || @project.default_branch || 'master' @ref = params[:ref] || @project.default_branch || 'master'
@build_badge = Gitlab::Badge::Build.new(@project, @ref)
@badges = [Gitlab::Badge::Build::Status,
Gitlab::Badge::Coverage::Report]
@badges.map! do |badge|
badge.new(@project, @ref).metadata
end
end end
def update def update
......
...@@ -9,16 +9,16 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ...@@ -9,16 +9,16 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
def index def index
@protected_branch = @project.protected_branches.new @protected_branch = @project.protected_branches.new
load_protected_branches_gon_variables load_gon_index
end end
def create def create
@protected_branch = ProtectedBranches::CreateService.new(@project, current_user, protected_branch_params).execute @protected_branch = ::ProtectedBranches::CreateService.new(@project, current_user, protected_branch_params).execute
if @protected_branch.persisted? if @protected_branch.persisted?
redirect_to namespace_project_protected_branches_path(@project.namespace, @project) redirect_to namespace_project_protected_branches_path(@project.namespace, @project)
else else
load_protected_branches load_protected_branches
load_protected_branches_gon_variables load_gon_index
render :index render :index
end end
end end
...@@ -28,7 +28,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ...@@ -28,7 +28,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
end end
def update def update
@protected_branch = ProtectedBranches::UpdateService.new(@project, current_user, protected_branch_params).execute(@protected_branch) @protected_branch = ::ProtectedBranches::UpdateService.new(@project, current_user, protected_branch_params).execute(@protected_branch)
if @protected_branch.valid? if @protected_branch.valid?
respond_to do |format| respond_to do |format|
...@@ -58,17 +58,23 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ...@@ -58,17 +58,23 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
def protected_branch_params def protected_branch_params
params.require(:protected_branch).permit(:name, params.require(:protected_branch).permit(:name,
merge_access_level_attributes: [:access_level], merge_access_levels_attributes: [:access_level, :id],
push_access_level_attributes: [:access_level]) push_access_levels_attributes: [:access_level, :id])
end end
def load_protected_branches def load_protected_branches
@protected_branches = @project.protected_branches.order(:name).page(params[:page]) @protected_branches = @project.protected_branches.order(:name).page(params[:page])
end end
def load_protected_branches_gon_variables def access_levels_options
gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } }, {
push_access_levels: ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } }, push_access_levels: ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } },
merge_access_levels: ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } } }) merge_access_levels: ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } }
}
end
def load_gon_index
params = { open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } } }
gon.push(params.merge(access_levels_options))
end end
end end
class Projects::TemplatesController < Projects::ApplicationController
before_action :authenticate_user!, :get_template_class
def show
template = @template_type.find(params[:key], project)
respond_to do |format|
format.json { render json: template.to_json }
end
end
private
def get_template_class
template_types = { issue: Gitlab::Template::IssueTemplate, merge_request: Gitlab::Template::MergeRequestTemplate }.with_indifferent_access
@template_type = template_types[params[:template_type]]
render json: [], status: 404 unless @template_type
end
end
...@@ -91,7 +91,7 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -91,7 +91,7 @@ class Projects::WikisController < Projects::ApplicationController
) )
end end
def markdown_preview def preview_markdown
text = params[:text] text = params[:text]
ext = Gitlab::ReferenceExtractor.new(@project, current_user) ext = Gitlab::ReferenceExtractor.new(@project, current_user)
......
...@@ -125,7 +125,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -125,7 +125,7 @@ class ProjectsController < Projects::ApplicationController
def destroy def destroy
return access_denied! unless can?(current_user, :remove_project, @project) return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).pending_delete! ::Projects::DestroyService.new(@project, current_user, {}).async_execute
flash[:alert] = "Project '#{@project.name}' will be deleted." flash[:alert] = "Project '#{@project.name}' will be deleted."
redirect_to dashboard_projects_path redirect_to dashboard_projects_path
...@@ -238,7 +238,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -238,7 +238,7 @@ class ProjectsController < Projects::ApplicationController
} }
end end
def markdown_preview def preview_markdown
text = params[:text] text = params[:text]
ext = Gitlab::ReferenceExtractor.new(@project, current_user) ext = Gitlab::ReferenceExtractor.new(@project, current_user)
......
class MoveToProjectFinder
def initialize(user)
@user = user
end
def execute(from_project, search: nil, offset_id: nil)
projects = @user.projects_where_can_admin_issues
projects = projects.search(search) if search.present?
projects = projects.excluding_project(from_project)
# to ask for Project#name_with_namespace
projects.includes(namespace: :owner)
end
end
class ProjectsFinder < UnionFinder class ProjectsFinder < UnionFinder
def execute(current_user = nil, options = {}) def execute(current_user = nil, project_ids_relation = nil)
segments = all_projects(current_user) segments = all_projects(current_user)
segments.map! { |s| s.where(id: project_ids_relation) } if project_ids_relation
find_union(segments, Project) find_union(segments, Project)
end end
......
...@@ -27,9 +27,11 @@ class TodosFinder ...@@ -27,9 +27,11 @@ class TodosFinder
items = by_action_id(items) items = by_action_id(items)
items = by_action(items) items = by_action(items)
items = by_author(items) items = by_author(items)
items = by_project(items)
items = by_state(items) items = by_state(items)
items = by_type(items) items = by_type(items)
# Filtering by project HAS TO be the last because we use
# the project IDs yielded by the todos query thus far
items = by_project(items)
items.reorder(id: :desc) items.reorder(id: :desc)
end end
...@@ -91,14 +93,9 @@ class TodosFinder ...@@ -91,14 +93,9 @@ class TodosFinder
@project @project
end end
def projects def projects(items)
return @projects if defined?(@projects) item_project_ids = items.reorder(nil).select(:project_id)
ProjectsFinder.new.execute(current_user, item_project_ids)
if project?
@projects = project
else
@projects = ProjectsFinder.new.execute(current_user)
end
end end
def type? def type?
...@@ -136,8 +133,9 @@ class TodosFinder ...@@ -136,8 +133,9 @@ class TodosFinder
def by_project(items) def by_project(items)
if project? if project?
items = items.where(project: project) items = items.where(project: project)
elsif projects else
items = items.merge(projects).joins(:project) item_projects = projects(items)
items = items.merge(item_projects).joins(:project)
end end
items items
......
...@@ -7,8 +7,6 @@ module AvatarsHelper ...@@ -7,8 +7,6 @@ module AvatarsHelper
})) }))
end end
private
def user_avatar(options = {}) def user_avatar(options = {})
avatar_size = options[:size] || 16 avatar_size = options[:size] || 16
user_name = options[:user].try(:name) || options[:user_name] user_name = options[:user].try(:name) || options[:user_name]
......
...@@ -182,17 +182,42 @@ module BlobHelper ...@@ -182,17 +182,42 @@ module BlobHelper
} }
end end
def selected_template(issuable)
templates = issuable_templates(issuable)
params[:issuable_template] if templates.include?(params[:issuable_template])
end
def can_add_template?(issuable)
names = issuable_templates(issuable)
names.empty? && can?(current_user, :push_code, @project) && !@project.private?
end
def merge_request_template_names
@merge_request_templates ||= Gitlab::Template::MergeRequestTemplate.dropdown_names(ref_project)
end
def issue_template_names
@issue_templates ||= Gitlab::Template::IssueTemplate.dropdown_names(ref_project)
end
def issuable_templates(issuable)
@issuable_templates ||=
if issuable.is_a?(Issue)
issue_template_names
elsif issuable.is_a?(MergeRequest)
merge_request_template_names
end
end
def ref_project
@ref_project ||= @target_project || @project
end
def gitignore_names def gitignore_names
@gitignore_names ||= @gitignore_names ||= Gitlab::Template::GitignoreTemplate.dropdown_names
Gitlab::Template::Gitignore.categories.keys.map do |k|
[k, Gitlab::Template::Gitignore.by_category(k).map { |t| { name: t.name } }]
end.to_h
end end
def gitlab_ci_ymls def gitlab_ci_ymls
@gitlab_ci_ymls ||= @gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names
Gitlab::Template::GitlabCiYml.categories.keys.map do |k|
[k, Gitlab::Template::GitlabCiYml.by_category(k).map { |t| { name: t.name } }]
end.to_h
end end
end end
...@@ -109,11 +109,10 @@ module DiffHelper ...@@ -109,11 +109,10 @@ module DiffHelper
end end
end end
def diff_file_html_data(project, diff_file) def diff_file_html_data(project, diff_file_path, diff_commit_id)
commit = commit_for_diff(diff_file)
{ {
blob_diff_path: namespace_project_blob_diff_path(project.namespace, project, blob_diff_path: namespace_project_blob_diff_path(project.namespace, project,
tree_join(commit.id, diff_file.file_path)), tree_join(diff_commit_id, diff_file_path)),
view: diff_view view: diff_view
} }
end end
......
module LfsHelper
def require_lfs_enabled!
return if Gitlab.config.lfs.enabled
render(
json: {
message: 'Git LFS is not enabled on this GitLab server, contact your admin.',
documentation_url: "#{Gitlab.config.gitlab.url}/help",
},
status: 501
)
end
def lfs_check_access!
return if download_request? && lfs_download_access?
return if upload_request? && lfs_upload_access?
if project.public? || (user && user.can?(:read_project, project))
render_lfs_forbidden
else
render_lfs_not_found
end
end
def lfs_download_access?
project.public? || ci? || (user && user.can?(:download_code, project))
end
def lfs_upload_access?
user && user.can?(:push_code, project)
end
def render_lfs_forbidden
render(
json: {
message: 'Access forbidden. Check your access level.',
documentation_url: "#{Gitlab.config.gitlab.url}/help",
},
content_type: "application/vnd.git-lfs+json",
status: 403
)
end
def render_lfs_not_found
render(
json: {
message: 'Not found.',
documentation_url: "#{Gitlab.config.gitlab.url}/help",
},
content_type: "application/vnd.git-lfs+json",
status: 404
)
end
def storage_project
@storage_project ||= begin
result = project
loop do
break unless result.forked?
result = result.forked_from_project
end
result
end
end
end
...@@ -6,12 +6,6 @@ module MembersHelper ...@@ -6,12 +6,6 @@ module MembersHelper
"#{action}_#{member.type.underscore}".to_sym "#{action}_#{member.type.underscore}".to_sym
end end
def default_show_roles(member)
can?(current_user, action_member_permission(:update, member), member) ||
can?(current_user, action_member_permission(:destroy, member), member) ||
can?(current_user, action_member_permission(:admin, member), member.source)
end
def remove_member_message(member, user: nil) def remove_member_message(member, user: nil)
user = current_user if defined?(current_user) user = current_user if defined?(current_user)
......
...@@ -20,13 +20,19 @@ module SortingHelper ...@@ -20,13 +20,19 @@ module SortingHelper
end end
def projects_sort_options_hash def projects_sort_options_hash
{ options = {
sort_value_name => sort_title_name, sort_value_name => sort_title_name,
sort_value_recently_updated => sort_title_recently_updated, sort_value_recently_updated => sort_title_recently_updated,
sort_value_oldest_updated => sort_title_oldest_updated, sort_value_oldest_updated => sort_title_oldest_updated,
sort_value_recently_created => sort_title_recently_created, sort_value_recently_created => sort_title_recently_created,
sort_value_oldest_created => sort_title_oldest_created, sort_value_oldest_created => sort_title_oldest_created,
} }
if current_controller?('admin/projects')
options.merge!(sort_value_largest_repo => sort_title_largest_repo)
end
options
end end
def sort_title_priority def sort_title_priority
......
module TodosHelper module TodosHelper
def todos_pending_count def todos_pending_count
@todos_pending_count ||= TodosFinder.new(current_user, state: :pending).execute.count @todos_pending_count ||= current_user.todos_pending_count
end end
def todos_done_count def todos_done_count
@todos_done_count ||= TodosFinder.new(current_user, state: :done).execute.count @todos_done_count ||= current_user.todos_done_count
end end
def todo_action_name(todo) def todo_action_name(todo)
......
...@@ -4,23 +4,11 @@ module TreeHelper ...@@ -4,23 +4,11 @@ module TreeHelper
# #
# contents - A Grit::Tree object for the current tree # contents - A Grit::Tree object for the current tree
def render_tree(tree) def render_tree(tree)
# Render Folders before Files/Submodules # Sort submodules and folders together by name ahead of files
folders, files, submodules = tree.trees, tree.blobs, tree.submodules folders, files, submodules = tree.trees, tree.blobs, tree.submodules
tree = "" tree = ""
items = (folders + submodules).sort_by(&:name) + files
# Render folders if we have any tree << render(partial: "projects/tree/tree_row", collection: items) if items.present?
tree << render(partial: 'projects/tree/tree_item', collection: folders,
locals: { type: 'folder' }) if folders.present?
# Render files if we have any
tree << render(partial: 'projects/tree/blob_item', collection: files,
locals: { type: 'file' }) if files.present?
# Render submodules if we have any
tree << render(partial: 'projects/tree/submodule_item',
collection: submodules) if submodules.present?
tree.html_safe tree.html_safe
end end
......
...@@ -3,6 +3,9 @@ class Blob < SimpleDelegator ...@@ -3,6 +3,9 @@ class Blob < SimpleDelegator
CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute
CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour
# The maximum size of an SVG that can be displayed.
MAXIMUM_SVG_SIZE = 2.megabytes
# Wrap a Gitlab::Git::Blob object, or return nil when given nil # Wrap a Gitlab::Git::Blob object, or return nil when given nil
# #
# This method prevents the decorated object from evaluating to "truthy" when # This method prevents the decorated object from evaluating to "truthy" when
...@@ -31,6 +34,10 @@ class Blob < SimpleDelegator ...@@ -31,6 +34,10 @@ class Blob < SimpleDelegator
text? && language && language.name == 'SVG' text? && language && language.name == 'SVG'
end end
def size_within_svg_limits?
size <= MAXIMUM_SVG_SIZE
end
def video? def video?
UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.')) UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.'))
end end
......
...@@ -16,7 +16,7 @@ module Ci ...@@ -16,7 +16,7 @@ module Ci
scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
scope :manual_actions, ->() { where(when: :manual) } scope :manual_actions, ->() { where(when: :manual).relevant }
mount_uploader :artifacts_file, ArtifactUploader mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader
...@@ -42,40 +42,35 @@ module Ci ...@@ -42,40 +42,35 @@ module Ci
end end
def retry(build, user = nil) def retry(build, user = nil)
new_build = Ci::Build.new(status: 'pending') new_build = Ci::Build.create(
new_build.ref = build.ref ref: build.ref,
new_build.tag = build.tag tag: build.tag,
new_build.options = build.options options: build.options,
new_build.commands = build.commands commands: build.commands,
new_build.tag_list = build.tag_list tag_list: build.tag_list,
new_build.project = build.project project: build.project,
new_build.pipeline = build.pipeline pipeline: build.pipeline,
new_build.name = build.name name: build.name,
new_build.allow_failure = build.allow_failure allow_failure: build.allow_failure,
new_build.stage = build.stage stage: build.stage,
new_build.stage_idx = build.stage_idx stage_idx: build.stage_idx,
new_build.trigger_request = build.trigger_request trigger_request: build.trigger_request,
new_build.yaml_variables = build.yaml_variables yaml_variables: build.yaml_variables,
new_build.when = build.when when: build.when,
new_build.user = user user: user,
new_build.environment = build.environment environment: build.environment,
new_build.save status_event: 'enqueue'
)
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
new_build new_build
end end
end end
state_machine :status, initial: :pending do state_machine :status do
after_transition pending: :running do |build| after_transition pending: :running do |build|
build.execute_hooks build.execute_hooks
end end
# We use around_transition to create builds for next stage as soon as possible, before the `after_*` is executed
around_transition any => [:success, :failed, :canceled] do |build, block|
block.call
build.pipeline.create_next_builds(build) if build.pipeline
end
after_transition any => [:success, :failed, :canceled] do |build| after_transition any => [:success, :failed, :canceled] do |build|
build.update_coverage build.update_coverage
build.execute_hooks build.execute_hooks
...@@ -107,7 +102,7 @@ module Ci ...@@ -107,7 +102,7 @@ module Ci
def play(current_user = nil) def play(current_user = nil)
# Try to queue a current build # Try to queue a current build
if self.queue if self.enqueue
self.update(user: current_user) self.update(user: current_user)
self self
else else
...@@ -349,7 +344,7 @@ module Ci ...@@ -349,7 +344,7 @@ module Ci
def execute_hooks def execute_hooks
return unless project return unless project
build_data = Gitlab::BuildDataBuilder.build(self) build_data = Gitlab::DataBuilder::Build.build(self)
project.execute_hooks(build_data.dup, :build_hooks) project.execute_hooks(build_data.dup, :build_hooks)
project.execute_services(build_data.dup, :build_hooks) project.execute_services(build_data.dup, :build_hooks)
project.running_or_pending_build_count(force: true) project.running_or_pending_build_count(force: true)
......
...@@ -13,13 +13,57 @@ module Ci ...@@ -13,13 +13,57 @@ module Ci
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
validates_presence_of :sha validates_presence_of :sha
validates_presence_of :ref
validates_presence_of :status validates_presence_of :status
validate :valid_commit_sha validate :valid_commit_sha
# Invalidate object and save if when touched
after_touch :update_state
after_save :keep_around_commits after_save :keep_around_commits
delegate :stages, to: :statuses
state_machine :status, initial: :created do
event :enqueue do
transition created: :pending
transition [:success, :failed, :canceled, :skipped] => :running
end
event :run do
transition any => :running
end
event :skip do
transition any => :skipped
end
event :drop do
transition any => :failed
end
event :succeed do
transition any => :success
end
event :cancel do
transition any => :canceled
end
before_transition [:created, :pending] => :running do |pipeline|
pipeline.started_at = Time.now
end
before_transition any => [:success, :failed, :canceled] do |pipeline|
pipeline.finished_at = Time.now
end
before_transition do |pipeline|
pipeline.update_duration
end
after_transition do |pipeline, transition|
pipeline.execute_hooks unless transition.loopback?
end
end
# ref can't be HEAD or SHA, can only be branch/tag name # ref can't be HEAD or SHA, can only be branch/tag name
scope :latest_successful_for, ->(ref = default_branch) do scope :latest_successful_for, ->(ref = default_branch) do
where(ref: ref).success.order(id: :desc).limit(1) where(ref: ref).success.order(id: :desc).limit(1)
...@@ -113,37 +157,6 @@ module Ci ...@@ -113,37 +157,6 @@ module Ci
trigger_requests.any? trigger_requests.any?
end end
def create_builds(user, trigger_request = nil)
##
# We persist pipeline only if there are builds available
#
return unless config_processor
build_builds_for_stages(config_processor.stages, user,
'success', trigger_request) && save
end
def create_next_builds(build)
return unless config_processor
# don't create other builds if this one is retried
latest_builds = builds.latest
return unless latest_builds.exists?(build.id)
# get list of stages after this build
next_stages = config_processor.stages.drop_while { |stage| stage != build.stage }
next_stages.delete(build.stage)
# get status for all prior builds
prior_builds = latest_builds.where.not(stage: next_stages)
prior_status = prior_builds.status
# build builds for next stage that has builds available
# and save pipeline if we have builds
build_builds_for_stages(next_stages, build.user, prior_status,
build.trigger_request) && save
end
def retried def retried
@retried ||= (statuses.order(id: :desc) - statuses.latest) @retried ||= (statuses.order(id: :desc) - statuses.latest)
end end
...@@ -155,6 +168,14 @@ module Ci ...@@ -155,6 +168,14 @@ module Ci
end end
end end
def config_builds_attributes
return [] unless config_processor
config_processor.
builds_for_ref(ref, tag?, trigger_requests.first).
sort_by { |build| build[:stage_idx] }
end
def has_warnings? def has_warnings?
builds.latest.ignored.any? builds.latest.ignored.any?
end end
...@@ -186,10 +207,6 @@ module Ci ...@@ -186,10 +207,6 @@ module Ci
end end
end end
def skip_ci?
git_commit_message =~ /\[(ci skip|skip ci)\]/i if git_commit_message
end
def environments def environments
builds.where.not(environment: nil).success.pluck(:environment).uniq builds.where.not(environment: nil).success.pluck(:environment).uniq
end end
...@@ -211,37 +228,47 @@ module Ci ...@@ -211,37 +228,47 @@ module Ci
Note.for_commit_id(sha) Note.for_commit_id(sha)
end end
def process!
Ci::ProcessPipelineService.new(project, user).execute(self)
end
def build_updated
case latest_builds_status
when 'pending' then enqueue
when 'running' then run
when 'success' then succeed
when 'failed' then drop
when 'canceled' then cancel
when 'skipped' then skip
end
end
def predefined_variables def predefined_variables
[ [
{ key: 'CI_PIPELINE_ID', value: id.to_s, public: true } { key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
] ]
end end
private def update_duration
self.duration = statuses.latest.duration
end
def build_builds_for_stages(stages, user, status, trigger_request) def execute_hooks
## data = pipeline_data
# Note that `Array#any?` implements a short circuit evaluation, so we project.execute_hooks(data, :pipeline_hooks)
# build builds only for the first stage that has builds available. project.execute_services(data, :pipeline_hooks)
#
stages.any? do |stage|
CreateBuildsService.new(self).
execute(stage, user, status, trigger_request).
any?(&:active?)
end end
private
def pipeline_data
Gitlab::DataBuilder::Pipeline.build(self)
end end
def update_state def latest_builds_status
statuses.reload return 'failed' unless yaml_errors.blank?
self.status = if yaml_errors.blank?
statuses.latest.status || 'skipped' statuses.latest.status || 'skipped'
else
'failed'
end
self.started_at = statuses.started_at
self.finished_at = statuses.finished_at
self.duration = statuses.latest.duration
save
end end
def keep_around_commits def keep_around_commits
......
...@@ -5,7 +5,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -5,7 +5,7 @@ class CommitStatus < ActiveRecord::Base
self.table_name = 'ci_builds' self.table_name = 'ci_builds'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id, touch: true belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
belongs_to :user belongs_to :user
delegate :commit, to: :pipeline delegate :commit, to: :pipeline
...@@ -25,28 +25,36 @@ class CommitStatus < ActiveRecord::Base ...@@ -25,28 +25,36 @@ class CommitStatus < ActiveRecord::Base
scope :ordered, -> { order(:name) } scope :ordered, -> { order(:name) }
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
state_machine :status, initial: :pending do state_machine :status do
event :queue do event :enqueue do
transition skipped: :pending transition [:created, :skipped] => :pending
end end
event :run do event :run do
transition pending: :running transition pending: :running
end end
event :skip do
transition [:created, :pending] => :skipped
end
event :drop do event :drop do
transition [:pending, :running] => :failed transition [:created, :pending, :running] => :failed
end end
event :success do event :success do
transition [:pending, :running] => :success transition [:created, :pending, :running] => :success
end end
event :cancel do event :cancel do
transition [:pending, :running] => :canceled transition [:created, :pending, :running] => :canceled
end end
after_transition pending: :running do |commit_status| after_transition created: [:pending, :running] do |commit_status|
commit_status.update_attributes queued_at: Time.now
end
after_transition [:created, :pending] => :running do |commit_status|
commit_status.update_attributes started_at: Time.now commit_status.update_attributes started_at: Time.now
end end
...@@ -54,7 +62,18 @@ class CommitStatus < ActiveRecord::Base ...@@ -54,7 +62,18 @@ class CommitStatus < ActiveRecord::Base
commit_status.update_attributes finished_at: Time.now commit_status.update_attributes finished_at: Time.now
end end
after_transition [:pending, :running] => :success do |commit_status| # We use around_transition to process pipeline on next stages as soon as possible, before the `after_*` is executed
around_transition any => [:success, :failed, :canceled] do |commit_status, block|
block.call
commit_status.pipeline.try(:process!)
end
after_transition do |commit_status, transition|
commit_status.pipeline.try(:build_updated) unless transition.loopback?
end
after_transition [:created, :pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
end end
......
module ProtectedBranchAccess
extend ActiveSupport::Concern
def humanize
self.class.human_access_levels[self.access_level]
end
end
module Spammable module Spammable
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods
def attr_spammable(attr, options = {})
spammable_attrs << [attr.to_s, options]
end
end
included do included do
has_one :user_agent_detail, as: :subject, dependent: :destroy
attr_accessor :spam attr_accessor :spam
after_validation :check_for_spam, on: :create after_validation :check_for_spam, on: :create
cattr_accessor :spammable_attrs, instance_accessor: false do
[]
end
delegate :ip_address, :user_agent, to: :user_agent_detail, allow_nil: true
end
def submittable_as_spam?
if user_agent_detail
user_agent_detail.submittable?
else
false
end
end end
def spam? def spam?
...@@ -13,4 +36,33 @@ module Spammable ...@@ -13,4 +36,33 @@ module Spammable
def check_for_spam def check_for_spam
self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam? self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam?
end end
def spam_title
attr = self.class.spammable_attrs.find do |_, options|
options.fetch(:spam_title, false)
end
public_send(attr.first) if attr && respond_to?(attr.first.to_sym)
end
def spam_description
attr = self.class.spammable_attrs.find do |_, options|
options.fetch(:spam_description, false)
end
public_send(attr.first) if attr && respond_to?(attr.first.to_sym)
end
def spammable_text
result = self.class.spammable_attrs.map do |attr|
public_send(attr.first)
end
result.reject(&:blank?).join("\n")
end
# Override in Spammable if further checks are necessary
def check_for_spam?
true
end
end end
module Statuseable module Statuseable
extend ActiveSupport::Concern extend ActiveSupport::Concern
AVAILABLE_STATUSES = %w(pending running success failed canceled skipped) AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
STARTED_STATUSES = %w[running success failed skipped]
ACTIVE_STATUSES = %w[pending running]
COMPLETED_STATUSES = %w[success failed canceled]
class_methods do class_methods do
def status_sql def status_sql
builds = all.select('count(*)').to_sql scope = all.relevant
success = all.success.select('count(*)').to_sql builds = scope.select('count(*)').to_sql
ignored = all.ignored.select('count(*)').to_sql if all.respond_to?(:ignored) success = scope.success.select('count(*)').to_sql
ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored)
ignored ||= '0' ignored ||= '0'
pending = all.pending.select('count(*)').to_sql pending = scope.pending.select('count(*)').to_sql
running = all.running.select('count(*)').to_sql running = scope.running.select('count(*)').to_sql
canceled = all.canceled.select('count(*)').to_sql canceled = scope.canceled.select('count(*)').to_sql
skipped = all.skipped.select('count(*)').to_sql skipped = scope.skipped.select('count(*)').to_sql
deduce_status = "(CASE deduce_status = "(CASE
WHEN (#{builds})=0 THEN NULL WHEN (#{builds})=0 THEN NULL
...@@ -48,7 +52,8 @@ module Statuseable ...@@ -48,7 +52,8 @@ module Statuseable
included do included do
validates :status, inclusion: { in: AVAILABLE_STATUSES } validates :status, inclusion: { in: AVAILABLE_STATUSES }
state_machine :status, initial: :pending do state_machine :status, initial: :created do
state :created, value: 'created'
state :pending, value: 'pending' state :pending, value: 'pending'
state :running, value: 'running' state :running, value: 'running'
state :failed, value: 'failed' state :failed, value: 'failed'
...@@ -57,6 +62,8 @@ module Statuseable ...@@ -57,6 +62,8 @@ module Statuseable
state :skipped, value: 'skipped' state :skipped, value: 'skipped'
end end
scope :created, -> { where(status: 'created') }
scope :relevant, -> { where.not(status: 'created') }
scope :running, -> { where(status: 'running') } scope :running, -> { where(status: 'running') }
scope :pending, -> { where(status: 'pending') } scope :pending, -> { where(status: 'pending') }
scope :success, -> { where(status: 'success') } scope :success, -> { where(status: 'success') }
...@@ -68,14 +75,14 @@ module Statuseable ...@@ -68,14 +75,14 @@ module Statuseable
end end
def started? def started?
!pending? && !canceled? && started_at STARTED_STATUSES.include?(status) && started_at
end end
def active? def active?
running? || pending? ACTIVE_STATUSES.include?(status)
end end
def complete? def complete?
canceled? || success? || failed? COMPLETED_STATUSES.include?(status)
end end
end end
...@@ -36,4 +36,10 @@ class Deployment < ActiveRecord::Base ...@@ -36,4 +36,10 @@ class Deployment < ActiveRecord::Base
def manual_actions def manual_actions
deployable.try(:other_actions) deployable.try(:other_actions)
end end
def includes_commit?(commit)
return false unless commit
project.repository.is_ancestor?(commit.id, sha)
end
end end
...@@ -25,4 +25,10 @@ class Environment < ActiveRecord::Base ...@@ -25,4 +25,10 @@ class Environment < ActiveRecord::Base
def nullify_external_url def nullify_external_url
self.external_url = nil if self.external_url.blank? self.external_url = nil if self.external_url.blank?
end end
def includes_commit?(commit)
return false unless last_deployment
last_deployment.includes_commit?(commit)
end
end end
...@@ -5,5 +5,6 @@ class ProjectHook < WebHook ...@@ -5,5 +5,6 @@ class ProjectHook < WebHook
scope :note_hooks, -> { where(note_events: true) } scope :note_hooks, -> { where(note_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true) }
scope :build_hooks, -> { where(build_events: true) } scope :build_hooks, -> { where(build_events: true) }
scope :pipeline_hooks, -> { where(pipeline_events: true) }
scope :wiki_page_hooks, -> { where(wiki_page_events: true) } scope :wiki_page_hooks, -> { where(wiki_page_events: true) }
end end
...@@ -8,6 +8,7 @@ class WebHook < ActiveRecord::Base ...@@ -8,6 +8,7 @@ class WebHook < ActiveRecord::Base
default_value_for :merge_requests_events, false default_value_for :merge_requests_events, false
default_value_for :tag_push_events, false default_value_for :tag_push_events, false
default_value_for :build_events, false default_value_for :build_events, false
default_value_for :pipeline_events, false
default_value_for :enable_ssl_verification, true default_value_for :enable_ssl_verification, true
scope :push_hooks, -> { where(push_events: true) } scope :push_hooks, -> { where(push_events: true) }
......
...@@ -36,6 +36,9 @@ class Issue < ActiveRecord::Base ...@@ -36,6 +36,9 @@ class Issue < ActiveRecord::Base
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') } scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') } scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true
state_machine :state, initial: :opened do state_machine :state, initial: :opened do
event :close do event :close do
transition [:reopened, :opened] => :closed transition [:reopened, :opened] => :closed
...@@ -262,4 +265,9 @@ class Issue < ActiveRecord::Base ...@@ -262,4 +265,9 @@ class Issue < ActiveRecord::Base
def overdue? def overdue?
due_date.try(:past?) || false due_date.try(:past?) || false
end end
# Only issues on public projects should be checked for spam
def check_for_spam?
project.public?
end
end end
...@@ -8,6 +8,7 @@ class ProjectMember < Member ...@@ -8,6 +8,7 @@ class ProjectMember < Member
# Make sure project member points only to project as it source # Make sure project member points only to project as it source
default_value_for :source_type, SOURCE_TYPE default_value_for :source_type, SOURCE_TYPE
validates_format_of :source_type, with: /\AProject\z/ validates_format_of :source_type, with: /\AProject\z/
validates :access_level, inclusion: { in: Gitlab::Access.values }
default_scope { where(source_type: SOURCE_TYPE) } default_scope { where(source_type: SOURCE_TYPE) }
scope :in_project, ->(project) { where(source_id: project.id) } scope :in_project, ->(project) { where(source_id: project.id) }
......
...@@ -104,6 +104,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -104,6 +104,7 @@ class MergeRequest < ActiveRecord::Base
scope :from_project, ->(project) { where(source_project_id: project.id) } scope :from_project, ->(project) { where(source_project_id: project.id) }
scope :merged, -> { with_state(:merged) } scope :merged, -> { with_state(:merged) }
scope :closed_and_merged, -> { with_states(:closed, :merged) } scope :closed_and_merged, -> { with_states(:closed, :merged) }
scope :from_source_branches, ->(branches) { where(source_branch: branches) }
scope :join_project, -> { joins(:target_project) } scope :join_project, -> { joins(:target_project) }
scope :references_project, -> { references(:target_project) } scope :references_project, -> { references(:target_project) }
...@@ -590,6 +591,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -590,6 +591,14 @@ class MergeRequest < ActiveRecord::Base
!pipeline || pipeline.success? !pipeline || pipeline.success?
end end
def environments
return unless diff_head_commit
target_project.environments.select do |environment|
environment.includes_commit?(diff_head_commit)
end
end
def state_human_name def state_human_name
if merged? if merged?
"Merged" "Merged"
......
class Namespace < ActiveRecord::Base class Namespace < ActiveRecord::Base
acts_as_paranoid
include Sortable include Sortable
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
......
...@@ -197,6 +197,8 @@ class Project < ActiveRecord::Base ...@@ -197,6 +197,8 @@ class Project < ActiveRecord::Base
scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') } scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') }
scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) } scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) }
scope :excluding_project, ->(project) { where.not(id: project) }
state_machine :import_status, initial: :none do state_machine :import_status, initial: :none do
event :import_start do event :import_start do
transition [:none, :finished] => :started transition [:none, :finished] => :started
...@@ -378,6 +380,12 @@ class Project < ActiveRecord::Base ...@@ -378,6 +380,12 @@ class Project < ActiveRecord::Base
joins(join_body).reorder('join_note_counts.amount DESC') joins(join_body).reorder('join_note_counts.amount DESC')
end end
def cached_count
Rails.cache.fetch('total_project_count', expires_in: 5.minutes) do
Project.count
end
end
end end
def repository_storage_path def repository_storage_path
...@@ -993,6 +1001,10 @@ class Project < ActiveRecord::Base ...@@ -993,6 +1001,10 @@ class Project < ActiveRecord::Base
project_members.find_by(user_id: user) project_members.find_by(user_id: user)
end end
def add_user(user, access_level, current_user = nil)
team.add_user(user, access_level, current_user)
end
def default_branch def default_branch
@default_branch ||= repository.root_ref if repository.exists? @default_branch ||= repository.root_ref if repository.exists?
end end
...@@ -1155,16 +1167,6 @@ class Project < ActiveRecord::Base ...@@ -1155,16 +1167,6 @@ class Project < ActiveRecord::Base
@wiki ||= ProjectWiki.new(self, self.owner) @wiki ||= ProjectWiki.new(self, self.owner)
end end
def schedule_delete!(user_id, params)
# Queue this task for after the commit, so once we mark pending_delete it will run
run_after_commit do
job_id = ProjectDestroyWorker.perform_async(id, user_id, params)
Rails.logger.info("User #{user_id} scheduled destruction of project #{path_with_namespace} with job ID #{job_id}")
end
update_attribute(:pending_delete, true)
end
def running_or_pending_build_count(force: false) def running_or_pending_build_count(force: false)
Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do
builds.running_or_pending.count(:all) builds.running_or_pending.count(:all)
......
...@@ -51,8 +51,7 @@ class BuildsEmailService < Service ...@@ -51,8 +51,7 @@ class BuildsEmailService < Service
end end
def test_data(project = nil, user = nil) def test_data(project = nil, user = nil)
build = project.builds.last Gitlab::DataBuilder::Build.build(project.builds.last)
Gitlab::BuildDataBuilder.build(build)
end end
def fields def fields
......
class CampfireService < Service class CampfireService < Service
include HTTParty
prop_accessor :token, :subdomain, :room prop_accessor :token, :subdomain, :room
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
...@@ -29,18 +31,53 @@ class CampfireService < Service ...@@ -29,18 +31,53 @@ class CampfireService < Service
def execute(data) def execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
room = gate.find_room_by_name(self.room) self.class.base_uri base_uri
return true unless room
message = build_message(data) message = build_message(data)
speak(self.room, message, auth)
room.speak(message)
end end
private private
def gate def base_uri
@gate ||= Tinder::Campfire.new(subdomain, token: token) @base_uri ||= "https://#{subdomain}.campfirenow.com"
end
def auth
# use a dummy password, as explained in the Campfire API doc:
# https://github.com/basecamp/campfire-api#authentication
@auth ||= {
basic_auth: {
username: token,
password: 'X'
}
}
end
# Post a message into a room, returns the message Hash in case of success.
# Returns nil otherwise.
# https://github.com/basecamp/campfire-api/blob/master/sections/messages.md#create-message
def speak(room_name, message, auth)
room = rooms(auth).find { |r| r["name"] == room_name }
return nil unless room
path = "/room/#{room["id"]}/speak.json"
body = {
body: {
message: {
type: 'TextMessage',
body: message
}
}
}
res = self.class.post(path, auth.merge(body))
res.code == 201 ? res : nil
end
# Returns a list of rooms, or [].
# https://github.com/basecamp/campfire-api/blob/master/sections/rooms.md#get-rooms
def rooms(auth)
res = self.class.get("/rooms.json", auth)
res.code == 200 ? res["rooms"] : []
end end
def build_message(push) def build_message(push)
......
class PivotaltrackerService < Service class PivotaltrackerService < Service
include HTTParty include HTTParty
prop_accessor :token API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'
prop_accessor :token, :restrict_to_branch
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
def title def title
...@@ -18,7 +20,17 @@ class PivotaltrackerService < Service ...@@ -18,7 +20,17 @@ class PivotaltrackerService < Service
def fields def fields
[ [
{ type: 'text', name: 'token', placeholder: '' } {
type: 'text',
name: 'token',
placeholder: 'Pivotal Tracker API token.'
},
{
type: 'text',
name: 'restrict_to_branch',
placeholder: 'Comma-separated list of branches which will be ' \
'automatically inspected. Leave blank to include all branches.'
}
] ]
end end
...@@ -28,8 +40,8 @@ class PivotaltrackerService < Service ...@@ -28,8 +40,8 @@ class PivotaltrackerService < Service
def execute(data) def execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
return unless allowed_branch?(data[:ref])
url = 'https://www.pivotaltracker.com/services/v5/source_commits'
data[:commits].each do |commit| data[:commits].each do |commit|
message = { message = {
'source_commit' => { 'source_commit' => {
...@@ -40,7 +52,7 @@ class PivotaltrackerService < Service ...@@ -40,7 +52,7 @@ class PivotaltrackerService < Service
} }
} }
PivotaltrackerService.post( PivotaltrackerService.post(
url, API_ENDPOINT,
body: message.to_json, body: message.to_json,
headers: { headers: {
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
...@@ -49,4 +61,15 @@ class PivotaltrackerService < Service ...@@ -49,4 +61,15 @@ class PivotaltrackerService < Service
) )
end end
end end
private
def allowed_branch?(ref)
return true unless ref.present? && restrict_to_branch.present?
branch = Gitlab::Git.ref_name(ref)
allowed_branches = restrict_to_branch.split(',').map(&:strip)
branch.present? && allowed_branches.include?(branch)
end
end end
...@@ -56,6 +56,10 @@ class ProjectWiki ...@@ -56,6 +56,10 @@ class ProjectWiki
end end
end end
def repository_exists?
!!repository.exists?
end
def empty? def empty?
pages.empty? pages.empty?
end end
......
...@@ -5,11 +5,14 @@ class ProtectedBranch < ActiveRecord::Base ...@@ -5,11 +5,14 @@ class ProtectedBranch < ActiveRecord::Base
validates :name, presence: true validates :name, presence: true
validates :project, presence: true validates :project, presence: true
has_one :merge_access_level, dependent: :destroy has_many :merge_access_levels, dependent: :destroy
has_one :push_access_level, dependent: :destroy has_many :push_access_levels, dependent: :destroy
accepts_nested_attributes_for :push_access_level validates_length_of :merge_access_levels, is: 1, message: "are restricted to a single instance per protected branch."
accepts_nested_attributes_for :merge_access_level validates_length_of :push_access_levels, is: 1, message: "are restricted to a single instance per protected branch."
accepts_nested_attributes_for :push_access_levels
accepts_nested_attributes_for :merge_access_levels
def commit def commit
project.commit(self.name) project.commit(self.name)
......
class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
include ProtectedBranchAccess
belongs_to :protected_branch belongs_to :protected_branch
delegate :project, to: :protected_branch delegate :project, to: :protected_branch
...@@ -17,8 +19,4 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base ...@@ -17,8 +19,4 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
project.team.max_member_access(user.id) >= access_level project.team.max_member_access(user.id) >= access_level
end end
def humanize
self.class.human_access_levels[self.access_level]
end
end end
class ProtectedBranch::PushAccessLevel < ActiveRecord::Base class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
include ProtectedBranchAccess
belongs_to :protected_branch belongs_to :protected_branch
delegate :project, to: :protected_branch delegate :project, to: :protected_branch
...@@ -20,8 +22,4 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base ...@@ -20,8 +22,4 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
project.team.max_member_access(user.id) >= access_level project.team.max_member_access(user.id) >= access_level
end end
def humanize
self.class.human_access_levels[self.access_level]
end
end end
...@@ -36,6 +36,7 @@ class Service < ActiveRecord::Base ...@@ -36,6 +36,7 @@ class Service < ActiveRecord::Base
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
scope :note_hooks, -> { where(note_events: true, active: true) } scope :note_hooks, -> { where(note_events: true, active: true) }
scope :build_hooks, -> { where(build_events: true, active: true) } scope :build_hooks, -> { where(build_events: true, active: true) }
scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) }
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) } scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
scope :external_issue_trackers, -> { issue_trackers.active.without_defaults } scope :external_issue_trackers, -> { issue_trackers.active.without_defaults }
...@@ -79,13 +80,17 @@ class Service < ActiveRecord::Base ...@@ -79,13 +80,17 @@ class Service < ActiveRecord::Base
end end
def test_data(project, user) def test_data(project, user)
Gitlab::PushDataBuilder.build_sample(project, user) Gitlab::DataBuilder::Push.build_sample(project, user)
end end
def event_channel_names def event_channel_names
[] []
end end
def event_names
supported_events.map { |event| "#{event}_events" }
end
def event_field(event) def event_field(event)
nil nil
end end
......
...@@ -7,4 +7,8 @@ class SpamLog < ActiveRecord::Base ...@@ -7,4 +7,8 @@ class SpamLog < ActiveRecord::Base
user.block user.block
user.destroy user.destroy
end end
def text
[title, description].join("\n")
end
end end
class SpamReport < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
end
...@@ -23,13 +23,13 @@ class User < ActiveRecord::Base ...@@ -23,13 +23,13 @@ class User < ActiveRecord::Base
default_value_for :theme_id, gitlab_config.default_theme default_value_for :theme_id, gitlab_config.default_theme
attr_encrypted :otp_secret, attr_encrypted :otp_secret,
key: Gitlab::Application.config.secret_key_base, key: Gitlab::Application.secrets.otp_key_base,
mode: :per_attribute_iv_and_salt, mode: :per_attribute_iv_and_salt,
insecure_mode: true, insecure_mode: true,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
devise :two_factor_authenticatable, devise :two_factor_authenticatable,
otp_secret_encryption_key: Gitlab::Application.config.secret_key_base otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base
devise :two_factor_backupable, otp_number_of_backup_codes: 10 devise :two_factor_backupable, otp_number_of_backup_codes: 10
serialize :otp_backup_codes, JSON serialize :otp_backup_codes, JSON
...@@ -429,6 +429,13 @@ class User < ActiveRecord::Base ...@@ -429,6 +429,13 @@ class User < ActiveRecord::Base
owned_groups.select(:id), namespace.id).joins(:namespace) owned_groups.select(:id), namespace.id).joins(:namespace)
end end
# Returns projects which user can admin issues on (for example to move an issue to that project).
#
# This logic is duplicated from `Ability#project_abilities` into a SQL form.
def projects_where_can_admin_issues
authorized_projects(Gitlab::Access::REPORTER).non_archived.where.not(issues_enabled: false)
end
def is_admin? def is_admin?
admin admin
end end
...@@ -809,13 +816,13 @@ class User < ActiveRecord::Base ...@@ -809,13 +816,13 @@ class User < ActiveRecord::Base
def todos_done_count(force: false) def todos_done_count(force: false)
Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do
todos.done.count TodosFinder.new(self, state: :done).execute.count
end end
end end
def todos_pending_count(force: false) def todos_pending_count(force: false)
Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force) do Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force) do
todos.pending.count TodosFinder.new(self, state: :pending).execute.count
end end
end end
......
class UserAgentDetail < ActiveRecord::Base
belongs_to :subject, polymorphic: true
validates :user_agent, :ip_address, :subject_id, :subject_type, presence: true
def submittable?
!submitted?
end
end
class AkismetService
attr_accessor :owner, :text, :options
def initialize(owner, text, options = {})
@owner = owner
@text = text
@options = options
end
def is_spam?
return false unless akismet_enabled?
params = {
type: 'comment',
text: text,
created_at: DateTime.now,
author: owner.name,
author_email: owner.email,
referrer: options[:referrer],
}
begin
is_spam, is_blatant = akismet_client.check(options[:ip_address], options[:user_agent], params)
is_spam || is_blatant
rescue => e
Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check")
false
end
end
def submit_ham
return false unless akismet_enabled?
params = {
type: 'comment',
text: text,
author: owner.name,
author_email: owner.email
}
begin
akismet_client.submit_ham(options[:ip_address], options[:user_agent], params)
true
rescue => e
Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!")
false
end
end
def submit_spam
return false unless akismet_enabled?
params = {
type: 'comment',
text: text,
author: owner.name,
author_email: owner.email
}
begin
akismet_client.submit_spam(options[:ip_address], options[:user_agent], params)
true
rescue => e
Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!")
false
end
end
private
def akismet_client
@akismet_client ||= ::Akismet::Client.new(current_application_settings.akismet_api_key,
Gitlab.config.gitlab.url)
end
def akismet_enabled?
current_application_settings.akismet_enabled
end
end
module Ci
class CreateBuildsService
def initialize(pipeline)
@pipeline = pipeline
@config = pipeline.config_processor
end
def execute(stage, user, status, trigger_request = nil)
builds_attrs = @config.builds_for_stage_and_ref(stage, @pipeline.ref, @pipeline.tag, trigger_request)
# check when to create next build
builds_attrs = builds_attrs.select do |build_attrs|
case build_attrs[:when]
when 'on_success'
status == 'success'
when 'on_failure'
status == 'failed'
when 'always', 'manual'
%w(success failed).include?(status)
end
end
# don't create the same build twice
builds_attrs.reject! do |build_attrs|
@pipeline.builds.find_by(ref: @pipeline.ref,
tag: @pipeline.tag,
trigger_request: trigger_request,
name: build_attrs[:name])
end
builds_attrs.map do |build_attrs|
build_attrs.slice!(:name,
:commands,
:tag_list,
:options,
:allow_failure,
:stage,
:stage_idx,
:environment,
:when,
:yaml_variables)
build_attrs.merge!(pipeline: @pipeline,
ref: @pipeline.ref,
tag: @pipeline.tag,
trigger_request: trigger_request,
user: user,
project: @pipeline.project)
# TODO: The proper implementation for this is in
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5295
build_attrs[:status] = 'skipped' if build_attrs[:when] == 'manual'
##
# We do not persist new builds here.
# Those will be persisted when @pipeline is saved.
#
@pipeline.builds.new(build_attrs)
end
end
end
end
module Ci
class CreatePipelineBuildsService < BaseService
attr_reader :pipeline
def execute(pipeline)
@pipeline = pipeline
new_builds.map do |build_attributes|
create_build(build_attributes)
end
end
private
def create_build(build_attributes)
build_attributes = build_attributes.merge(
pipeline: pipeline,
project: pipeline.project,
ref: pipeline.ref,
tag: pipeline.tag,
user: current_user,
trigger_request: trigger_request
)
pipeline.builds.create(build_attributes)
end
def new_builds
@new_builds ||= pipeline.config_builds_attributes.
reject { |build| existing_build_names.include?(build[:name]) }
end
def existing_build_names
@existing_build_names ||= pipeline.builds.pluck(:name)
end
def trigger_request
return @trigger_request if defined?(@trigger_request)
@trigger_request ||= pipeline.trigger_requests.first
end
end
end
module Ci module Ci
class CreatePipelineService < BaseService class CreatePipelineService < BaseService
def execute attr_reader :pipeline
pipeline = project.pipelines.new(params)
pipeline.user = current_user
unless ref_names.include?(params[:ref]) def execute(ignore_skip_ci: false, save_on_errors: true, trigger_request: nil)
pipeline.errors.add(:base, 'Reference not found') @pipeline = Ci::Pipeline.new(
return pipeline project: project,
ref: ref,
sha: sha,
before_sha: before_sha,
tag: tag?,
trigger_requests: Array(trigger_request),
user: current_user
)
unless project.builds_enabled?
return error('Pipeline is disabled')
end end
if commit unless trigger_request || can?(current_user, :create_pipeline, project)
pipeline.sha = commit.id return error('Insufficient permissions to create a new pipeline')
else
pipeline.errors.add(:base, 'Commit not found')
return pipeline
end end
unless can?(current_user, :create_pipeline, project) unless branch? || tag?
pipeline.errors.add(:base, 'Insufficient permissions to create a new pipeline') return error('Reference not found')
return pipeline end
unless commit
return error('Commit not found')
end end
unless pipeline.config_processor unless pipeline.config_processor
pipeline.errors.add(:base, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file') unless pipeline.ci_yaml_file
return pipeline return error('Missing .gitlab-ci.yml file')
end
return error(pipeline.yaml_errors, save: save_on_errors)
end end
pipeline.save! if !ignore_skip_ci && skip_ci?
pipeline.skip if save_on_errors
return pipeline
end
unless pipeline.create_builds(current_user) unless pipeline.config_builds_attributes.present?
pipeline.errors.add(:base, 'No builds for this pipeline.') return error('No builds for this pipeline.')
end end
pipeline.save pipeline.save
pipeline.process!
pipeline pipeline
end end
private private
def ref_names def skip_ci?
@ref_names ||= project.repository.ref_names pipeline.git_commit_message =~ /\[(ci skip|skip ci)\]/i if pipeline.git_commit_message
end end
def commit def commit
@commit ||= project.commit(params[:ref]) @commit ||= project.commit(origin_sha || origin_ref)
end
def sha
commit.try(:id)
end
def before_sha
params[:checkout_sha] || params[:before] || Gitlab::Git::BLANK_SHA
end
def origin_sha
params[:checkout_sha] || params[:after]
end
def origin_ref
params[:ref]
end
def branch?
project.repository.ref_exists?(Gitlab::Git::BRANCH_REF_PREFIX + ref)
end
def tag?
project.repository.ref_exists?(Gitlab::Git::TAG_REF_PREFIX + ref)
end
def ref
Gitlab::Git.ref_name(origin_ref)
end
def valid_sha?
origin_sha && origin_sha != Gitlab::Git::BLANK_SHA
end
def error(message, save: false)
pipeline.errors.add(:base, message)
pipeline.drop if save
pipeline
end end
end end
end end
module Ci module Ci
class CreateTriggerRequestService class CreateTriggerRequestService
def execute(project, trigger, ref, variables = nil) def execute(project, trigger, ref, variables = nil)
commit = project.commit(ref) trigger_request = trigger.trigger_requests.create(variables: variables)
return unless commit
# check if ref is tag pipeline = Ci::CreatePipelineService.new(project, nil, ref: ref).
tag = project.repository.find_tag(ref).present? execute(ignore_skip_ci: true, trigger_request: trigger_request)
if pipeline.persisted?
pipeline = project.pipelines.create(sha: commit.sha, ref: ref, tag: tag)
trigger_request = trigger.trigger_requests.create!(
variables: variables,
pipeline: pipeline,
)
if pipeline.create_builds(nil, trigger_request)
trigger_request trigger_request
end end
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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