summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouwe Maan <douwe@selenight.nl>2016-07-28 16:57:42 -0600
committerDouwe Maan <douwe@selenight.nl>2016-07-28 16:57:42 -0600
commitce1843b7dea3b63cfced7800612098a766c68e13 (patch)
tree3340c4b5aca30047c2e22986c15f355bf3f845fe
parent8b1a2e4d0b635c6040cd7e4a7a5870ffae516cb0 (diff)
parentb9b0c0283a001010370741c2683e2fe400bf7599 (diff)
downloadgitlab-ce-ce1843b7dea3b63cfced7800612098a766c68e13.tar.gz
Merge branch 'master' into diff-line-comment-vuejs
# Conflicts: # db/schema.rb
-rw-r--r--.gitlab-ci.yml43
-rw-r--r--.simplecov4
-rw-r--r--CHANGELOG34
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile10
-rw-r--r--Gemfile.lock34
-rw-r--r--app/assets/javascripts/importer_status.js8
-rw-r--r--app/assets/javascripts/users_select.js4
-rw-r--r--app/assets/stylesheets/pages/issues.scss30
-rw-r--r--app/controllers/admin/requests_profiles_controller.rb17
-rw-r--r--app/controllers/autocomplete_controller.rb1
-rw-r--r--app/controllers/explore/application_controller.rb2
-rw-r--r--app/controllers/help_controller.rb2
-rw-r--r--app/controllers/projects/branches_controller.rb1
-rw-r--r--app/controllers/projects/issues_controller.rb7
-rw-r--r--app/controllers/projects/merge_requests_controller.rb5
-rw-r--r--app/controllers/projects/tags_controller.rb3
-rw-r--r--app/controllers/search_controller.rb2
-rw-r--r--app/helpers/notes_helper.rb15
-rw-r--r--app/helpers/selects_helper.rb30
-rw-r--r--app/models/ability.rb12
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/concerns/issuable.rb2
-rw-r--r--app/models/concerns/spammable.rb16
-rw-r--r--app/models/issue.rb1
-rw-r--r--app/models/legacy_diff_note.rb10
-rw-r--r--app/models/member.rb4
-rw-r--r--app/models/project.rb22
-rw-r--r--app/models/project_services/hipchat_service.rb21
-rw-r--r--app/models/project_team.rb72
-rw-r--r--app/models/repository.rb11
-rw-r--r--app/services/issues/create_service.rb14
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/services/spam_check_service.rb38
-rw-r--r--app/views/admin/background_jobs/_head.html.haml4
-rw-r--r--app/views/admin/requests_profiles/index.html.haml26
-rw-r--r--app/views/import/bitbucket/status.html.haml4
-rw-r--r--app/views/import/fogbugz/status.html.haml3
-rw-r--r--app/views/import/github/status.html.haml3
-rw-r--r--app/views/import/gitlab/status.html.haml3
-rw-r--r--app/views/import/gitorious/status.html.haml3
-rw-r--r--app/views/import/google_code/status.html.haml3
-rw-r--r--app/views/layouts/nav/_admin.html.haml2
-rw-r--r--app/views/projects/branches/index.html.haml40
-rw-r--r--app/views/projects/builds/_sidebar.html.haml5
-rw-r--r--app/views/projects/ci/builds/_build.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml2
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/graphs/ci/_build_times.haml7
-rw-r--r--app/views/projects/graphs/ci/_builds.haml7
-rw-r--r--app/views/projects/graphs/commits.html.haml4
-rw-r--r--app/views/projects/issues/_issue_by_email.html.haml27
-rw-r--r--app/views/projects/issues/index.html.haml7
-rw-r--r--app/views/projects/update.js.haml2
-rw-r--r--app/views/shared/icons/_icon_status_cancel.svg14
-rw-r--r--app/views/shared/icons/_icon_status_failed.svg14
-rw-r--r--app/views/shared/icons/_icon_status_pending.svg15
-rw-r--r--app/views/shared/icons/_icon_status_running.svg14
-rw-r--r--app/views/shared/icons/_icon_status_success.svg17
-rw-r--r--app/views/shared/icons/_icon_status_warning.svg17
-rw-r--r--app/workers/email_receiver_worker.rb54
-rw-r--r--app/workers/repository_fork_worker.rb5
-rw-r--r--app/workers/requests_profiles_worker.rb9
-rw-r--r--config/dependency_decisions.yml31
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--config/initializers/6_validations.rb2
-rw-r--r--config/initializers/request_profiler.rb3
-rw-r--r--config/routes.rb23
-rw-r--r--db/fixtures/development/04_project.rb1
-rw-r--r--doc/api/commits.md5
-rw-r--r--doc/integration/akismet.md11
-rw-r--r--doc/update/8.10-to-8.11.md191
-rw-r--r--doc/update/8.9-to-8.10.md2
-rw-r--r--features/support/env.rb5
-rw-r--r--lib/api/entities.rb5
-rw-r--r--lib/api/issues.rb19
-rw-r--r--lib/gitlab/access.rb1
-rw-r--r--lib/gitlab/akismet_helper.rb4
-rw-r--r--lib/gitlab/backend/shell.rb10
-rw-r--r--lib/gitlab/email/handler.rb17
-rw-r--r--lib/gitlab/email/handler/base_handler.rb60
-rw-r--r--lib/gitlab/email/handler/create_issue_handler.rb52
-rw-r--r--lib/gitlab/email/handler/create_note_handler.rb55
-rw-r--r--lib/gitlab/email/receiver.rb117
-rw-r--r--lib/gitlab/incoming_email.rb6
-rw-r--r--lib/gitlab/metrics.rb5
-rw-r--r--lib/gitlab/metrics/instrumentation.rb13
-rw-r--r--lib/gitlab/metrics/method_call.rb4
-rw-r--r--lib/gitlab/metrics/system.rb8
-rw-r--r--lib/gitlab/metrics/transaction.rb27
-rw-r--r--lib/gitlab/request_profiler.rb19
-rw-r--r--lib/gitlab/request_profiler/middleware.rb47
-rw-r--r--lib/gitlab/request_profiler/profile.rb43
-rw-r--r--lib/tasks/test.rake2
-rw-r--r--public/404.html84
-rw-r--r--public/422.html82
-rw-r--r--public/500.html77
-rw-r--r--public/502.html27
-rw-r--r--public/503.html27
-rw-r--r--public/deploy.html98
-rwxr-xr-xscripts/merge-simplecov30
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb13
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb31
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb20
-rw-r--r--spec/factories/ci/trigger_requests.rb3
-rw-r--r--spec/features/issues_spec.rb29
-rw-r--r--spec/features/projects/branches_spec.rb~HEAD32
-rw-r--r--spec/features/projects/project_settings_spec.rb41
-rw-r--r--spec/finders/branches_finder_spec.rb8
-rw-r--r--spec/fixtures/emails/valid_new_issue.eml23
-rw-r--r--spec/fixtures/emails/valid_new_issue_empty.eml18
-rw-r--r--spec/fixtures/emails/wrong_authentication_token.eml18
-rw-r--r--spec/fixtures/emails/wrong_mail_key.eml (renamed from spec/fixtures/emails/wrong_reply_key.eml)0
-rw-r--r--spec/helpers/notes_helper_spec.rb57
-rw-r--r--spec/lib/gitlab/akismet_helper_spec.rb12
-rw-r--r--spec/lib/gitlab/email/email_shared_blocks.rb41
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_handler_spec.rb79
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb116
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb157
-rw-r--r--spec/lib/gitlab/incoming_email_spec.rb4
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb12
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb12
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb16
-rw-r--r--spec/lib/gitlab/metrics_spec.rb6
-rw-r--r--spec/models/ability_spec.rb56
-rw-r--r--spec/models/build_spec.rb2
-rw-r--r--spec/models/member_spec.rb12
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb30
-rw-r--r--spec/models/project_spec.rb72
-rw-r--r--spec/models/project_team_spec.rb61
-rw-r--r--spec/models/repository_spec.rb37
-rw-r--r--spec/requests/api/commits_spec.rb4
-rw-r--r--spec/requests/api/issues_spec.rb6
-rw-r--r--spec/requests/ci/api/builds_spec.rb2
-rw-r--r--spec/routing/admin_routing_spec.rb5
-rw-r--r--spec/routing/project_routing_spec.rb4
-rw-r--r--spec/routing/routing_spec.rb8
-rw-r--r--spec/simplecov_env.rb54
-rw-r--r--spec/spec_helper.rb6
-rw-r--r--spec/views/projects/builds/show.html.haml_spec.rb22
-rw-r--r--spec/workers/email_receiver_worker_spec.rb2
-rw-r--r--spec/workers/repository_fork_worker_spec.rb10
-rw-r--r--vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml2
143 files changed, 2297 insertions, 858 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2d33bad5886..2eda2a6007d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -28,6 +28,7 @@ stages:
- prepare
- test
- post-test
+- pages
# Prepare and merge knapsack tests
.knapsack-state: &knapsack-state
@@ -40,6 +41,7 @@ stages:
paths:
- knapsack/
artifacts:
+ expire_in: 31d
paths:
- knapsack/
@@ -81,8 +83,10 @@ update-knapsack:
- cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH}
- knapsack rspec
artifacts:
+ expire_in: 31d
paths:
- knapsack/
+ - coverage/
.spinach-knapsack: &spinach-knapsack
stage: test
@@ -97,8 +101,10 @@ update-knapsack:
- cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH}
- knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts:
+ expire_in: 31d
paths:
- knapsack/
+ - coverage/
rspec 0 20: *rspec-knapsack
rspec 1 20: *rspec-knapsack
@@ -186,14 +192,14 @@ spinach 9 10 ruby23: *spinach-knapsack-ruby23
# Other generic tests
-.static-analyses-variables: &static-analyses-variables
+.ruby-static-analysis: &ruby-static-analysis
variables:
SIMPLECOV: "false"
USE_DB: "false"
USE_BUNDLE_INSTALL: "true"
.exec: &exec
- <<: *static-analyses-variables
+ <<: *ruby-static-analysis
stage: test
script:
- bundle exec $CI_BUILD_NAME
@@ -220,12 +226,28 @@ teaspoon:
bundler:audit:
stage: test
- <<: *static-analyses-variables
+ <<: *ruby-static-analysis
only:
- master
script:
- "bundle exec bundle-audit check --update --ignore OSVDB-115941"
+coverage:
+ stage: post-test
+ services: []
+ variables:
+ USE_DB: "false"
+ USE_BUNDLE_INSTALL: "true"
+ script:
+ - bundle exec scripts/merge-simplecov
+ artifacts:
+ name: coverage
+ expire_in: 31d
+ paths:
+ - coverage/index.html
+ - coverage/assets/
+
+
# Notify slack in the end
notify:slack:
@@ -238,3 +260,18 @@ notify:slack:
- tags@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- tags@gitlab-org/gitlab-ee
+
+pages:
+ before_script: []
+ stage: pages
+ dependencies:
+ - coverage
+ script:
+ - mv public/ .public/
+ - mkdir public/
+ - mv coverage public/coverage-ruby
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/.simplecov b/.simplecov
deleted file mode 100644
index d979288df44..00000000000
--- a/.simplecov
+++ /dev/null
@@ -1,4 +0,0 @@
-# .simplecov
-SimpleCov.start 'rails' do
- merge_timeout 3600
-end
diff --git a/CHANGELOG b/CHANGELOG
index 181829a86a5..2704f89ccda 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,27 +1,57 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.11.0 (unreleased)
+ - Fix the title of the toggle dropdown button. !5515 (herminiotorres)
- Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
- Fix CI status icon link underline (ClemMakesApps)
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
- Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
+ - Optimize maximum user access level lookup in loading of notes
- Limit git rev-list output count to one in forced push check
+ - Clean up unused routes (Josef Strzibny)
- Add green outline to New Branch button. !5447 (winniehell)
- Retrieve rendered HTML from cache in one request
+ - Fix renaming repository when name contains invalid chararacters under project settings
- Nokogiri's various parsing methods are now instrumented
+ - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363
+ - Add build event color in HipChat messages (David Eisner)
- Make fork counter always clickable. !5463 (winniehell)
+ - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333
+ - The overhead of instrumented method calls has been reduced
- Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le)
- Load project invited groups and members eagerly in `ProjectTeam#fetch_members`
+ - Make branches sortable without push permission !5462 (winniehell)
- Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska)
- Add the `sprockets-es6` gem
+ - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska)
+ - Profile requests when a header is passed
+ - Add commit stats in commit api. !5517 (dixpac)
+ - Make error pages responsive (Takuya Noguchi)
+ - Change requests_profiles resource constraint to catch virtually any file
+ - Reduce number of queries made for merge_requests/:id/diffs
-v 8.10.2 (unreleased)
+v 8.10.3 (unreleased)
+
+v 8.10.2
- User can now search branches by name. !5144
+ - Page is now properly rendered after committing the first file and creating the first branch. !5399
+ - Add branch or tag icon to ref in builds page. !5434
- Fix backup restore. !5459
- - Disable MySQL foreign key checks before dropping all tables. !5472
- Use project ID in repository cache to prevent stale data from persisting across projects. !5460
+ - Fix issue with autocomplete search not working with enter key. !5466
+ - Add iid to MR API response. !5468
+ - Disable MySQL foreign key checks before dropping all tables. !5472
- Ensure relative paths for video are rewritten as we do for images. !5474
- Ensure current user can retry a build before showing the 'Retry' button. !5476
+ - Add ENV variable to skip repository storages validations. !5478
+ - Added `*.js.es6 gitlab-language=javascript` to `.gitattributes`. !5486
+ - Don't show comment button in gutter of diffs on MR discussion tab. !5493
+ - Rescue Rugged::OSError (lock exists) when creating references. !5497
+ - Fix expand all diffs button in compare view. !5500
+ - Show release notes in tags list. !5503
+ - Fix a bug where forking a project from a repository storage to another would fail. !5509
+ - Fix missing schema update for `20160722221922`. !5512
+ - Update `gitlab-shell` version to 3.2.1 in the 8.9->8.10 update guide. !5516
v 8.10.1
- Refactor repository storages documentation. !5428
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 944880fa15e..e4604e3afd0 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-3.2.0
+3.2.1
diff --git a/Gemfile b/Gemfile
index f2ac74a5976..071277de068 100644
--- a/Gemfile
+++ b/Gemfile
@@ -225,7 +225,7 @@ gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.6.1'
gem 'gemojione', '~> 3.0'
-gem 'gon', '~> 6.0.1'
+gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-ui-rails', '~> 5.0.0'
@@ -253,7 +253,7 @@ group :development do
gem 'letter_opener_web', '~> 1.3.0'
gem 'rerun', '~> 0.11.0'
- gem 'bullet', '~> 5.0.0', require: false
+ gem 'bullet', '~> 5.2.0', require: false
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
gem 'web-console', '~> 2.0'
@@ -275,7 +275,7 @@ group :development, :test do
gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0'
- gem 'database_cleaner', '~> 1.4.0'
+ gem 'database_cleaner', '~> 1.5.0'
gem 'factory_girl_rails', '~> 4.6.0'
gem 'rspec-rails', '~> 3.5.0'
gem 'rspec-retry', '~> 0.4.5'
@@ -303,7 +303,7 @@ group :development, :test do
gem 'rubocop', '~> 0.41.2', require: false
gem 'rubocop-rspec', '~> 1.5.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
- gem 'simplecov', '~> 0.11.0', require: false
+ gem 'simplecov', '0.12.0', require: false
gem 'flog', '~> 4.3.2', require: false
gem 'flay', '~> 2.6.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
@@ -334,6 +334,8 @@ gem 'mail_room', '~> 0.8'
gem 'email_reply_parser', '~> 0.5.8'
+gem 'ruby-prof', '~> 0.15.9'
+
## CI
gem 'activerecord-session_store', '~> 1.0.0'
gem 'nested_form', '~> 0.3.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index bfa7e38da85..670578dec6d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -59,7 +59,7 @@ GEM
oauth2 (~> 1.0)
asciidoctor (1.5.3)
ast (2.3.0)
- attr_encrypted (3.0.1)
+ attr_encrypted (3.0.3)
encryptor (~> 3.0.0)
attr_required (1.0.0)
autoprefixer-rails (6.2.3)
@@ -104,9 +104,9 @@ GEM
brakeman (3.3.2)
browser (2.2.0)
builder (3.2.2)
- bullet (5.0.0)
+ bullet (5.2.0)
activesupport (>= 3.0.0)
- uniform_notifier (~> 1.9.0)
+ uniform_notifier (~> 1.10.0)
bundler-audit (0.5.0)
bundler (~> 1.2)
thor (~> 0.18)
@@ -153,11 +153,11 @@ GEM
d3_rails (3.5.11)
railties (>= 3.1.0)
daemons (1.2.3)
- database_cleaner (1.4.1)
+ database_cleaner (1.5.3)
debug_inspector (0.0.2)
debugger-ruby_core_source (1.3.8)
- default_value_for (3.0.1)
- activerecord (>= 3.2.0, < 5.0)
+ default_value_for (3.0.2)
+ activerecord (>= 3.2.0, < 5.1)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
devise (4.1.1)
@@ -303,7 +303,7 @@ GEM
gollum-rugged_adapter (0.4.2)
mime-types (>= 1.15)
rugged (~> 0.24.0, >= 0.21.3)
- gon (6.0.1)
+ gon (6.1.0)
actionpack (>= 3.0)
json
multi_json
@@ -509,7 +509,7 @@ GEM
rack-cors (0.4.0)
rack-mount (0.8.3)
rack (>= 1.0.0)
- rack-oauth2 (1.2.1)
+ rack-oauth2 (1.2.3)
activesupport (>= 2.3)
attr_required (>= 0.0.5)
httpclient (>= 2.4)
@@ -575,7 +575,7 @@ GEM
redis-store (~> 1.1.0)
redis-store (1.1.7)
redis (>= 2.2)
- request_store (1.3.0)
+ request_store (1.3.1)
rerun (0.11.0)
listen (~> 3.0)
responders (2.1.1)
@@ -620,6 +620,7 @@ GEM
rubocop (>= 0.40.0)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
+ ruby-prof (0.15.9)
ruby-progressbar (1.8.1)
ruby-saml (1.3.0)
nokogiri (>= 1.5.10)
@@ -672,9 +673,9 @@ GEM
rufus-scheduler (>= 2.0.24)
sidekiq (>= 4.0.0)
simple_oauth (0.1.9)
- simplecov (0.11.2)
+ simplecov (0.12.0)
docile (~> 1.1.0)
- json (~> 1.8)
+ json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
sinatra (1.4.7)
@@ -774,7 +775,7 @@ GEM
unicorn-worker-killer (0.4.4)
get_process_mem (~> 0)
unicorn (>= 4, < 6)
- uniform_notifier (1.9.0)
+ uniform_notifier (1.10.0)
uuid (2.3.8)
macaddr (~> 1.0)
version_sorter (2.0.0)
@@ -829,7 +830,7 @@ DEPENDENCIES
bootstrap-sass (~> 3.3.0)
brakeman (~> 3.3.0)
browser (~> 2.2)
- bullet (~> 5.0.0)
+ bullet (~> 5.2.0)
bundler-audit (~> 0.5.0)
byebug (~> 8.2.1)
capybara (~> 2.6.2)
@@ -841,7 +842,7 @@ DEPENDENCIES
connection_pool (~> 2.0)
creole (~> 0.5.0)
d3_rails (~> 3.5.0)
- database_cleaner (~> 1.4.0)
+ database_cleaner (~> 1.5.0)
default_value_for (~> 3.0.0)
devise (~> 4.0)
devise-two-factor (~> 3.0.0)
@@ -874,7 +875,7 @@ DEPENDENCIES
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.2)
- gon (~> 6.0.1)
+ gon (~> 6.1.0)
grape (~> 0.13.0)
grape-entity (~> 0.4.2)
hamlit (~> 2.5)
@@ -948,6 +949,7 @@ DEPENDENCIES
rubocop (~> 0.41.2)
rubocop-rspec (~> 1.5.0)
ruby-fogbugz (~> 0.2.1)
+ ruby-prof (~> 0.15.9)
sanitize (~> 2.0)
sass-rails (~> 5.0.0)
scss_lint (~> 0.47.0)
@@ -960,7 +962,7 @@ DEPENDENCIES
shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.0)
sidekiq-cron (~> 0.4.0)
- simplecov (~> 0.11.0)
+ simplecov (= 0.12.0)
sinatra (~> 1.4.4)
six (~> 0.2.0)
slack-notifier (~> 1.2.0)
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index 55b6f132bab..0f840821f53 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -66,4 +66,12 @@
})();
+ $(function() {
+ if ($('.js-importer-status').length) {
+ var jobsImportPath = $('.js-importer-status').data('jobs-import-path');
+ var importPath = $('.js-importer-status').data('import-path');
+
+ new ImporterStatus(jobsImportPath, importPath);
+ }
+ });
}).call(this);
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 64a29d36cdf..4af2a214e12 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -189,6 +189,7 @@
_this.groupId = $(select).data('group-id');
_this.showCurrentUser = $(select).data('current-user');
_this.authorId = $(select).data('author-id');
+ _this.skipUsers = $(select).data('skip-users');
showNullUser = $(select).data('null-user');
showAnyUser = $(select).data('any-user');
showEmailUser = $(select).data('email-user');
@@ -320,7 +321,8 @@
project_id: this.projectId,
group_id: this.groupId,
current_user: this.showCurrentUser,
- author_id: this.authorId
+ author_id: this.authorId,
+ skip_users: this.skipUsers
},
dataType: "json"
}).done(function(users) {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index ee3b2d2b801..dfe1e3075da 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -99,3 +99,33 @@ form.edit-issue {
.issue-form .select2-container {
width: 250px !important;
}
+
+.issues-footer {
+ padding-top: $gl-padding;
+ padding-bottom: 37px;
+}
+
+.issue-email-modal-btn {
+ padding: 0;
+ color: $gl-link-color;
+ background-color: transparent;
+ border: 0;
+ outline: 0;
+
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+.email-modal-input-group {
+ margin-bottom: 10px;
+
+ .form-control {
+ background-color: $white-light;
+ }
+
+ .btn {
+ background-color: $background-color;
+ border: 1px solid $border-gray-light;
+ }
+}
diff --git a/app/controllers/admin/requests_profiles_controller.rb b/app/controllers/admin/requests_profiles_controller.rb
new file mode 100644
index 00000000000..a478176e138
--- /dev/null
+++ b/app/controllers/admin/requests_profiles_controller.rb
@@ -0,0 +1,17 @@
+class Admin::RequestsProfilesController < Admin::ApplicationController
+ def index
+ @profile_token = Gitlab::RequestProfiler.profile_token
+ @profiles = Gitlab::RequestProfiler::Profile.all.group_by(&:request_path)
+ end
+
+ def show
+ clean_name = Rack::Utils.clean_path_info(params[:name])
+ profile = Gitlab::RequestProfiler::Profile.find(clean_name)
+
+ if profile
+ render text: profile.content
+ else
+ redirect_to admin_requests_profiles_path, alert: 'Profile not found'
+ end
+ end
+end
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index c89678cf2d8..d828d163c28 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -5,6 +5,7 @@ class AutocompleteController < ApplicationController
def users
@users ||= User.none
@users = @users.search(params[:search]) if params[:search].present?
+ @users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present?
@users = @users.active
@users = @users.reorder(:name)
@users = @users.page(params[:page])
diff --git a/app/controllers/explore/application_controller.rb b/app/controllers/explore/application_controller.rb
index 461fc059a3c..a1ab8b99048 100644
--- a/app/controllers/explore/application_controller.rb
+++ b/app/controllers/explore/application_controller.rb
@@ -1,5 +1,5 @@
class Explore::ApplicationController < ApplicationController
- skip_before_action :authenticate_user!, :reject_blocked
+ skip_before_action :authenticate_user!, :reject_blocked!
layout 'explore'
end
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index f7b44099b78..4eca278599f 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -1,5 +1,5 @@
class HelpController < ApplicationController
- skip_before_action :authenticate_user!, :reject_blocked
+ skip_before_action :authenticate_user!, :reject_blocked!
layout 'help'
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 6126acccaab..e926043f3eb 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -6,6 +6,7 @@ class Projects::BranchesController < Projects::ApplicationController
before_action :authorize_push_code!, only: [:new, :create, :destroy]
def index
+ @sort = params[:sort].presence || 'name'
@branches = BranchesFinder.new(@repository, params).execute
@branches = Kaminari.paginate_array(@branches).page(params[:page])
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index fa663c9bda4..3c6f29ac0ba 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -1,4 +1,5 @@
class Projects::IssuesController < Projects::ApplicationController
+ include NotesHelper
include ToggleSubscriptionAction
include IssuableActions
include ToggleAwardEmoji
@@ -70,6 +71,8 @@ class Projects::IssuesController < Projects::ApplicationController
@note = @project.notes.new(noteable: @issue)
@noteable = @issue
+ preload_max_access_for_authors(@notes, @project)
+
respond_to do |format|
format.html
format.json do
@@ -79,7 +82,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def create
- @issue = Issues::CreateService.new(project, current_user, issue_params).execute
+ @issue = Issues::CreateService.new(project, current_user, issue_params.merge(request: request)).execute
respond_to do |format|
format.html do
@@ -89,7 +92,7 @@ class Projects::IssuesController < Projects::ApplicationController
render :new
end
end
- format.js do |format|
+ format.js do
@link = @issue.attachment.url.to_js
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 72ab7cff90c..b162384d44d 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -3,6 +3,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
include DiffForPath
include DiffHelper
include IssuableActions
+ include NotesHelper
include ToggleAwardEmoji
before_action :module_enabled
@@ -382,6 +383,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@project_wiki,
@ref
)
+
+ preload_max_access_for_authors(@notes, @project)
end
def define_widget_vars
@@ -401,7 +404,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
}
@use_legacy_diff_notes = !@merge_request.support_new_diff_notes?
- @grouped_diff_discussions = @merge_request.notes.grouped_diff_discussions
+ @grouped_diff_discussions = @merge_request.notes.inc_author_project_award_emoji.grouped_diff_discussions
Banzai::NoteRenderer.render(
@grouped_diff_discussions.values.flat_map(&:notes),
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 6dc495247c8..8592579abbd 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -10,11 +10,12 @@ class Projects::TagsController < Projects::ApplicationController
@tags = @repository.tags_sorted_by(@sort)
@tags = Kaminari.paginate_array(@tags).page(params[:page])
- @releases = project.releases.where(tag: @tags)
+ @releases = project.releases.where(tag: @tags.map(&:name))
end
def show
@tag = @repository.find_tag(params[:id])
+
@release = @project.releases.find_or_initialize_by(tag: @tag.name)
@commit = @repository.commit(@tag.target)
end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 69c92d2bed2..61517d21f9f 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -1,5 +1,5 @@
class SearchController < ApplicationController
- skip_before_action :authenticate_user!, :reject_blocked
+ skip_before_action :authenticate_user!, :reject_blocked!
include SearchHelper
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 87bf84adad1..9d9b0dba57f 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -7,7 +7,7 @@ module NotesHelper
end
def note_editable?(note)
- note.editable? && can?(current_user, :admin_note, note)
+ Ability.can_edit_note?(current_user, note)
end
def noteable_json(noteable)
@@ -85,14 +85,13 @@ module NotesHelper
data: data, title: 'Add a reply'
end
- def note_max_access_for_user(note)
- @max_access_by_user_id ||= Hash.new do |hash, key|
- project = key[:project]
- hash[key] = project.team.human_max_access(key[:user_id])
- end
+ def preload_max_access_for_authors(notes, project)
+ user_ids = notes.map(&:author_id)
+ project.team.max_member_access_for_user_ids(user_ids)
+ end
- full_key = { project: note.project, user_id: note.author_id }
- @max_access_by_user_id[full_key]
+ def note_max_access_for_user(note)
+ note.project.team.human_max_access(note.author_id)
end
def discussion_diff_path(discussion)
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index bb395e37884..5f27e33c6ad 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -5,21 +5,9 @@ module SelectsHelper
css_class << "skip_ldap " if opts[:skip_ldap]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
-
- first_user = opts[:first_user] && current_user ? current_user.username : false
-
html = {
class: css_class,
- data: {
- placeholder: opts[:placeholder] || 'Search for a user',
- null_user: opts[:null_user] || false,
- any_user: opts[:any_user] || false,
- email_user: opts[:email_user] || false,
- first_user: first_user,
- current_user: opts[:current_user] || false,
- "push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
- author_id: opts[:author_id] || ''
- }
+ data: users_select_data_attributes(opts)
}
unless opts[:scope] == :all
@@ -68,4 +56,20 @@ module SelectsHelper
hidden_field_tag(id, value, class: css_class)
end
+
+ private
+
+ def users_select_data_attributes(opts)
+ {
+ placeholder: opts[:placeholder] || 'Search for a user',
+ null_user: opts[:null_user] || false,
+ any_user: opts[:any_user] || false,
+ email_user: opts[:email_user] || false,
+ first_user: opts[:first_user] && current_user ? current_user.username : false,
+ current_user: opts[:current_user] || false,
+ "push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
+ author_id: opts[:author_id] || '',
+ skip_users: opts[:skip_users] ? opts[:skip_users].map(&:id) : nil,
+ }
+ end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index ac3bf441c32..5075076c27b 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -389,6 +389,18 @@ class Ability
GroupProjectsFinder.new(group).execute(user).any?
end
+ def can_edit_note?(user, note)
+ return false if !note.editable? || !user.present?
+ return true if note.author == user || user.admin?
+
+ if note.project
+ max_access_level = note.project.team.max_member_access(user.id)
+ max_access_level >= Gitlab::Access::MASTER
+ else
+ false
+ end
+ end
+
def namespace_abilities(user, namespace)
rules = []
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index cbfa14e81f1..aac78d75f57 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -331,7 +331,7 @@ module Ci
end
def valid_token?(token)
- project.valid_runners_token? token
+ project.valid_runners_token?(token)
end
def has_tags?
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index acb6f5a2998..cbae1cd439b 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -17,7 +17,7 @@ module Issuable
belongs_to :assignee, class_name: "User"
belongs_to :updated_by, class_name: "User"
belongs_to :milestone
- has_many :notes, as: :noteable, dependent: :destroy do
+ has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy do
def authors_loaded?
# We check first if we're loaded to not load unnecessarily.
loaded? && to_a.all? { |note| note.association(:author).loaded? }
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
new file mode 100644
index 00000000000..3b8e6df2da9
--- /dev/null
+++ b/app/models/concerns/spammable.rb
@@ -0,0 +1,16 @@
+module Spammable
+ extend ActiveSupport::Concern
+
+ included do
+ attr_accessor :spam
+ after_validation :check_for_spam, on: :create
+ end
+
+ def spam?
+ @spam
+ end
+
+ def check_for_spam
+ self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam?
+ end
+end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 60af8c15340..d9428ebc9fb 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -6,6 +6,7 @@ class Issue < ActiveRecord::Base
include Referable
include Sortable
include Taskable
+ include Spammable
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
index 7bff9f4f052..bbb9f2346eb 100644
--- a/app/models/legacy_diff_note.rb
+++ b/app/models/legacy_diff_note.rb
@@ -25,6 +25,14 @@ class LegacyDiffNote < Note
@discussion_id ||= Digest::SHA1.hexdigest(self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code))
end
+ def project_repository
+ if RequestStore.active?
+ RequestStore.fetch("project:#{project_id}:repository") { self.project.repository }
+ else
+ self.project.repository
+ end
+ end
+
def diff_file_hash
line_code.split('_')[0] if line_code
end
@@ -34,7 +42,7 @@ class LegacyDiffNote < Note
end
def diff_file
- @diff_file ||= Gitlab::Diff::File.new(diff, repository: self.project.repository) if diff
+ @diff_file ||= Gitlab::Diff::File.new(diff, repository: project_repository) if diff
end
def diff_line
diff --git a/app/models/member.rb b/app/models/member.rb
index 44db3d977fa..24ab1276ee9 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -53,6 +53,10 @@ class Member < ActiveRecord::Base
default_value_for :notification_level, NotificationSetting.levels[:global]
class << self
+ def access_for_user_ids(user_ids)
+ where(user_id: user_ids).has_access.pluck(:user_id, :access_level).to_h
+ end
+
def find_by_invite_token(invite_token)
invite_token = Devise.token_generator.digest(self, :invite_token, invite_token)
find_by(invite_token: invite_token)
diff --git a/app/models/project.rb b/app/models/project.rb
index 023b1dc3725..dc44a757b4b 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -451,7 +451,9 @@ class Project < ActiveRecord::Base
def add_import_job
if forked?
- job_id = RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path)
+ job_id = RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path,
+ forked_from_project.path_with_namespace,
+ self.namespace.path)
else
job_id = RepositoryImportWorker.perform_async(self.id)
end
@@ -584,7 +586,11 @@ class Project < ActiveRecord::Base
end
def to_param
- path
+ if persisted? && errors.include?(:path)
+ path_was
+ else
+ path
+ end
end
def to_reference(_from_project = nil)
@@ -599,6 +605,13 @@ class Project < ActiveRecord::Base
web_url.split('://')[1]
end
+ def new_issue_address(author)
+ if Gitlab::IncomingEmail.enabled? && author
+ Gitlab::IncomingEmail.reply_address(
+ "#{path_with_namespace}+#{author.authentication_token}")
+ end
+ end
+
def build_commit_note(commit)
notes.new(commit_id: commit.id, noteable_type: 'Commit')
end
@@ -1151,7 +1164,10 @@ class Project < ActiveRecord::Base
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 { ProjectDestroyWorker.perform_async(id, user_id, params) }
+ 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
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 23e5b16221b..d7c986c1a91 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -46,7 +46,7 @@ class HipchatService < Service
return unless supported_events.include?(data[:object_kind])
message = create_message(data)
return unless message.present?
- gate[room].send('GitLab', message, message_options)
+ gate[room].send('GitLab', message, message_options(data))
end
def test(data)
@@ -67,8 +67,8 @@ class HipchatService < Service
@gate ||= HipChat::Client.new(token, options)
end
- def message_options
- { notify: notify.present? && notify == '1', color: color || 'yellow' }
+ def message_options(data = nil)
+ { notify: notify.present? && notify == '1', color: message_color(data) }
end
def create_message(data)
@@ -240,6 +240,21 @@ class HipchatService < Service
"#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
end
+ def message_color(data)
+ build_status_color(data) || color || 'yellow'
+ end
+
+ def build_status_color(data)
+ return unless data && data[:object_kind] == 'build'
+
+ case data[:commit][:status]
+ when 'success'
+ 'green'
+ else
+ 'red'
+ end
+ end
+
def project_name
project.name_with_namespace.gsub(/\s/, '')
end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 9d312a53790..fdfaf052730 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -132,39 +132,63 @@ class ProjectTeam
Gitlab::Access.options_with_owner.key(max_member_access(user_id))
end
- # This method assumes project and group members are eager loaded for optimal
- # performance.
- def max_member_access(user_id)
- access = []
+ # Determine the maximum access level for a group of users in bulk.
+ #
+ # Returns a Hash mapping user ID -> maximum access level.
+ def max_member_access_for_user_ids(user_ids)
+ user_ids = user_ids.uniq
+ key = "max_member_access:#{project.id}"
+ RequestStore.store[key] ||= {}
+ access = RequestStore.store[key]
- access += project.members.where(user_id: user_id).has_access.pluck(:access_level)
+ # Lookup only the IDs we need
+ user_ids = user_ids - access.keys
- if group
- access += group.members.where(user_id: user_id).has_access.pluck(:access_level)
- end
+ if user_ids.present?
+ user_ids.each { |id| access[id] = Gitlab::Access::NO_ACCESS }
- if project.invited_groups.any? && project.allowed_to_share_with_group?
- access << max_invited_level(user_id)
+ member_access = project.members.access_for_user_ids(user_ids)
+ merge_max!(access, member_access)
+
+ if group
+ group_access = group.members.access_for_user_ids(user_ids)
+ merge_max!(access, group_access)
+ end
+
+ # Each group produces a list of maximum access level per user. We take the
+ # max of the values produced by each group.
+ if project.invited_groups.any? && project.allowed_to_share_with_group?
+ project.project_group_links.each do |group_link|
+ invited_access = max_invited_level_for_users(group_link, user_ids)
+ merge_max!(access, invited_access)
+ end
+ end
end
- access.compact.max
+ access
+ end
+
+ def max_member_access(user_id)
+ max_member_access_for_user_ids([user_id])[user_id]
end
private
- def max_invited_level(user_id)
- project.project_group_links.map do |group_link|
- invited_group = group_link.group
- access = invited_group.group_members.find_by(user_id: user_id).try(:access_field)
+ # For a given group, return the maximum access level for the user. This is the min of
+ # the invited access level of the group and the access level of the user within the group.
+ # For example, if the group has been given DEVELOPER access but the member has MASTER access,
+ # the user should receive only DEVELOPER access.
+ def max_invited_level_for_users(group_link, user_ids)
+ invited_group = group_link.group
+ capped_access_level = group_link.group_access
+ access = invited_group.group_members.access_for_user_ids(user_ids)
- # If group member has higher access level we should restrict it
- # to max allowed access level
- if access && access > group_link.group_access
- access = group_link.group_access
- end
+ # If the user is not in the list, assume he/she does not have access
+ missing_users = user_ids - access.keys
+ missing_users.each { |id| access[id] = Gitlab::Access::NO_ACCESS }
- access
- end.compact.max
+ # Cap the maximum access by the invited level access
+ access.each { |key, value| access[key] = [value, capped_access_level].min }
end
def fetch_members(level = nil)
@@ -215,4 +239,8 @@ class ProjectTeam
def group
project.group
end
+
+ def merge_max!(first_hash, second_hash)
+ first_hash.merge!(second_hash) { |_key, old, new| old > new ? old : new }
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index e9d5f4c91f8..af65e5b20ec 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -211,6 +211,9 @@ class Repository
rugged.references.create(keep_around_ref_name(sha), sha, force: true)
rescue Rugged::ReferenceError => ex
Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
+ rescue Rugged::OSError => ex
+ raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
+ Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
end
end
@@ -612,11 +615,11 @@ class Repository
case value
when 'name'
branches.sort_by(&:name)
- when 'recently_updated'
+ when 'updated_desc'
branches.sort do |a, b|
commit(b.target).committed_date <=> commit(a.target).committed_date
end
- when 'last_updated'
+ when 'updated_asc'
branches.sort do |a, b|
commit(a.target).committed_date <=> commit(b.target).committed_date
end
@@ -985,6 +988,10 @@ class Repository
if was_empty || !target_branch
# Create branch
rugged.references.create(ref, newrev)
+
+ # If repo was empty expire cache
+ after_create if was_empty
+ after_create_branch
else
# Update head
current_head = find_branch(branch).target
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index e63e1af8766..5e2de2ccf64 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -2,10 +2,14 @@ module Issues
class CreateService < Issues::BaseService
def execute
filter_params
- label_params = params[:label_ids]
- issue = project.issues.new(params.except(:label_ids))
+ label_params = params.delete(:label_ids)
+ request = params.delete(:request)
+ api = params.delete(:api)
+ issue = project.issues.new(params)
issue.author = params[:author] || current_user
+ issue.spam = spam_check_service.execute(request, api)
+
if issue.save
issue.update_attributes(label_ids: label_params)
notification_service.new_issue(issue, current_user)
@@ -17,5 +21,11 @@ module Issues
issue
end
+
+ private
+
+ def spam_check_service
+ SpamCheckService.new(project, current_user, params)
+ end
end
end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index f06311511cc..921ca6748d3 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -3,7 +3,7 @@ module Projects
def execute
# check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level]
-
+
if new_visibility && new_visibility.to_i != project.visibility_level
unless can?(current_user, :change_visibility_level, project) &&
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
diff --git a/app/services/spam_check_service.rb b/app/services/spam_check_service.rb
new file mode 100644
index 00000000000..7c3e692bde9
--- /dev/null
+++ b/app/services/spam_check_service.rb
@@ -0,0 +1,38 @@
+class SpamCheckService < BaseService
+ include Gitlab::AkismetHelper
+
+ attr_accessor :request, :api
+
+ def execute(request, api)
+ @request, @api = request, api
+ return false unless request || check_for_spam?(project)
+ return false unless is_spam?(request.env, current_user, text)
+
+ create_spam_log
+
+ true
+ end
+
+ private
+
+ def text
+ [params[:title], params[:description]].reject(&:blank?).join("\n")
+ end
+
+ def spam_log_attrs
+ {
+ user_id: current_user.id,
+ project_id: project.id,
+ title: params[:title],
+ description: params[:description],
+ source_ip: client_ip(request.env),
+ user_agent: user_agent(request.env),
+ noteable_type: 'Issue',
+ via_api: api
+ }
+ end
+
+ def create_spam_log
+ CreateSpamLogService.new(project, current_user, spam_log_attrs).execute
+ end
+end
diff --git a/app/views/admin/background_jobs/_head.html.haml b/app/views/admin/background_jobs/_head.html.haml
index 9d722bd7382..89d7a40d6b0 100644
--- a/app/views/admin/background_jobs/_head.html.haml
+++ b/app/views/admin/background_jobs/_head.html.haml
@@ -16,3 +16,7 @@
= link_to admin_health_check_path, title: 'Health Check' do
%span
Health Check
+ = nav_link(controller: :requests_profiles) do
+ = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
+ %span
+ Requests Profiles
diff --git a/app/views/admin/requests_profiles/index.html.haml b/app/views/admin/requests_profiles/index.html.haml
new file mode 100644
index 00000000000..ae918086a57
--- /dev/null
+++ b/app/views/admin/requests_profiles/index.html.haml
@@ -0,0 +1,26 @@
+- @no_container = true
+- page_title 'Requests Profiles'
+= render 'admin/background_jobs/head'
+
+%div{ class: container_class }
+ %h3.page-title
+ = page_title
+
+ .bs-callout.clearfix
+ Pass the header
+ %code X-Profile-Token: #{@profile_token}
+ to profile the request
+
+ - if @profiles.present?
+ .prepend-top-default
+ - @profiles.each do |path, profiles|
+ .panel.panel-default.panel-small
+ .panel-heading
+ %code= path
+ %ul.content-list
+ - profiles.each do |profile|
+ %li
+ = link_to profile.time.to_s(:long), admin_requests_profile_path(profile), data: {no_turbolink: true}
+ - else
+ %p
+ No profiles found
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
index 6e993e58f0d..15dd98077c8 100644
--- a/app/views/import/bitbucket/status.html.haml
+++ b/app/views/import/bitbucket/status.html.haml
@@ -74,6 +74,4 @@
= link_to "import flow", status_import_bitbucket_path, "data-no-turbolink" => "true"
again.
-
-:javascript
- new ImporterStatus("#{jobs_import_bitbucket_path}", "#{import_bitbucket_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } }
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index d3d3c595c17..c8a6fa1aa9e 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -56,5 +56,4 @@
Import
= icon("spinner spin", class: "loading-icon")
-:javascript
- new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } }
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index 7486b1423e2..deaaf9af875 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -55,5 +55,4 @@
Import
= icon("spinner spin", class: "loading-icon")
-:javascript
- new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_github_path}", import_path: "#{import_github_path}" } }
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index aedb8468eca..fcfc6fd37f4 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -51,5 +51,4 @@
Import
= icon("spinner spin", class: "loading-icon")
-:javascript
- new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } }
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
index 267eee4f262..ed3afb0ce33 100644
--- a/app/views/import/gitorious/status.html.haml
+++ b/app/views/import/gitorious/status.html.haml
@@ -51,5 +51,4 @@
Import
= icon("spinner spin", class: "loading-icon")
-:javascript
- new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitorious_path}", import_path: "#{import_gitorious_path}" } }
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
index 5ada6b174eb..e79f122940a 100644
--- a/app/views/import/google_code/status.html.haml
+++ b/app/views/import/google_code/status.html.haml
@@ -77,5 +77,4 @@
= link_to "import flow", new_import_google_code_path
again.
-:javascript
- new ImporterStatus("#{jobs_import_google_code_path}", "#{import_google_code_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_google_code_path}", import_path: "#{import_google_code_path}" } }
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 5ee8772882e..ac04f57e217 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -9,7 +9,7 @@
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
%span
Overview
- = nav_link(controller: %w(system_info background_jobs logs health_check)) do
+ = nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do
= link_to admin_system_info_path, title: 'Monitoring' do
%span
Monitoring
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 6f806e3ce53..e889f29c816 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -7,28 +7,28 @@
.nav-text
Protected branches can be managed in project settings
- - if can? current_user, :push_code, @project
- .nav-controls
- = form_tag(filter_branches_path, method: :get) do
- = search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
- .dropdown.inline
- %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light
- - if params[:sort].present?
- = params[:sort].humanize
- - else
- Name
- %b.caret
- %ul.dropdown-menu.dropdown-menu-align-right
- %li
- = link_to filter_branches_path(sort: nil) do
- = sort_title_name
- = link_to filter_branches_path(sort: 'recently_updated') do
- = sort_title_recently_updated
- = link_to filter_branches_path(sort: 'last_updated') do
- = sort_title_oldest_updated
+ .nav-controls
+ = form_tag(filter_branches_path, method: :get) do
+ = search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
+
+ .dropdown.inline
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+ %span.light
+ = projects_sort_options_hash[@sort]
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ %li
+ = link_to filter_branches_path(sort: sort_value_name) do
+ = sort_title_name
+ = link_to filter_branches_path(sort: sort_value_recently_updated) do
+ = sort_title_recently_updated
+ = link_to filter_branches_path(sort: sort_value_oldest_updated) do
+ = sort_title_oldest_updated
+
+ - if can? current_user, :push_code, @project
= link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
New branch
+
- if @branches.any?
%ul.content-list.all-branches
- @branches.each do |branch|
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index b89183c40dc..a8bc53c2849 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -88,8 +88,9 @@
%p
%span.build-light-text Variables:
- %code
- - @build.trigger_request.variables.each do |key, value|
+
+ - @build.trigger_request.variables.each do |key, value|
+ %code
#{key}=#{value}
.block
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index a9fb3c58431..a3114771a42 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -22,6 +22,8 @@
- if defined?(ref) && ref
- if build.ref
+ .icon-container
+ = build.tag? ? icon('tag') : icon('code-fork')
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
- else
.light none
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 8ae433b4823..4bf3ccace20 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -7,7 +7,7 @@
.content-block.oneline-block.files-changed
.inline-parallel-buttons
- if !expand_all_diffs? && diff_files.any? { |diff_file| diff_file.collapsed? }
- = link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: 'html')), class: 'btn btn-default'
+ = link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: nil)), class: 'btn btn-default'
- if show_whitespace_toggle
- if current_controller?(:commit)
= commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs')
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 921155e970b..b282aa52b25 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -4,6 +4,7 @@
%h4.prepend-top-0
Project settings
.col-lg-9
+ .project-edit-errors
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
%fieldset.append-bottom-0
.form-group
@@ -190,6 +191,7 @@
%h4.prepend-top-0.warning-title
Rename repository
.col-lg-9
+ = render 'projects/errors'
= form_for([@project.namespace.becomes(Namespace), @project]) do |f|
.form-group.project_name_holder
= f.label :name, class: 'label-light' do
diff --git a/app/views/projects/graphs/ci/_build_times.haml b/app/views/projects/graphs/ci/_build_times.haml
index c58223fd39e..195f18afc76 100644
--- a/app/views/projects/graphs/ci/_build_times.haml
+++ b/app/views/projects/graphs/ci/_build_times.haml
@@ -19,4 +19,9 @@
]
}
var ctx = $("#build_timesChart").get(0).getContext("2d");
- new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, maintainAspectRatio: false});
+ var options = { scaleOverlay: true, responsive: true, maintainAspectRatio: false };
+ if (window.innerWidth < 768) {
+ // Scale fonts if window width lower than 768px (iPad portrait)
+ options.scaleFontSize = 8
+ }
+ new Chart(ctx).Bar(data, options);
diff --git a/app/views/projects/graphs/ci/_builds.haml b/app/views/projects/graphs/ci/_builds.haml
index 8fca07114fa..1fbf6ca2c1c 100644
--- a/app/views/projects/graphs/ci/_builds.haml
+++ b/app/views/projects/graphs/ci/_builds.haml
@@ -48,4 +48,9 @@
]
}
var ctx = $("##{scope}Chart").get(0).getContext("2d");
- new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true, maintainAspectRatio: false});
+ var options = { scaleOverlay: true, responsive: true, maintainAspectRatio: false };
+ if (window.innerWidth < 768) {
+ // Scale fonts if window width lower than 768px (iPad portrait)
+ options.scaleFontSize = 8
+ }
+ new Chart(ctx).Line(data, options);
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index 65db8af494d..7e34a89f9ae 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -59,6 +59,10 @@
var container = $(selector).parent();
var generateChart = function() {
selector.attr('width', $(container).width());
+ if (window.innerWidth < 768) {
+ // Scale fonts if window width lower than 768px (iPad portrait)
+ options.scaleFontSize = 8
+ }
return new Chart(ctx).Bar(data, options);
};
// enabling auto-resizing
diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml
new file mode 100644
index 00000000000..72669372497
--- /dev/null
+++ b/app/views/projects/issues/_issue_by_email.html.haml
@@ -0,0 +1,27 @@
+.issues-footer.text-center
+ %button.issue-email-modal-btn{ type: "button", data: { toggle: "modal", target: "#issue-email-modal" } }
+ Email a new issue to this project
+
+#issue-email-modal.modal.fade{ tabindex: "-1", role: "dialog" }
+ .modal-dialog{ role: "document" }
+ .modal-content
+ .modal-header
+ %button.close{ type: "button", data: { dismiss: "modal" }, aria: { label: "close" } }
+ %span{ aria: { hidden: "true" } }= icon("times")
+ %h4.modal-title
+ Create new issue by email
+ .modal-body
+ %p
+ Write an email to the below email address. (This is a private email address, so keep it secret.)
+ .email-modal-input-group.input-group
+ = text_field_tag :issue_email, email, class: "monospace js-select-on-focus form-control", readonly: true
+ .input-group-btn
+ = clipboard_button(clipboard_target: '#issue_email')
+ %p
+ Send an email to this address to create an issue.
+ %p
+ Use the subject line as the title of your issue.
+ %p
+ Use the message as the body of your issue (feel free to include some nice
+ = succeed ")." do
+ = link_to "Markdown", help_page_path('markdown', 'markdown')
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 7612fe3719a..d0edd2f22ec 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,5 +1,6 @@
- @no_container = true
- page_title "Issues"
+- new_issue_email = @project.new_issue_address(current_user)
= render "projects/issues/head"
= content_for :meta_tags do
@@ -23,7 +24,9 @@
= render 'shared/issuable/filter', type: :issues
.issues-holder
- = render "issues"
+ = render 'issues'
+ - if new_issue_email
+ = render 'issue_by_email', email: new_issue_email
- else
.blank-state.blank-state-welcome
%h2.blank-state-title.blank-state-welcome-title
@@ -40,3 +43,5 @@
- if can? current_user, :create_issue, @project
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
New Issue
+ - if new_issue_email
+ = render 'issue_by_email', email: new_issue_email
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index 7d9bd08385a..dcf1f767bf7 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -6,4 +6,4 @@
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
$('.save-project-loader').hide();
$('.project-edit-container').show();
- $('.project-edit-content .btn-save').enable();
+ $('.edit-project .btn-save').enable();
diff --git a/app/views/shared/icons/_icon_status_cancel.svg b/app/views/shared/icons/_icon_status_cancel.svg
index 6a0bc1490c4..fd1ebbcbabd 100644
--- a/app/views/shared/icons/_icon_status_cancel.svg
+++ b/app/views/shared/icons/_icon_status_cancel.svg
@@ -1,12 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- <circle id="a" cx="7" cy="7" r="7"/>
- <mask id="b" width="14" height="14" x="0" y="0" fill="white">
- <use xlink:href="#a"/>
- </mask>
- </defs>
- <g fill="none" fill-rule="evenodd">
- <use stroke="#5C5C5C" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
- <rect width="10" height="1" x="2" y="6.5" fill="#5C5C5C" transform="rotate(45 7 7)" rx=".3"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <g fill="#5C5C5C" fill-rule="evenodd">
+ <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+ <rect width="8" height="2" x="3" y="6" transform="rotate(45 7 7)" rx=".5"/>
</g>
</svg>
diff --git a/app/views/shared/icons/_icon_status_failed.svg b/app/views/shared/icons/_icon_status_failed.svg
index c41ca18cae7..e56e0887416 100644
--- a/app/views/shared/icons/_icon_status_failed.svg
+++ b/app/views/shared/icons/_icon_status_failed.svg
@@ -1,12 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- <circle id="a" cx="7" cy="7" r="7"/>
- <mask id="b" width="14" height="14" x="0" y="0" fill="white">
- <use xlink:href="#a"/>
- </mask>
- </defs>
- <g fill="none" fill-rule="evenodd">
- <use stroke="#D22852" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
- <path fill="#D22852" d="M7.5,6.5 L7.5,4.30578971 C7.5,4.12531853 7.36809219,4 7.20537567,4 L6.79462433,4 C6.63904572,4 6.5,4.13690672 6.5,4.30578971 L6.5,6.5 L4.30578971,6.5 C4.12531853,6.5 4,6.63190781 4,6.79462433 L4,7.20537567 C4,7.36095428 4.13690672,7.5 4.30578971,7.5 L6.5,7.5 L6.5,9.69421029 C6.5,9.87468147 6.63190781,10 6.79462433,10 L7.20537567,10 C7.36095428,10 7.5,9.86309328 7.5,9.69421029 L7.5,7.5 L9.69421029,7.5 C9.87468147,7.5 10,7.36809219 10,7.20537567 L10,6.79462433 C10,6.63904572 9.86309328,6.5 9.69421029,6.5 L7.5,6.5 Z" transform="rotate(45 7 7)"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <g fill="#D22852" fill-rule="evenodd">
+ <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+ <path d="M7.72916667,6.27083333 L7.72916667,4.28939247 C7.72916667,4.12531853 7.59703895,4 7.43405116,4 L6.56594884,4 C6.40541585,4 6.27083333,4.12956542 6.27083333,4.28939247 L6.27083333,6.27083333 L4.28939247,6.27083333 C4.12531853,6.27083333 4,6.40296105 4,6.56594884 L4,7.43405116 C4,7.59458415 4.12956542,7.72916667 4.28939247,7.72916667 L6.27083333,7.72916667 L6.27083333,9.71060753 C6.27083333,9.87468147 6.40296105,10 6.56594884,10 L7.43405116,10 C7.59458415,10 7.72916667,9.87043458 7.72916667,9.71060753 L7.72916667,7.72916667 L9.71060753,7.72916667 C9.87468147,7.72916667 10,7.59703895 10,7.43405116 L10,6.56594884 C10,6.40541585 9.87043458,6.27083333 9.71060753,6.27083333 L7.72916667,6.27083333 Z" transform="rotate(-45 7 7)"/>
</g>
</svg>
diff --git a/app/views/shared/icons/_icon_status_pending.svg b/app/views/shared/icons/_icon_status_pending.svg
index 035cd8b4ccc..117f0367161 100644
--- a/app/views/shared/icons/_icon_status_pending.svg
+++ b/app/views/shared/icons/_icon_status_pending.svg
@@ -1,13 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- <circle id="a" cx="7" cy="7" r="7"/>
- <mask id="b" width="14" height="14" x="0" y="0" fill="white">
- <use xlink:href="#a"/>
- </mask>
- </defs>
- <g fill="none" fill-rule="evenodd">
- <use stroke="#E75E40" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
- <rect width="1" height="4" x="5" y="5" fill="#E75E40" rx=".3"/>
- <rect width="1" height="4" x="8" y="5" fill="#E75E40" rx=".3"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <g fill="#E75E40" fill-rule="evenodd">
+ <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+ <path d="M4.69999981,5.30065012 C4.69999981,5.13460564 4.83842754,5 5.00354719,5 L5.89645243,5 C6.06409702,5 6.19999981,5.13308716 6.19999981,5.30065012 L6.19999981,8.69934988 C6.19999981,8.86539436 6.06157207,9 5.89645243,9 L5.00354719,9 C4.8359026,9 4.69999981,8.86691284 4.69999981,8.69934988 L4.69999981,5.30065012 Z M7.69999981,5.30065012 C7.69999981,5.13460564 7.83842754,5 8.00354719,5 L8.89645243,5 C9.06409702,5 9.19999981,5.13308716 9.19999981,5.30065012 L9.19999981,8.69934988 C9.19999981,8.86539436 9.06157207,9 8.89645243,9 L8.00354719,9 C7.8359026,9 7.69999981,8.86691284 7.69999981,8.69934988 L7.69999981,5.30065012 Z"/>
</g>
</svg>
diff --git a/app/views/shared/icons/_icon_status_running.svg b/app/views/shared/icons/_icon_status_running.svg
index a48b3a25099..920d7952eb5 100644
--- a/app/views/shared/icons/_icon_status_running.svg
+++ b/app/views/shared/icons/_icon_status_running.svg
@@ -1,12 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- <circle id="a" cx="7" cy="7" r="7"/>
- <mask id="b" width="14" height="14" x="0" y="0" fill="white">
- <use xlink:href="#a"/>
- </mask>
- </defs>
- <g fill="none" fill-rule="evenodd">
- <use stroke="#2D9FD8" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
- <path fill="#2D9FD8" d="M7,3.00800862 C9.09023405,3.13960661 10.7448145,4.87657932 10.7448145,7 C10.7448145,9.209139 8.95395346,11 6.74481446,11 C5.4560962,11 4.30972054,10.3905589 3.57817301,9.44416214 L7,7 L7,3.00800862 Z"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <g fill="#2D9FD8" fill-rule="evenodd">
+ <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+ <path d="M7,3 C9.209139,3 11,4.790861 11,7 C11,9.209139 9.209139,11 7,11 C5.65802855,11 4.47040669,10.3391508 3.74481446,9.32513253 L7,7 L7,3 L7,3 Z"/>
</g>
</svg>
diff --git a/app/views/shared/icons/_icon_status_success.svg b/app/views/shared/icons/_icon_status_success.svg
index 260eab013a3..67b378b3571 100644
--- a/app/views/shared/icons/_icon_status_success.svg
+++ b/app/views/shared/icons/_icon_status_success.svg
@@ -1,15 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- <circle id="a" cx="7" cy="7" r="7"/>
- <mask id="b" width="14" height="14" x="0" y="0" fill="white">
- <use xlink:href="#a"/>
- </mask>
- </defs>
- <g fill="none" fill-rule="evenodd">
- <use stroke="#31AF64" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
- <g fill="#31AF64" transform="rotate(45 -.13 10.953)">
- <rect width="1" height="5" x="2" rx=".3"/>
- <rect width="3" height="1" y="4" rx=".3"/>
- </g>
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <g fill="#31AF64" fill-rule="evenodd">
+ <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+ <path d="M7.29166667,7.875 L5.54840803,7.875 C5.38293028,7.875 5.25,8.00712771 5.25,8.17011551 L5.25,9.03821782 C5.25,9.19875081 5.38360183,9.33333333 5.54840803,9.33333333 L8.24853534,9.33333333 C8.52035522,9.33333333 8.75,9.11228506 8.75,8.83960819 L8.75,8.46475969 L8.75,4.07392947 C8.75,3.92144267 8.61787229,3.79166667 8.45488449,3.79166667 L7.58678218,3.79166667 C7.42624919,3.79166667 7.29166667,3.91804003 7.29166667,4.07392947 L7.29166667,7.875 Z" transform="rotate(45 7 6.563)"/>
</g>
</svg>
diff --git a/app/views/shared/icons/_icon_status_warning.svg b/app/views/shared/icons/_icon_status_warning.svg
index d47e7a1c93f..d0ad4bd65b1 100644
--- a/app/views/shared/icons/_icon_status_warning.svg
+++ b/app/views/shared/icons/_icon_status_warning.svg
@@ -1,15 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- <circle id="a" cx="7" cy="7" r="7"/>
- <mask id="b" width="14" height="14" x="0" y="0" fill="white">
- <use xlink:href="#a"/>
- </mask>
- </defs>
- <g fill="none" fill-rule="evenodd">
- <g fill="#FF8A24" transform="translate(6 3)">
- <rect width="2" height="5" rx=".5"/>
- <rect width="2" height="2" y="6" rx=".5"/>
- </g>
- <use stroke="#FF8A24" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <g fill="#FF8A24" fill-rule="evenodd">
+ <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+ <path d="M6,3.49769878 C6,3.22282734 6.21403503,3 6.50468445,3 L7.49531555,3 C7.77404508,3 8,3.21484375 8,3.49769878 L8,7.50230122 C8,7.77717266 7.78596497,8 7.49531555,8 L6.50468445,8 C6.22595492,8 6,7.78515625 6,7.50230122 L6,3.49769878 Z M6,9.50468445 C6,9.22595492 6.21403503,9 6.50468445,9 L7.49531555,9 C7.77404508,9 8,9.21403503 8,9.50468445 L8,10.4953156 C8,10.7740451 7.78596497,11 7.49531555,11 L6.50468445,11 C6.22595492,11 6,10.785965 6,10.4953156 L6,9.50468445 Z"/>
</g>
</svg>
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index f2649e38eb3..842eebdea9e 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -21,31 +21,35 @@ class EmailReceiverWorker
return unless raw.present?
can_retry = false
- reason = nil
-
- case e
- when Gitlab::Email::Receiver::SentNotificationNotFoundError
- reason = "We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
- when Gitlab::Email::Receiver::EmptyEmailError
- can_retry = true
- reason = "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
- when Gitlab::Email::Receiver::AutoGeneratedEmailError
- reason = "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface."
- when Gitlab::Email::Receiver::UserNotFoundError
- reason = "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
- when Gitlab::Email::Receiver::UserBlockedError
- reason = "Your account has been blocked. If you believe this is in error, contact a staff member."
- when Gitlab::Email::Receiver::UserNotAuthorizedError
- reason = "You are not allowed to respond to the thread you are replying to. If you believe this is in error, contact a staff member."
- when Gitlab::Email::Receiver::NoteableNotFoundError
- reason = "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
- when Gitlab::Email::Receiver::InvalidNoteError
- can_retry = true
- reason = e.message
- else
- return
+ reason =
+ case e
+ when Gitlab::Email::UnknownIncomingEmail
+ "We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+ when Gitlab::Email::SentNotificationNotFoundError
+ "We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+ when Gitlab::Email::ProjectNotFound
+ "We couldn't find the project. Please check if there's any typo."
+ when Gitlab::Email::EmptyEmailError
+ can_retry = true
+ "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+ when Gitlab::Email::AutoGeneratedEmailError
+ "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface."
+ when Gitlab::Email::UserNotFoundError
+ "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+ when Gitlab::Email::UserBlockedError
+ "Your account has been blocked. If you believe this is in error, contact a staff member."
+ when Gitlab::Email::UserNotAuthorizedError
+ "You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+ when Gitlab::Email::NoteableNotFoundError
+ "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+ when Gitlab::Email::InvalidNoteError,
+ Gitlab::Email::InvalidIssueError
+ can_retry = true
+ e.message
+ end
+
+ if reason
+ EmailRejectionMailer.rejection(reason, raw, can_retry).deliver_later
end
-
- EmailRejectionMailer.rejection(reason, raw, can_retry).deliver_later
end
end
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index f7604e48f83..d69d6037053 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -4,7 +4,7 @@ class RepositoryForkWorker
sidekiq_options queue: :gitlab_shell
- def perform(project_id, source_path, target_path)
+ def perform(project_id, forked_from_repository_storage_path, source_path, target_path)
project = Project.find_by_id(project_id)
unless project.present?
@@ -12,7 +12,8 @@ class RepositoryForkWorker
return
end
- result = gitlab_shell.fork_repository(project.repository_storage_path, source_path, target_path)
+ result = gitlab_shell.fork_repository(forked_from_repository_storage_path, source_path,
+ project.repository_storage_path, target_path)
unless result
logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}")
project.mark_import_as_failed('The project could not be forked.')
diff --git a/app/workers/requests_profiles_worker.rb b/app/workers/requests_profiles_worker.rb
new file mode 100644
index 00000000000..9dd228a2483
--- /dev/null
+++ b/app/workers/requests_profiles_worker.rb
@@ -0,0 +1,9 @@
+class RequestsProfilesWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform
+ Gitlab::RequestProfiler.remove_all_profiles
+ end
+end
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index 293f2b71d65..74325872b09 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -68,6 +68,25 @@
:why: https://opensource.org/licenses/BSD-2-Clause
:versions: []
:when: 2016-05-02 05:55:09.796363000 Z
+- - :whitelist
+ - LGPLv2+
+ - :who: Stan Hu
+ :why: Equivalent to LGPLv2
+ :versions: []
+ :when: 2016-06-07 17:14:10.907682000 Z
+- - :whitelist
+ - Artistic 2.0
+ - :who: Josh Frye
+ :why: Disk/mount information display on Admin pages
+ :versions: []
+ :when: 2016-06-29 16:32:45.432113000 Z
+- - :whitelist
+ - Simplified BSD
+ - :who: Douwe Maan
+ :why: https://opensource.org/licenses/BSD-2-Clause
+ :versions: []
+ :when: 2016-07-26 21:24:07.248480000 Z
+
# LICENSE BLACKLIST
- - :blacklist
@@ -175,15 +194,3 @@
:why: https://github.com/jmcnevin/rubypants/blob/master/LICENSE.rdoc
:versions: []
:when: 2016-05-02 05:56:50.696858000 Z
-- - :whitelist
- - LGPLv2+
- - :who: Stan Hu
- :why: Equivalent to LGPLv2
- :versions: []
- :when: 2016-06-07 17:14:10.907682000 Z
-- - :whitelist
- - Artistic 2.0
- - :who: Josh Frye
- :why: Disk/mount information display on Admin pages
- :versions: []
- :when: 2016-06-29 16:32:45.432113000 Z
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 86f55210487..49130f37b31 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -290,6 +290,9 @@ Settings.cron_jobs['repository_archive_cache_worker']['job_class'] = 'Repository
Settings.cron_jobs['gitlab_remove_project_export_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['gitlab_remove_project_export_worker']['cron'] ||= '0 * * * *'
Settings.cron_jobs['gitlab_remove_project_export_worker']['job_class'] = 'GitlabRemoveProjectExportWorker'
+Settings.cron_jobs['requests_profiles_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['requests_profiles_worker']['cron'] ||= '0 0 * * *'
+Settings.cron_jobs['requests_profiles_worker']['job_class'] = 'RequestsProfilesWorker'
#
# GitLab Shell
diff --git a/config/initializers/6_validations.rb b/config/initializers/6_validations.rb
index 37746968675..d92f64e1647 100644
--- a/config/initializers/6_validations.rb
+++ b/config/initializers/6_validations.rb
@@ -26,4 +26,4 @@ def validate_storages
end
end
-validate_storages unless Rails.env.test?
+validate_storages unless Rails.env.test? || ENV['SKIP_STORAGE_VALIDATION'] == 'true'
diff --git a/config/initializers/request_profiler.rb b/config/initializers/request_profiler.rb
new file mode 100644
index 00000000000..fb5a7b8372e
--- /dev/null
+++ b/config/initializers/request_profiler.rb
@@ -0,0 +1,3 @@
+Rails.application.configure do |config|
+ config.middleware.use(Gitlab::RequestProfiler::Middleware)
+end
diff --git a/config/routes.rb b/config/routes.rb
index 40dce1745db..30f1134c573 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -42,10 +42,9 @@ Rails.application.routes.draw do
resource :lint, only: [:show, :create]
- resources :projects do
+ resources :projects, only: [:index, :show] do
member do
get :status, to: 'projects#badge'
- get :integration
end
end
@@ -144,13 +143,13 @@ Rails.application.routes.draw do
get :jobs
end
- resource :gitlab, only: [:create, :new], controller: :gitlab do
+ resource :gitlab, only: [:create], controller: :gitlab do
get :status
get :callback
get :jobs
end
- resource :bitbucket, only: [:create, :new], controller: :bitbucket do
+ resource :bitbucket, only: [:create], controller: :bitbucket do
get :status
get :callback
get :jobs
@@ -243,7 +242,6 @@ Rails.application.routes.draw do
get :projects
get :keys
get :groups
- put :team_update
put :block
put :unblock
put :unlock
@@ -281,6 +279,7 @@ Rails.application.routes.draw do
resource :health_check, controller: 'health_check', only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show]
resource :system_info, controller: 'system_info', only: [:show]
+ resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ }
resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
root to: 'projects#index', as: :projects
@@ -300,7 +299,7 @@ Rails.application.routes.draw do
end
end
- resource :appearances, path: 'appearance' do
+ resource :appearances, only: [:show, :create, :update], path: 'appearance' do
member do
get :preview
delete :logo
@@ -309,7 +308,7 @@ Rails.application.routes.draw do
end
resource :application_settings, only: [:show, :update] do
- resources :services
+ resources :services, only: [:index, :edit, :update]
put :reset_runners_token
put :reset_health_check_token
put :clear_repository_check_states
@@ -346,7 +345,7 @@ Rails.application.routes.draw do
end
scope module: :profiles do
- resource :account, only: [:show, :update] do
+ resource :account, only: [:show] do
member do
delete :unlink
end
@@ -358,7 +357,7 @@ Rails.application.routes.draw do
end
end
resource :preferences, only: [:show, :update]
- resources :keys
+ resources :keys, only: [:index, :show, :new, :create, :destroy]
resources :emails, only: [:index, :create, :destroy]
resource :avatar, only: [:destroy]
@@ -660,7 +659,7 @@ Rails.application.routes.draw do
post '/wikis/*id/markdown_preview', to: 'wikis#markdown_preview', constraints: WIKI_SLUG_ID, as: 'wiki_markdown_preview'
end
- resource :repository, only: [:show, :create] do
+ resource :repository, only: [:create] do
member do
get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex }
end
@@ -789,7 +788,7 @@ Rails.application.routes.draw do
end
end
- resources :labels, constraints: { id: /\d+/ } do
+ resources :labels, except: [:show], constraints: { id: /\d+/ } do
collection do
post :generate
post :set_priorities
@@ -814,7 +813,7 @@ Rails.application.routes.draw do
end
end
- resources :project_members, except: [:new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do
+ resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do
collection do
delete :leave
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index b4639999967..e3316ecdb6c 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -20,7 +20,6 @@ Sidekiq::Testing.inline! do
'https://github.com/airbnb/javascript.git',
'https://github.com/tessalt/echo-chamber-js.git',
'https://github.com/atom/atom.git',
- 'https://github.com/ipselon/react-ui-builder.git',
'https://github.com/mattermost/platform.git',
'https://github.com/purifycss/purifycss.git',
'https://github.com/facebook/nuclide.git',
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 57c2e1d9b87..2960c2ae428 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -81,6 +81,11 @@ Example response:
"parent_ids": [
"ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
],
+ "stats": {
+ "additions": 15,
+ "deletions": 10,
+ "total": 25
+ },
"status": "running"
}
```
diff --git a/doc/integration/akismet.md b/doc/integration/akismet.md
index 5cc09bd536d..c222d21612f 100644
--- a/doc/integration/akismet.md
+++ b/doc/integration/akismet.md
@@ -1,9 +1,14 @@
# Akismet
+> *Note:* Before 8.11 only issues submitted via the API and for non-project
+members were submitted to Akismet.
+
GitLab leverages [Akismet](http://akismet.com) to protect against spam. Currently
-GitLab uses Akismet to prevent users who are not members of a project from
-creating spam via the GitLab API. Detected spam will be rejected, and
-an entry in the "Spam Log" section in the Admin page will be created.
+GitLab uses Akismet to prevent the creation of spam issues on public projects. Issues
+created via the WebUI or the API can be submitted to Akismet for review.
+
+Detected spam will be rejected, and an entry in the "Spam Log" section in the
+Admin page will be created.
Privacy note: GitLab submits the user's IP and user agent to Akismet. Note that
adding a user to a project will disable the Akismet check and prevent this
diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md
new file mode 100644
index 00000000000..fc6262dd108
--- /dev/null
+++ b/doc/update/8.10-to-8.11.md
@@ -0,0 +1,191 @@
+# From 8.10 to 8.11
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 8-11-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-11-stable-ee
+```
+
+### 4. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v3.2.1
+```
+
+### 5. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. This requires
+[Go 1.5](https://golang.org/dl) which should already be on your system from
+GitLab 8.1.
+
+```bash
+cd /home/git/gitlab-workhorse
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout v0.7.8
+sudo -u git -H make
+```
+
+### 6. Update MySQL permissions
+
+If you are using MySQL you need to grant the GitLab user the necessary
+permissions on the database:
+
+```bash
+# Login to MySQL
+mysql -u root -p
+
+# Grant the GitLab user the REFERENCES permission on the database
+GRANT REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost';
+
+# Quit the database session
+mysql> \q
+```
+
+### 7. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+```
+
+### 8. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+git diff origin/8-10-stable:config/gitlab.yml.example origin/8-11-stable:config/gitlab.yml.example
+```
+
+#### Git configuration
+
+Disable `git gc --auto` because GitLab runs `git gc` for us already.
+
+```sh
+sudo -u git -H git config --global gc.auto 0
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-10-stable:lib/support/nginx/gitlab-ssl origin/8-11-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-10-stable:lib/support/nginx/gitlab origin/8-11-stable:lib/support/nginx/gitlab
+```
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-11-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-11-stable/config/initializers/smtp_settings.rb.sample#L13?
+
+#### Init script
+
+Ensure you're still up-to-date with the latest init script changes:
+
+ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+
+### 9. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 10. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (8.10)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 8.9 to 8.10](8.9-to-8.10.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md
index 71cbe5c8ac6..a057a423e61 100644
--- a/doc/update/8.9-to-8.10.md
+++ b/doc/update/8.9-to-8.10.md
@@ -46,7 +46,7 @@ sudo -u git -H git checkout 8-10-stable-ee
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags
-sudo -u git -H git checkout v3.2.0
+sudo -u git -H git checkout v3.2.1
```
### 5. Update gitlab-workhorse
diff --git a/features/support/env.rb b/features/support/env.rb
index f0a3dd8d2d0..569fd444e86 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -1,6 +1,5 @@
-if ENV['SIMPLECOV']
- require 'simplecov'
-end
+require './spec/simplecov_env'
+SimpleCovEnv.start!
ENV['RAILS_ENV'] = 'test'
require './config/environment'
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index fbf0d74663f..e76e7304674 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -149,8 +149,13 @@ module API
expose :safe_message, as: :message
end
+ class RepoCommitStats < Grape::Entity
+ expose :additions, :deletions, :total
+ end
+
class RepoCommitDetail < RepoCommit
expose :parent_ids, :committed_date, :authored_date
+ expose :stats, using: Entities::RepoCommitStats
expose :status
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index c588103e517..c4d3134da6c 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -21,17 +21,6 @@ module API
def filter_issues_milestone(issues, milestone)
issues.includes(:milestone).where('milestones.title' => milestone)
end
-
- def create_spam_log(project, current_user, attrs)
- params = attrs.merge({
- source_ip: client_ip(env),
- user_agent: user_agent(env),
- noteable_type: 'Issue',
- via_api: true
- })
-
- ::CreateSpamLogService.new(project, current_user, params).execute
- end
end
resource :issues do
@@ -168,15 +157,13 @@ module API
end
project = user_project
- text = [attrs[:title], attrs[:description]].reject(&:blank?).join("\n")
- if check_for_spam?(project, current_user) && is_spam?(env, current_user, text)
- create_spam_log(project, current_user, attrs)
+ issue = ::Issues::CreateService.new(project, current_user, attrs.merge(request: request, api: true)).execute
+
+ if issue.spam?
render_api_error!({ error: 'Spam detected' }, 400)
end
- issue = ::Issues::CreateService.new(project, current_user, attrs).execute
-
if issue.valid?
# Find or create labels and attach to issue. Labels are valid because
# we already checked its name, so there can't be an error here
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index de41ea415a6..a533bac2692 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -7,6 +7,7 @@ module Gitlab
module Access
class AccessDeniedError < StandardError; end
+ NO_ACCESS = 0
GUEST = 10
REPORTER = 20
DEVELOPER = 30
diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb
index 04676fdb748..207736b59db 100644
--- a/lib/gitlab/akismet_helper.rb
+++ b/lib/gitlab/akismet_helper.rb
@@ -17,8 +17,8 @@ module Gitlab
env['HTTP_USER_AGENT']
end
- def check_for_spam?(project, user)
- akismet_enabled? && !project.team.member?(user)
+ def check_for_spam?(project)
+ akismet_enabled? && project.public?
end
def is_spam?(environment, user, text)
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 34e0143a82e..839a4fa30d5 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -60,16 +60,18 @@ module Gitlab
end
# Fork repository to new namespace
- # storage - project's storage path
+ # forked_from_storage - forked-from project's storage path
# path - project path with namespace
+ # forked_to_storage - forked-to project's storage path
# fork_namespace - namespace for forked project
#
# Ex.
- # fork_repository("/path/to/storage", "gitlab/gitlab-ci", "randx")
+ # fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "randx")
#
- def fork_repository(storage, path, fork_namespace)
+ def fork_repository(forked_from_storage, path, forked_to_storage, fork_namespace)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project',
- storage, "#{path}.git", fork_namespace])
+ forked_from_storage, "#{path}.git", forked_to_storage,
+ fork_namespace])
end
# Remove repository from file system
diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb
new file mode 100644
index 00000000000..bd3267e2a80
--- /dev/null
+++ b/lib/gitlab/email/handler.rb
@@ -0,0 +1,17 @@
+require 'gitlab/email/handler/create_note_handler'
+require 'gitlab/email/handler/create_issue_handler'
+
+module Gitlab
+ module Email
+ module Handler
+ HANDLERS = [CreateNoteHandler, CreateIssueHandler]
+
+ def self.for(mail, mail_key)
+ HANDLERS.find do |klass|
+ handler = klass.new(mail, mail_key)
+ break handler if handler.can_handle?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb
new file mode 100644
index 00000000000..b7ed11cb638
--- /dev/null
+++ b/lib/gitlab/email/handler/base_handler.rb
@@ -0,0 +1,60 @@
+module Gitlab
+ module Email
+ module Handler
+ class BaseHandler
+ attr_reader :mail, :mail_key
+
+ def initialize(mail, mail_key)
+ @mail = mail
+ @mail_key = mail_key
+ end
+
+ def message
+ @message ||= process_message
+ end
+
+ def author
+ raise NotImplementedError
+ end
+
+ def project
+ raise NotImplementedError
+ end
+
+ private
+
+ def validate_permission!(permission)
+ raise UserNotFoundError unless author
+ raise UserBlockedError if author.blocked?
+ raise ProjectNotFound unless author.can?(:read_project, project)
+ raise UserNotAuthorizedError unless author.can?(permission, project)
+ end
+
+ def process_message
+ message = ReplyParser.new(mail).execute.strip
+ add_attachments(message)
+ end
+
+ def add_attachments(reply)
+ attachments = Email::AttachmentUploader.new(mail).execute(project)
+
+ reply + attachments.map do |link|
+ "\n\n#{link[:markdown]}"
+ end.join
+ end
+
+ def verify_record!(record:, invalid_exception:, record_name:)
+ return if record.persisted?
+
+ error_title = "The #{record_name} could not be created for the following reasons:"
+
+ msg = error_title + record.errors.full_messages.map do |error|
+ "\n\n- #{error}"
+ end.join
+
+ raise invalid_exception, msg
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb
new file mode 100644
index 00000000000..4e6566af8ab
--- /dev/null
+++ b/lib/gitlab/email/handler/create_issue_handler.rb
@@ -0,0 +1,52 @@
+
+require 'gitlab/email/handler/base_handler'
+
+module Gitlab
+ module Email
+ module Handler
+ class CreateIssueHandler < BaseHandler
+ attr_reader :project_path, :authentication_token
+
+ def initialize(mail, mail_key)
+ super(mail, mail_key)
+ @project_path, @authentication_token =
+ mail_key && mail_key.split('+', 2)
+ end
+
+ def can_handle?
+ !authentication_token.nil?
+ end
+
+ def execute
+ raise ProjectNotFound unless project
+
+ validate_permission!(:create_issue)
+
+ verify_record!(
+ record: create_issue,
+ invalid_exception: InvalidIssueError,
+ record_name: 'issue')
+ end
+
+ def author
+ @author ||= User.find_by(authentication_token: authentication_token)
+ end
+
+ def project
+ @project ||= Project.find_with_namespace(project_path)
+ end
+
+ private
+
+ def create_issue
+ Issues::CreateService.new(
+ project,
+ author,
+ title: mail.subject,
+ description: message
+ ).execute
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb
new file mode 100644
index 00000000000..06dae31cc27
--- /dev/null
+++ b/lib/gitlab/email/handler/create_note_handler.rb
@@ -0,0 +1,55 @@
+
+require 'gitlab/email/handler/base_handler'
+
+module Gitlab
+ module Email
+ module Handler
+ class CreateNoteHandler < BaseHandler
+ def can_handle?
+ mail_key =~ /\A\w+\z/
+ end
+
+ def execute
+ raise SentNotificationNotFoundError unless sent_notification
+ raise AutoGeneratedEmailError if mail.header.to_s =~ /auto-(generated|replied)/
+
+ validate_permission!(:create_note)
+
+ raise NoteableNotFoundError unless sent_notification.noteable
+ raise EmptyEmailError if message.blank?
+
+ verify_record!(
+ record: create_note,
+ invalid_exception: InvalidNoteError,
+ record_name: 'comment')
+ end
+
+ def author
+ sent_notification.recipient
+ end
+
+ def project
+ sent_notification.project
+ end
+
+ def sent_notification
+ @sent_notification ||= SentNotification.for(mail_key)
+ end
+
+ private
+
+ def create_note
+ Notes::CreateService.new(
+ project,
+ author,
+ note: message,
+ noteable_type: sent_notification.noteable_type,
+ noteable_id: sent_notification.noteable_id,
+ commit_id: sent_notification.commit_id,
+ line_code: sent_notification.line_code
+ ).execute
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index 1c671a7487b..9213cfb51e8 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -1,18 +1,24 @@
+
+require 'gitlab/email/handler'
+
# Inspired in great part by Discourse's Email::Receiver
module Gitlab
module Email
- class Receiver
- class ProcessingError < StandardError; end
- class EmailUnparsableError < ProcessingError; end
- class SentNotificationNotFoundError < ProcessingError; end
- class EmptyEmailError < ProcessingError; end
- class AutoGeneratedEmailError < ProcessingError; end
- class UserNotFoundError < ProcessingError; end
- class UserBlockedError < ProcessingError; end
- class UserNotAuthorizedError < ProcessingError; end
- class NoteableNotFoundError < ProcessingError; end
- class InvalidNoteError < ProcessingError; end
+ class ProcessingError < StandardError; end
+ class EmailUnparsableError < ProcessingError; end
+ class SentNotificationNotFoundError < ProcessingError; end
+ class ProjectNotFound < ProcessingError; end
+ class EmptyEmailError < ProcessingError; end
+ class AutoGeneratedEmailError < ProcessingError; end
+ class UserNotFoundError < ProcessingError; end
+ class UserBlockedError < ProcessingError; end
+ class UserNotAuthorizedError < ProcessingError; end
+ class NoteableNotFoundError < ProcessingError; end
+ class InvalidNoteError < ProcessingError; end
+ class InvalidIssueError < ProcessingError; end
+ class UnknownIncomingEmail < ProcessingError; end
+ class Receiver
def initialize(raw)
@raw = raw
end
@@ -20,91 +26,38 @@ module Gitlab
def execute
raise EmptyEmailError if @raw.blank?
- raise SentNotificationNotFoundError unless sent_notification
-
- raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/
-
- author = sent_notification.recipient
-
- raise UserNotFoundError unless author
-
- raise UserBlockedError if author.blocked?
-
- project = sent_notification.project
-
- raise UserNotAuthorizedError unless project && author.can?(:create_note, project)
-
- raise NoteableNotFoundError unless sent_notification.noteable
-
- reply = ReplyParser.new(message).execute.strip
-
- raise EmptyEmailError if reply.blank?
-
- reply = add_attachments(reply)
-
- note = create_note(reply)
+ mail = build_mail
+ mail_key = extract_mail_key(mail)
+ handler = Handler.for(mail, mail_key)
- unless note.persisted?
- msg = "The comment could not be created for the following reasons:"
- note.errors.full_messages.each do |error|
- msg << "\n\n- #{error}"
- end
+ raise UnknownIncomingEmail unless handler
- raise InvalidNoteError, msg
- end
+ handler.execute
end
- private
-
- def message
- @message ||= Mail::Message.new(@raw)
- rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e
+ def build_mail
+ Mail::Message.new(@raw)
+ rescue Encoding::UndefinedConversionError,
+ Encoding::InvalidByteSequenceError => e
raise EmailUnparsableError, e
end
- def reply_key
- key_from_to_header || key_from_additional_headers
+ def extract_mail_key(mail)
+ key_from_to_header(mail) || key_from_additional_headers(mail)
end
- def key_from_to_header
- key = nil
- message.to.each do |address|
+ def key_from_to_header(mail)
+ mail.to.find do |address|
key = Gitlab::IncomingEmail.key_from_address(address)
- break if key
+ break key if key
end
-
- key
end
- def key_from_additional_headers
- reply_key = nil
-
- Array(message.references).each do |message_id|
- reply_key = Gitlab::IncomingEmail.key_from_fallback_reply_message_id(message_id)
- break if reply_key
+ def key_from_additional_headers(mail)
+ Array(mail.references).find do |mail_id|
+ key = Gitlab::IncomingEmail.key_from_fallback_message_id(mail_id)
+ break key if key
end
-
- reply_key
- end
-
- def sent_notification
- return nil unless reply_key
-
- SentNotification.for(reply_key)
- end
-
- def add_attachments(reply)
- attachments = Email::AttachmentUploader.new(message).execute(sent_notification.project)
-
- attachments.each do |link|
- reply << "\n\n#{link[:markdown]}"
- end
-
- reply
- end
-
- def create_note(reply)
- sent_notification.create_note(reply)
end
end
end
diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb
index 8ce9d32abe0..d7be50bd437 100644
--- a/lib/gitlab/incoming_email.rb
+++ b/lib/gitlab/incoming_email.rb
@@ -1,7 +1,7 @@
module Gitlab
module IncomingEmail
class << self
- FALLBACK_REPLY_MESSAGE_ID_REGEX = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\Z/.freeze
+ FALLBACK_MESSAGE_ID_REGEX = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\Z/.freeze
def enabled?
config.enabled && config.address
@@ -21,8 +21,8 @@ module Gitlab
match[1]
end
- def key_from_fallback_reply_message_id(message_id)
- match = message_id.match(FALLBACK_REPLY_MESSAGE_ID_REGEX)
+ def key_from_fallback_message_id(mail_id)
+ match = mail_id.match(FALLBACK_MESSAGE_ID_REGEX)
return unless match
match[1]
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
index 49f702f91f6..86a5b9a201a 100644
--- a/lib/gitlab/metrics.rb
+++ b/lib/gitlab/metrics.rb
@@ -124,6 +124,11 @@ module Gitlab
trans.action = action if trans
end
+ # Returns the prefix to use for the name of a series.
+ def self.series_prefix
+ @series_prefix ||= Sidekiq.server? ? 'sidekiq_' : 'rails_'
+ end
+
# When enabled this should be set before being used as the usual pattern
# "@foo ||= bar" is _not_ thread-safe.
if enabled?
diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb
index dcec7543c13..4b7a791e497 100644
--- a/lib/gitlab/metrics/instrumentation.rb
+++ b/lib/gitlab/metrics/instrumentation.rb
@@ -9,14 +9,17 @@ module Gitlab
#
# Gitlab::Metrics::Instrumentation.instrument_method(User, :by_login)
module Instrumentation
- SERIES = 'method_calls'
-
PROXY_IVAR = :@__gitlab_instrumentation_proxy
def self.configure
yield self
end
+ # Returns the name of the series to use for storing method calls.
+ def self.series
+ @series ||= "#{Metrics.series_prefix}method_calls"
+ end
+
# Instruments a class method.
#
# mod - The module to instrument as a Module/Class.
@@ -141,15 +144,15 @@ module Gitlab
# generated method _only_ accepts regular arguments if the underlying
# method also accepts them.
if method.arity == 0
- args_signature = '&block'
+ args_signature = ''
else
- args_signature = '*args, &block'
+ args_signature = '*args'
end
proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1
def #{name}(#{args_signature})
if trans = Gitlab::Metrics::Instrumentation.transaction
- trans.measure_method(#{label.inspect}) { super }
+ trans.method_call_for(#{label.to_sym.inspect}).measure { super }
else
super
end
diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index c048fe20ba7..d3465e5ec19 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -11,8 +11,8 @@ module Gitlab
def initialize(name, series)
@name = name
@series = series
- @real_time = 0.0
- @cpu_time = 0.0
+ @real_time = 0
+ @cpu_time = 0
@call_count = 0
end
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 82c18bb108b..287b7a83547 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -35,12 +35,12 @@ module Gitlab
if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)
def self.cpu_time
Process.
- clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond).to_f
+ clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond)
end
else
def self.cpu_time
Process.
- clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond).to_f
+ clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond)
end
end
@@ -48,14 +48,14 @@ module Gitlab
#
# Returns the time as a Float.
def self.real_time(precision = :millisecond)
- Process.clock_gettime(Process::CLOCK_REALTIME, precision).to_f
+ Process.clock_gettime(Process::CLOCK_REALTIME, precision)
end
# Returns the current monotonic clock time in a given precision.
#
# Returns the time as a Float.
def self.monotonic_time(precision = :millisecond)
- Process.clock_gettime(Process::CLOCK_MONOTONIC, precision).to_f
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, precision)
end
end
end
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index bded245da43..968f3218950 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -52,23 +52,16 @@ module Gitlab
end
def add_metric(series, values, tags = {})
- @metrics << Metric.new("#{series_prefix}#{series}", values, tags)
+ @metrics << Metric.new("#{Metrics.series_prefix}#{series}", values, tags)
end
- # Measures the time it takes to execute a method.
- #
- # Multiple calls to the same method add up to the total runtime of the
- # method.
- #
- # name - The full name of the method to measure (e.g. `User#sign_in`).
- def measure_method(name, &block)
- unless @methods[name]
- series = "#{series_prefix}#{Instrumentation::SERIES}"
-
- @methods[name] = MethodCall.new(name, series)
+ # Returns a MethodCall object for the given name.
+ def method_call_for(name)
+ unless method = @methods[name]
+ @methods[name] = method = MethodCall.new(name, Instrumentation.series)
end
- @methods[name].measure(&block)
+ method
end
def increment(name, value)
@@ -115,14 +108,6 @@ module Gitlab
Metrics.submit_metrics(submit_hashes)
end
-
- def sidekiq?
- Sidekiq.server?
- end
-
- def series_prefix
- sidekiq? ? 'sidekiq_' : 'rails_'
- end
end
end
end
diff --git a/lib/gitlab/request_profiler.rb b/lib/gitlab/request_profiler.rb
new file mode 100644
index 00000000000..8130e55351e
--- /dev/null
+++ b/lib/gitlab/request_profiler.rb
@@ -0,0 +1,19 @@
+require 'fileutils'
+
+module Gitlab
+ module RequestProfiler
+ PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles"
+
+ def profile_token
+ Rails.cache.fetch('profile-token') do
+ Devise.friendly_token
+ end
+ end
+ module_function :profile_token
+
+ def remove_all_profiles
+ FileUtils.rm_rf(PROFILES_DIR)
+ end
+ module_function :remove_all_profiles
+ end
+end
diff --git a/lib/gitlab/request_profiler/middleware.rb b/lib/gitlab/request_profiler/middleware.rb
new file mode 100644
index 00000000000..8da8b754975
--- /dev/null
+++ b/lib/gitlab/request_profiler/middleware.rb
@@ -0,0 +1,47 @@
+require 'ruby-prof'
+
+module Gitlab
+ module RequestProfiler
+ class Middleware
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ if profile?(env)
+ call_with_profiling(env)
+ else
+ @app.call(env)
+ end
+ end
+
+ def profile?(env)
+ header_token = env['HTTP_X_PROFILE_TOKEN']
+ return unless header_token.present?
+
+ profile_token = RequestProfiler.profile_token
+ return unless profile_token.present?
+
+ header_token == profile_token
+ end
+
+ def call_with_profiling(env)
+ ret = nil
+ result = RubyProf::Profile.profile do
+ ret = @app.call(env)
+ end
+
+ printer = RubyProf::CallStackPrinter.new(result)
+ file_name = "#{env['PATH_INFO'].tr('/', '|')}_#{Time.current.to_i}.html"
+ file_path = "#{PROFILES_DIR}/#{file_name}"
+
+ FileUtils.mkdir_p(PROFILES_DIR)
+ File.open(file_path, 'wb') do |file|
+ printer.print(file)
+ end
+
+ ret
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/request_profiler/profile.rb b/lib/gitlab/request_profiler/profile.rb
new file mode 100644
index 00000000000..f89d56903ef
--- /dev/null
+++ b/lib/gitlab/request_profiler/profile.rb
@@ -0,0 +1,43 @@
+module Gitlab
+ module RequestProfiler
+ class Profile
+ attr_reader :name, :time, :request_path
+
+ alias_method :to_param, :name
+
+ def self.all
+ Dir["#{PROFILES_DIR}/*.html"].map do |path|
+ new(File.basename(path))
+ end
+ end
+
+ def self.find(name)
+ name_dup = name.dup
+ name_dup << '.html' unless name.end_with?('.html')
+
+ file_path = "#{PROFILES_DIR}/#{name_dup}"
+ return unless File.exist?(file_path)
+
+ new(name_dup)
+ end
+
+ def initialize(name)
+ @name = name
+
+ set_attributes
+ end
+
+ def content
+ File.read("#{PROFILES_DIR}/#{name}")
+ end
+
+ private
+
+ def set_attributes
+ _, path, timestamp = name.split(/(.*)_(\d+)\.html$/)
+ @request_path = path.tr('|', '/')
+ @time = Time.at(timestamp.to_i).utc
+ end
+ end
+ end
+end
diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake
index 21c0e5f1d41..d3dcbd2c29b 100644
--- a/lib/tasks/test.rake
+++ b/lib/tasks/test.rake
@@ -7,5 +7,5 @@ end
unless Rails.env.production?
desc "GitLab | Run all tests on CI with simplecov"
- task test_ci: [:rubocop, :brakeman, 'teaspoon', :spinach, :spec]
+ task test_ci: [:rubocop, :brakeman, :teaspoon, :spinach, :spec]
end
diff --git a/public/404.html b/public/404.html
index 4862770cc2a..92b7f4da0b9 100644
--- a/public/404.html
+++ b/public/404.html
@@ -1,55 +1,65 @@
<!DOCTYPE html>
<html>
<head>
+ <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>The page you're looking for could not be found (404)</title>
<style>
- body {
- color: #666;
- text-align: center;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin: 0;
- width: 800px;
- margin: auto;
- font-size: 14px;
- }
+ body {
+ color: #666;
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: auto;
+ font-size: 14px;
+ }
- h1 {
- font-size: 56px;
- line-height: 100px;
- font-weight: normal;
- color: #456;
- }
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
- h2 {
- font-size: 24px;
- color: #666;
- line-height: 1.5em;
- }
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
- h3 {
- color: #456;
- font-size: 20px;
- font-weight: normal;
- line-height: 28px;
- }
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
- hr {
- margin: 18px 0;
- border: 0;
- border-top: 1px solid #EEE;
- border-bottom: 1px solid white;
- }
+ hr {
+ max-width: 800px;
+ margin: 18px auto;
+ border: 0;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid white;
+ }
+
+ img {
+ max-width: 40vw;
+ }
+
+ .container {
+ margin: auto 20px;
+ }
</style>
</head>
<body>
<h1>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
404
</h1>
- <h3>The page you're looking for could not be found.</h3>
- <hr/>
- <p>Make sure the address is correct and that the page hasn't moved.</p>
- <p>Please contact your GitLab administrator if you think this is a mistake.</p>
+ <div class="container">
+ <h3>The page you're looking for could not be found.</h3>
+ <hr />
+ <p>Make sure the address is correct and that the page hasn't moved.</p>
+ <p>Please contact your GitLab administrator if you think this is a mistake.</p>
+ </div>
</body>
</html>
diff --git a/public/422.html b/public/422.html
index 055b0bde165..f625f8a33b7 100644
--- a/public/422.html
+++ b/public/422.html
@@ -1,55 +1,65 @@
<!DOCTYPE html>
<html>
<head>
+ <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>The change you requested was rejected (422)</title>
<style>
body {
color: #666;
- text-align: center;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin: 0;
- width: 800px;
- margin: auto;
- font-size: 14px;
- }
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: auto;
+ font-size: 14px;
+ }
- h1 {
- font-size: 56px;
- line-height: 100px;
- font-weight: normal;
- color: #456;
- }
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
- h2 {
- font-size: 24px;
- color: #666;
- line-height: 1.5em;
- }
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
+
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
+
+ hr {
+ max-width: 800px;
+ margin: 18px auto;
+ border: 0;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid white;
+ }
- h3 {
- color: #456;
- font-size: 20px;
- font-weight: normal;
- line-height: 28px;
- }
+ img {
+ max-width: 40vw;
+ }
- hr {
- margin: 18px 0;
- border: 0;
- border-top: 1px solid #EEE;
- border-bottom: 1px solid white;
- }
- </style>
+ .container {
+ margin: auto 20px;
+ }
+ </style>
</head>
<body>
<h1>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
422
</h1>
- <h3>The change you requested was rejected.</h3>
- <hr />
- <p>Make sure you have access to the thing you tried to change.</p>
- <p>Please contact your GitLab administrator if you think this is a mistake.</p>
+ <div class="container">
+ <h3>The change you requested was rejected.</h3>
+ <hr />
+ <p>Make sure you have access to the thing you tried to change.</p>
+ <p>Please contact your GitLab administrator if you think this is a mistake.</p>
+ </div>
</body>
</html>
diff --git a/public/500.html b/public/500.html
index 3d59d1392f5..d76c66ba92a 100644
--- a/public/500.html
+++ b/public/500.html
@@ -1,54 +1,65 @@
<!DOCTYPE html>
<html>
<head>
+ <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>Something went wrong (500)</title>
<style>
- body {
- color: #666;
- text-align: center;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin: 0;
- width: 800px;
- margin: auto;
- font-size: 14px;
- }
+ body {
+ color: #666;
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: auto;
+ font-size: 14px;
+ }
- h1 {
- font-size: 56px;
- line-height: 100px;
- font-weight: normal;
- color: #456;
- }
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
- h2 {
- font-size: 24px;
- color: #666;
- line-height: 1.5em;
- }
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
- h3 {
- color: #456;
- font-size: 20px;
- font-weight: normal;
- line-height: 28px;
- }
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
- hr {
- margin: 18px 0;
+ hr {
+ max-width: 800px;
+ margin: 18px auto;
border: 0;
border-top: 1px solid #EEE;
border-bottom: 1px solid white;
}
+
+ img {
+ max-width: 40vw;
+ }
+
+ .container {
+ margin: auto 20px;
+ }
</style>
</head>
+
<body>
<h1>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
500
</h1>
- <h3>Whoops, something went wrong on our end.</h3>
- <hr/>
- <p>Try refreshing the page, or going back and attempting the action again.</p>
- <p>Please contact your GitLab administrator if this problem persists.</p>
+ <div class="container">
+ <h3>Whoops, something went wrong on our end.</h3>
+ <hr />
+ <p>Try refreshing the page, or going back and attempting the action again.</p>
+ <p>Please contact your GitLab administrator if this problem persists.</p>
+ </div>
</body>
</html>
diff --git a/public/502.html b/public/502.html
index 67dfd8a2743..1a3c7efc769 100644
--- a/public/502.html
+++ b/public/502.html
@@ -1,14 +1,13 @@
<!DOCTYPE html>
<html>
<head>
+ <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>GitLab is not responding (502)</title>
<style>
body {
color: #666;
text-align: center;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin: 0;
- width: 800px;
margin: auto;
font-size: 14px;
}
@@ -34,21 +33,33 @@
}
hr {
- margin: 18px 0;
+ max-width: 800px;
+ margin: 18px auto;
border: 0;
border-top: 1px solid #EEE;
border-bottom: 1px solid white;
}
+
+ img {
+ max-width: 40vw;
+ }
+
+ .container {
+ margin: auto 20px;
+ }
</style>
</head>
+
<body>
<h1>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
502
</h1>
- <h3>Whoops, GitLab is taking too much time to respond.</h3>
- <hr/>
- <p>Try refreshing the page, or going back and attempting the action again.</p>
- <p>Please contact your GitLab administrator if this problem persists.</p>
+ <div class="container">
+ <h3>Whoops, GitLab is taking too much time to respond.</h3>
+ <hr />
+ <p>Try refreshing the page, or going back and attempting the action again.</p>
+ <p>Please contact your GitLab administrator if this problem persists.</p>
+ </div>
</body>
</html>
diff --git a/public/503.html b/public/503.html
index 6ab1185658d..c1c4e3ffdb8 100644
--- a/public/503.html
+++ b/public/503.html
@@ -1,14 +1,13 @@
<!DOCTYPE html>
<html>
<head>
+ <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>GitLab is not responding (503)</title>
<style>
body {
color: #666;
text-align: center;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin: 0;
- width: 800px;
margin: auto;
font-size: 14px;
}
@@ -34,21 +33,33 @@
}
hr {
- margin: 18px 0;
+ max-width: 800px;
+ margin: 18px auto;
border: 0;
border-top: 1px solid #EEE;
border-bottom: 1px solid white;
}
+
+ img {
+ max-width: 40vw;
+ }
+
+ .container {
+ margin: auto 20px;
+ }
</style>
</head>
+
<body>
<h1>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo"/><br />
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
503
</h1>
- <h3>Whoops, GitLab is currently unavailable.</h3>
- <hr/>
- <p>Try refreshing the page, or going back and attempting the action again.</p>
- <p>Please contact your GitLab administrator if this problem persists.</p>
+ <div class="container">
+ <h3>Whoops, GitLab is currently unavailable.</h3>
+ <hr />
+ <p>Try refreshing the page, or going back and attempting the action again.</p>
+ <p>Please contact your GitLab administrator if this problem persists.</p>
+ </div>
</body>
</html>
diff --git a/public/deploy.html b/public/deploy.html
index 48976dacf41..142472b6c35 100644
--- a/public/deploy.html
+++ b/public/deploy.html
@@ -1,54 +1,64 @@
<!DOCTYPE html>
<html>
- <head>
- <title>Deploy in progress</title>
- <style>
- body {
- color: #666;
- text-align: center;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin: 0;
- width: 800px;
- margin: auto;
- font-size: 14px;
- }
+<head>
+ <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
+ <title>Deploy in progress</title>
+ <style>
+ body {
+ color: #666;
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: auto;
+ font-size: 14px;
+ }
- h1 {
- font-size: 56px;
- line-height: 100px;
- font-weight: normal;
- color: #456;
- }
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
- h2 {
- font-size: 24px;
- color: #666;
- line-height: 1.5em;
- }
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
- h3 {
- color: #456;
- font-size: 20px;
- font-weight: normal;
- line-height: 28px;
- }
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
- hr {
- margin: 18px 0;
- border: 0;
- border-top: 1px solid #EEE;
- border-bottom: 1px solid white;
- }
- </style>
- </head>
+ hr {
+ max-width: 800px;
+ margin: 18px auto;
+ border: 0;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid white;
+ }
- <body>
- <h1>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
- Deploy in progress
- </h1>
+ img {
+ max-width: 40vw;
+ }
+
+ .container {
+ margin: auto 20px;
+ }
+ </style>
+</head>
+
+<body>
+ <h1>
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
+ Deploy in progress
+ </h1>
+ <div class="container">
<h3>Please try again in a few minutes.</h3>
- <hr/>
+ <hr />
<p>Please contact your GitLab administrator if this problem persists.</p>
- </body>
+ </div>
+</body>
</html>
diff --git a/scripts/merge-simplecov b/scripts/merge-simplecov
new file mode 100755
index 00000000000..65f93f8830b
--- /dev/null
+++ b/scripts/merge-simplecov
@@ -0,0 +1,30 @@
+#!/usr/bin/env ruby
+
+require_relative '../spec/simplecov_env'
+SimpleCovEnv.configure_profile
+
+module SimpleCov
+ module ResultMerger
+ class << self
+ def resultset_files
+ Dir.glob(File.join(SimpleCov.coverage_path, '*', '.resultset.json'))
+ end
+
+ def resultset_hashes
+ resultset_files.map do |path|
+ begin
+ JSON.parse(File.read(path))
+ rescue
+ {}
+ end
+ end
+ end
+
+ def resultset
+ resultset_hashes.reduce({}, :merge)
+ end
+ end
+ end
+end
+
+SimpleCov::ResultMerger.merged_result.format!
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 60c654f622d..ed0b7f9e240 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -163,4 +163,17 @@ describe AutocompleteController do
expect(body.collect { |u| u['id'] }).not_to include(99999)
end
end
+
+ context 'skip_users parameter included' do
+ before { sign_in(user) }
+
+ it 'skips the user IDs passed' do
+ get(:users, skip_users: [user, user2].map(&:id))
+
+ other_user_ids = [non_member, project.owner, project.creator].map(&:id)
+ response_user_ids = JSON.parse(response.body).map { |user| user['id'] }
+
+ expect(response_user_ids).to contain_exactly(*other_user_ids)
+ end
+ end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 7cf09fa4a4a..77f65057f71 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -243,6 +243,37 @@ describe Projects::IssuesController do
end
end
+ describe 'POST #create' do
+ context 'Akismet is enabled' do
+ before do
+ allow_any_instance_of(Gitlab::AkismetHelper).to receive(:check_for_spam?).and_return(true)
+ allow_any_instance_of(Gitlab::AkismetHelper).to receive(:is_spam?).and_return(true)
+ end
+
+ def post_spam_issue
+ sign_in(user)
+ spam_project = create(:empty_project, :public)
+ post :create, {
+ namespace_id: spam_project.namespace.to_param,
+ project_id: spam_project.to_param,
+ issue: { title: 'Spam Title', description: 'Spam lives here' }
+ }
+ end
+
+ it 'rejects an issue recognized as spam' do
+ expect{ post_spam_issue }.not_to change(Issue, :count)
+ expect(response).to render_template(:new)
+ end
+
+ it 'creates a spam log' do
+ post_spam_issue
+ spam_logs = SpamLog.all
+ expect(spam_logs.count).to eq(1)
+ expect(spam_logs[0].title).to eq('Spam Title')
+ end
+ end
+ end
+
describe "DELETE #destroy" do
context "when the user is a developer" do
before { sign_in(user) }
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
new file mode 100644
index 00000000000..a6995145cc1
--- /dev/null
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Projects::TagsController do
+ let(:project) { create(:project, :public) }
+ let!(:release) { create(:release, project: project) }
+ let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') }
+
+ describe 'GET index' do
+ before { get :index, namespace_id: project.namespace.to_param, project_id: project.to_param }
+
+ it 'returns the tags for the page' do
+ expect(assigns(:tags).map(&:name)).to eq(['v1.1.0', 'v1.0.0'])
+ end
+
+ it 'returns releases matching those tags' do
+ expect(assigns(:releases)).to include(release)
+ expect(assigns(:releases)).not_to include(invalid_release)
+ end
+ end
+end
diff --git a/spec/factories/ci/trigger_requests.rb b/spec/factories/ci/trigger_requests.rb
index 6d47d05f8ad..b8d8fab0e0b 100644
--- a/spec/factories/ci/trigger_requests.rb
+++ b/spec/factories/ci/trigger_requests.rb
@@ -5,7 +5,8 @@ FactoryGirl.define do
variables do
{
- TRIGGER_KEY: 'TRIGGER_VALUE'
+ TRIGGER_KEY_1: 'TRIGGER_VALUE_1',
+ TRIGGER_KEY_2: 'TRIGGER_VALUE_2'
}
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index d51c9abea19..93dcb2ec3fc 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -524,6 +524,35 @@ describe 'Issues', feature: true do
end
end
+ describe 'new issue by email' do
+ shared_examples 'show the email in the modal' do
+ before do
+ stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
+
+ visit namespace_project_issues_path(project.namespace, project)
+ click_button('Email a new issue')
+ end
+
+ it 'click the button to show modal for the new email' do
+ page.within '#issue-email-modal' do
+ email = project.new_issue_address(@user)
+
+ expect(page).to have_selector("input[value='#{email}']")
+ end
+ end
+ end
+
+ context 'with existing issues' do
+ let!(:issue) { create(:issue, project: project, author: @user) }
+
+ it_behaves_like 'show the email in the modal'
+ end
+
+ context 'without existing issues' do
+ it_behaves_like 'show the email in the modal'
+ end
+ end
+
describe 'due date' do
context 'update due on issue#show', js: true do
let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
diff --git a/spec/features/projects/branches_spec.rb~HEAD b/spec/features/projects/branches_spec.rb~HEAD
deleted file mode 100644
index 79abba21854..00000000000
--- a/spec/features/projects/branches_spec.rb~HEAD
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-
-describe 'Branches', feature: true do
- let(:project) { create(:project) }
- let(:repository) { project.repository }
-
- before do
- login_as :user
- project.team << [@user, :developer]
- end
-
- describe 'Initial branches page' do
- it 'shows all the branches' do
- visit namespace_project_branches_path(project.namespace, project)
-
- repository.branches { |branch| expect(page).to have_content("#{branch.name}") }
- expect(page).to have_content("Protected branches can be managed in project settings")
- end
- end
-
- describe 'Find branches' do
- it 'shows filtered branches', js: true do
- visit namespace_project_branches_path(project.namespace, project, project.id)
-
- fill_in 'branch-search', with: 'fix'
- find('#branch-search').native.send_keys(:enter)
-
- expect(page).to have_content('fix')
- expect(find('.all-branches')).to have_selector('li', count: 1)
- end
- end
-end
diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb
new file mode 100644
index 00000000000..3de25d7af7d
--- /dev/null
+++ b/spec/features/projects/project_settings_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe 'Edit Project Settings', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') }
+
+ before do
+ login_as(user)
+ project.team << [user, :master]
+ end
+
+ describe 'Project settings', js: true do
+ it 'shows errors for invalid project name' do
+ visit edit_namespace_project_path(project.namespace, project)
+
+ fill_in 'project_name_edit', with: 'foo&bar'
+
+ click_button 'Save changes'
+
+ expect(page).to have_field 'project_name_edit', with: 'foo&bar'
+ expect(page).to have_content "Name can contain only letters, digits, '_', '.', dash and space. It must start with letter, digit or '_'."
+ expect(page).to have_button 'Save changes'
+ end
+ end
+
+ describe 'Rename repository' do
+ it 'shows errors for invalid project path/name' do
+ visit edit_namespace_project_path(project.namespace, project)
+
+ fill_in 'Project name', with: 'foo&bar'
+ fill_in 'Path', with: 'foo&bar'
+
+ click_button 'Rename project'
+
+ expect(page).to have_field 'Project name', with: 'foo&bar'
+ expect(page).to have_field 'Path', with: 'foo&bar'
+ expect(page).to have_content "Name can contain only letters, digits, '_', '.', dash and space. It must start with letter, digit or '_'."
+ expect(page).to have_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"
+ end
+ end
+end
diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb
index dd85203a038..6ea9a3a3ec5 100644
--- a/spec/finders/branches_finder_spec.rb
+++ b/spec/finders/branches_finder_spec.rb
@@ -16,7 +16,7 @@ describe BranchesFinder do
end
it 'sorts by recently_updated' do
- branches_finder = described_class.new(repository, { sort: 'recently_updated' })
+ branches_finder = described_class.new(repository, { sort: 'updated_desc' })
result = branches_finder.execute
@@ -24,7 +24,7 @@ describe BranchesFinder do
end
it 'sorts by last_updated' do
- branches_finder = described_class.new(repository, { sort: 'last_updated' })
+ branches_finder = described_class.new(repository, { sort: 'updated_asc' })
result = branches_finder.execute
@@ -53,7 +53,7 @@ describe BranchesFinder do
context 'filter and sort' do
it 'filters branches by name and sorts by recently_updated' do
- params = { sort: 'recently_updated', search: 'feature' }
+ params = { sort: 'updated_desc', search: 'feature' }
branches_finder = described_class.new(repository, params)
result = branches_finder.execute
@@ -63,7 +63,7 @@ describe BranchesFinder do
end
it 'filters branches by name and sorts by last_updated' do
- params = { sort: 'last_updated', search: 'feature' }
+ params = { sort: 'updated_asc', search: 'feature' }
branches_finder = described_class.new(repository, params)
result = branches_finder.execute
diff --git a/spec/fixtures/emails/valid_new_issue.eml b/spec/fixtures/emails/valid_new_issue.eml
new file mode 100644
index 00000000000..3cf53a656a5
--- /dev/null
+++ b/spec/fixtures/emails/valid_new_issue.eml
@@ -0,0 +1,23 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: New Issue by email
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+The reply by email functionality should be extended to allow creating a new issue by email.
+
+* Allow an admin to specify which project the issue should be created under by checking the sender domain.
+* Possibly allow the use of regular expression matches within the subject/body to specify which project the issue should be created under.
diff --git a/spec/fixtures/emails/valid_new_issue_empty.eml b/spec/fixtures/emails/valid_new_issue_empty.eml
new file mode 100644
index 00000000000..fc1d52a3f42
--- /dev/null
+++ b/spec/fixtures/emails/valid_new_issue_empty.eml
@@ -0,0 +1,18 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: New Issue by email
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
diff --git a/spec/fixtures/emails/wrong_authentication_token.eml b/spec/fixtures/emails/wrong_authentication_token.eml
new file mode 100644
index 00000000000..0994c2f7775
--- /dev/null
+++ b/spec/fixtures/emails/wrong_authentication_token.eml
@@ -0,0 +1,18 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq/gitlabhq+bad_token@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: New Issue by email
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
diff --git a/spec/fixtures/emails/wrong_reply_key.eml b/spec/fixtures/emails/wrong_mail_key.eml
index 491e078fb5b..491e078fb5b 100644
--- a/spec/fixtures/emails/wrong_reply_key.eml
+++ b/spec/fixtures/emails/wrong_mail_key.eml
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index 08a93503258..af371248ae9 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -1,37 +1,30 @@
require "spec_helper"
describe NotesHelper do
- describe "#notes_max_access_for_users" do
- let(:owner) { create(:owner) }
- let(:group) { create(:group) }
- let(:project) { create(:empty_project, namespace: group) }
- let(:master) { create(:user) }
- let(:reporter) { create(:user) }
- let(:guest) { create(:user) }
-
- let(:owner_note) { create(:note, author: owner, project: project) }
- let(:master_note) { create(:note, author: master, project: project) }
- let(:reporter_note) { create(:note, author: reporter, project: project) }
- let!(:notes) { [owner_note, master_note, reporter_note] }
-
- before do
- group.add_owner(owner)
- project.team << [master, :master]
- project.team << [reporter, :reporter]
- project.team << [guest, :guest]
- end
+ let(:owner) { create(:owner) }
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, namespace: group) }
+ let(:master) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
- it 'return human access levels' do
- original_method = project.team.method(:human_max_access)
- expect_any_instance_of(ProjectTeam).to receive(:human_max_access).exactly(3).times do |*args|
- original_method.call(args[1])
- end
+ let(:owner_note) { create(:note, author: owner, project: project) }
+ let(:master_note) { create(:note, author: master, project: project) }
+ let(:reporter_note) { create(:note, author: reporter, project: project) }
+ let!(:notes) { [owner_note, master_note, reporter_note] }
+ before do
+ group.add_owner(owner)
+ project.team << [master, :master]
+ project.team << [reporter, :reporter]
+ project.team << [guest, :guest]
+ end
+
+ describe "#notes_max_access_for_users" do
+ it 'return human access levels' do
expect(helper.note_max_access_for_user(owner_note)).to eq('Owner')
expect(helper.note_max_access_for_user(master_note)).to eq('Master')
expect(helper.note_max_access_for_user(reporter_note)).to eq('Reporter')
- # Call it again to ensure value is cached
- expect(helper.note_max_access_for_user(owner_note)).to eq('Owner')
end
it 'handles access in different projects' do
@@ -43,4 +36,16 @@ describe NotesHelper do
expect(helper.note_max_access_for_user(other_note)).to eq('Reporter')
end
end
+
+ describe '#preload_max_access_for_authors' do
+ it 'loads multiple users' do
+ expected_access = {
+ owner.id => Gitlab::Access::OWNER,
+ master.id => Gitlab::Access::MASTER,
+ reporter.id => Gitlab::Access::REPORTER
+ }
+
+ expect(helper.preload_max_access_for_authors(notes, project)).to eq(expected_access)
+ end
+ end
end
diff --git a/spec/lib/gitlab/akismet_helper_spec.rb b/spec/lib/gitlab/akismet_helper_spec.rb
index 88a71528867..b08396da4d2 100644
--- a/spec/lib/gitlab/akismet_helper_spec.rb
+++ b/spec/lib/gitlab/akismet_helper_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::AkismetHelper, type: :helper do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :public) }
let(:user) { create(:user) }
before do
@@ -11,13 +11,13 @@ describe Gitlab::AkismetHelper, type: :helper do
end
describe '#check_for_spam?' do
- it 'returns true for non-member' do
- expect(helper.check_for_spam?(project, user)).to eq(true)
+ it 'returns true for public project' do
+ expect(helper.check_for_spam?(project)).to eq(true)
end
- it 'returns false for member' do
- project.team << [user, :guest]
- expect(helper.check_for_spam?(project, user)).to eq(false)
+ it 'returns false for private project' do
+ project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
+ expect(helper.check_for_spam?(project)).to eq(false)
end
end
diff --git a/spec/lib/gitlab/email/email_shared_blocks.rb b/spec/lib/gitlab/email/email_shared_blocks.rb
new file mode 100644
index 00000000000..19298e261e3
--- /dev/null
+++ b/spec/lib/gitlab/email/email_shared_blocks.rb
@@ -0,0 +1,41 @@
+require 'gitlab/email/receiver'
+
+shared_context :email_shared_context do
+ let(:mail_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" }
+ let(:receiver) { Gitlab::Email::Receiver.new(email_raw) }
+ let(:markdown) { "![image](uploads/image.png)" }
+
+ def setup_attachment
+ allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return(
+ [
+ {
+ url: "uploads/image.png",
+ alt: "image",
+ markdown: markdown
+ }
+ ]
+ )
+ end
+end
+
+shared_examples :email_shared_examples do
+ context "when the user could not be found" do
+ before do
+ user.destroy
+ end
+
+ it "raises a UserNotFoundError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError)
+ end
+ end
+
+ context "when the user is not authorized to the project" do
+ before do
+ project.update_attribute(:visibility_level, Project::PRIVATE)
+ end
+
+ it "raises a ProjectNotFound" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
new file mode 100644
index 00000000000..e1153154778
--- /dev/null
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+require_relative '../email_shared_blocks'
+
+describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do
+ include_context :email_shared_context
+ it_behaves_like :email_shared_examples
+
+ before do
+ stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo")
+ stub_config_setting(host: 'localhost')
+ end
+
+ let(:email_raw) { fixture_file('emails/valid_new_issue.eml') }
+ let(:namespace) { create(:namespace, path: 'gitlabhq') }
+
+ let!(:project) { create(:project, :public, namespace: namespace) }
+ let!(:user) do
+ create(
+ :user,
+ email: 'jake@adventuretime.ooo',
+ authentication_token: 'auth_token'
+ )
+ end
+
+ context "when everything is fine" do
+ it "creates a new issue" do
+ setup_attachment
+
+ expect { receiver.execute }.to change { project.issues.count }.by(1)
+ issue = project.issues.last
+
+ expect(issue.author).to eq(user)
+ expect(issue.title).to eq('New Issue by email')
+ expect(issue.description).to include('reply by email')
+ expect(issue.description).to include(markdown)
+ end
+
+ context "when the reply is blank" do
+ let(:email_raw) { fixture_file("emails/valid_new_issue_empty.eml") }
+
+ it "creates a new issue" do
+ expect { receiver.execute }.to change { project.issues.count }.by(1)
+ issue = project.issues.last
+
+ expect(issue.author).to eq(user)
+ expect(issue.title).to eq('New Issue by email')
+ expect(issue.description).to eq('')
+ end
+ end
+ end
+
+ context "something is wrong" do
+ context "when the issue could not be saved" do
+ before do
+ allow_any_instance_of(Issue).to receive(:persisted?).and_return(false)
+ end
+
+ it "raises an InvalidIssueError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidIssueError)
+ end
+ end
+
+ context "when we can't find the authentication_token" do
+ let(:email_raw) { fixture_file("emails/wrong_authentication_token.eml") }
+
+ it "raises an UserNotFoundError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError)
+ end
+ end
+
+ context "when project is private" do
+ let(:project) { create(:project, :private, namespace: namespace) }
+
+ it "raises a ProjectNotFound if the user is not a member" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
new file mode 100644
index 00000000000..a2119b0dadf
--- /dev/null
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -0,0 +1,116 @@
+require 'spec_helper'
+require_relative '../email_shared_blocks'
+
+describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do
+ include_context :email_shared_context
+ it_behaves_like :email_shared_examples
+
+ before do
+ stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo")
+ stub_config_setting(host: 'localhost')
+ end
+
+ let(:email_raw) { fixture_file('emails/valid_reply.eml') }
+ let(:project) { create(:project, :public) }
+ let(:noteable) { create(:issue, project: project) }
+ let(:user) { create(:user) }
+
+ let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) }
+
+ context "when the recipient address doesn't include a mail key" do
+ let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "") }
+
+ it "raises a UnknownIncomingEmail" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
+ end
+ end
+
+ context "when no sent notification for the mail key could be found" do
+ let(:email_raw) { fixture_file('emails/wrong_mail_key.eml') }
+
+ it "raises a SentNotificationNotFoundError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError)
+ end
+ end
+
+ context "when the email was auto generated" do
+ let!(:mail_key) { '636ca428858779856c226bb145ef4fad' }
+ let!(:email_raw) { fixture_file("emails/auto_reply.eml") }
+
+ it "raises an AutoGeneratedEmailError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::AutoGeneratedEmailError)
+ end
+ end
+
+ context "when the noteable could not be found" do
+ before do
+ noteable.destroy
+ end
+
+ it "raises a NoteableNotFoundError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::NoteableNotFoundError)
+ end
+ end
+
+ context "when the note could not be saved" do
+ before do
+ allow_any_instance_of(Note).to receive(:persisted?).and_return(false)
+ end
+
+ it "raises an InvalidNoteError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError)
+ end
+ end
+
+ context "when the reply is blank" do
+ let!(:email_raw) { fixture_file("emails/no_content_reply.eml") }
+
+ it "raises an EmptyEmailError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError)
+ end
+ end
+
+ context "when everything is fine" do
+ before do
+ setup_attachment
+ end
+
+ it "creates a comment" do
+ expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+ note = noteable.notes.last
+
+ expect(note.author).to eq(sent_notification.recipient)
+ expect(note.note).to include("I could not disagree more.")
+ end
+
+ it "adds all attachments" do
+ receiver.execute
+
+ note = noteable.notes.last
+
+ expect(note.note).to include(markdown)
+ end
+
+ context 'when sub-addressing is not supported' do
+ before do
+ stub_incoming_email_setting(enabled: true, address: nil)
+ end
+
+ shared_examples 'an email that contains a mail key' do |header|
+ it "fetches the mail key from the #{header} header and creates a comment" do
+ expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+ note = noteable.notes.last
+
+ expect(note.author).to eq(sent_notification.recipient)
+ expect(note.note).to include('I could not disagree more.')
+ end
+ end
+
+ context 'mail key is in the References header' do
+ let(:email_raw) { fixture_file('emails/reply_without_subaddressing_and_key_inside_references.eml') }
+
+ it_behaves_like 'an email that contains a mail key', 'References'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index 84d2584a791..2a86b427806 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -1,34 +1,14 @@
-require "spec_helper"
+require 'spec_helper'
+require_relative 'email_shared_blocks'
describe Gitlab::Email::Receiver, lib: true do
- before do
- stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo")
- stub_config_setting(host: 'localhost')
- end
-
- let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" }
- let(:email_raw) { fixture_file('emails/valid_reply.eml') }
-
- let(:project) { create(:project, :public) }
- let(:noteable) { create(:issue, project: project) }
- let(:user) { create(:user) }
- let!(:sent_notification) { SentNotification.record(noteable, user.id, reply_key) }
-
- let(:receiver) { described_class.new(email_raw) }
-
- context "when the recipient address doesn't include a reply key" do
- let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(reply_key, "") }
-
- it "raises a SentNotificationNotFoundError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError)
- end
- end
+ include_context :email_shared_context
- context "when no sent notificiation for the reply key could be found" do
- let(:email_raw) { fixture_file('emails/wrong_reply_key.eml') }
+ context "when we cannot find a capable handler" do
+ let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "!!!") }
- it "raises a SentNotificationNotFoundError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError)
+ it "raises a UnknownIncomingEmail" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
end
end
@@ -36,128 +16,7 @@ describe Gitlab::Email::Receiver, lib: true do
let(:email_raw) { "" }
it "raises an EmptyEmailError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError)
- end
- end
-
- context "when the email was auto generated" do
- let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
- let!(:email_raw) { fixture_file("emails/auto_reply.eml") }
-
- it "raises an AutoGeneratedEmailError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::AutoGeneratedEmailError)
- end
- end
-
- context "when the user could not be found" do
- before do
- user.destroy
- end
-
- it "raises a UserNotFoundError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotFoundError)
- end
- end
-
- context "when the user has been blocked" do
- before do
- user.block
- end
-
- it "raises a UserBlockedError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserBlockedError)
- end
- end
-
- context "when the user is not authorized to create a note" do
- before do
- project.update_attribute(:visibility_level, Project::PRIVATE)
- end
-
- it "raises a UserNotAuthorizedError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotAuthorizedError)
- end
- end
-
- context "when the noteable could not be found" do
- before do
- noteable.destroy
- end
-
- it "raises a NoteableNotFoundError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::NoteableNotFoundError)
- end
- end
-
- context "when the reply is blank" do
- let!(:email_raw) { fixture_file("emails/no_content_reply.eml") }
-
- it "raises an EmptyEmailError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError)
- end
- end
-
- context "when the note could not be saved" do
- before do
- allow_any_instance_of(Note).to receive(:persisted?).and_return(false)
- end
-
- it "raises an InvalidNoteError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::InvalidNoteError)
- end
- end
-
- context "when everything is fine" do
- let(:markdown) { "![image](uploads/image.png)" }
-
- before do
- allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return(
- [
- {
- url: "uploads/image.png",
- alt: "image",
- markdown: markdown
- }
- ]
- )
- end
-
- it "creates a comment" do
- expect { receiver.execute }.to change { noteable.notes.count }.by(1)
- note = noteable.notes.last
-
- expect(note.author).to eq(sent_notification.recipient)
- expect(note.note).to include("I could not disagree more.")
- end
-
- it "adds all attachments" do
- receiver.execute
-
- note = noteable.notes.last
-
- expect(note.note).to include(markdown)
- end
-
- context 'when sub-addressing is not supported' do
- before do
- stub_incoming_email_setting(enabled: true, address: nil)
- end
-
- shared_examples 'an email that contains a reply key' do |header|
- it "fetches the reply key from the #{header} header and creates a comment" do
- expect { receiver.execute }.to change { noteable.notes.count }.by(1)
- note = noteable.notes.last
-
- expect(note.author).to eq(sent_notification.recipient)
- expect(note.note).to include('I could not disagree more.')
- end
- end
-
- context 'reply key is in the References header' do
- let(:email_raw) { fixture_file('emails/reply_without_subaddressing_and_key_inside_references.eml') }
-
- it_behaves_like 'an email that contains a reply key', 'References'
- end
+ expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError)
end
end
end
diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb
index afb3e26f8fb..1dcf2c0668b 100644
--- a/spec/lib/gitlab/incoming_email_spec.rb
+++ b/spec/lib/gitlab/incoming_email_spec.rb
@@ -43,9 +43,9 @@ describe Gitlab::IncomingEmail, lib: true do
end
end
- context 'self.key_from_fallback_reply_message_id' do
+ context 'self.key_from_fallback_message_id' do
it 'returns reply key' do
- expect(described_class.key_from_fallback_reply_message_id('reply-key@localhost')).to eq('key')
+ expect(described_class.key_from_fallback_message_id('reply-key@localhost')).to eq('key')
end
end
end
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index 8809b7e3f12..d88bcae41fb 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -39,6 +39,12 @@ describe Gitlab::Metrics::Instrumentation do
allow(@dummy).to receive(:name).and_return('Dummy')
end
+ describe '.series' do
+ it 'returns a String' do
+ expect(described_class.series).to be_an_instance_of(String)
+ end
+ end
+
describe '.configure' do
it 'yields self' do
described_class.configure do |c|
@@ -78,8 +84,7 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction).
and_return(transaction)
- expect(transaction).to receive(:measure_method).
- with('Dummy.foo')
+ expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure)
@dummy.foo
end
@@ -157,8 +162,7 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction).
and_return(transaction)
- expect(transaction).to receive(:measure_method).
- with('Dummy#bar')
+ expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure)
@dummy.new.bar
end
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index cf0e282c2fb..9e2ea89a712 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -28,20 +28,20 @@ describe Gitlab::Metrics::System do
end
describe '.cpu_time' do
- it 'returns a Float' do
- expect(described_class.cpu_time).to be_an_instance_of(Float)
+ it 'returns a Fixnum' do
+ expect(described_class.cpu_time).to be_an_instance_of(Fixnum)
end
end
describe '.real_time' do
- it 'returns a Float' do
- expect(described_class.real_time).to be_an_instance_of(Float)
+ it 'returns a Fixnum' do
+ expect(described_class.real_time).to be_an_instance_of(Fixnum)
end
end
describe '.monotonic_time' do
- it 'returns a Float' do
- expect(described_class.monotonic_time).to be_an_instance_of(Float)
+ it 'returns a Fixnum' do
+ expect(described_class.monotonic_time).to be_an_instance_of(Fixnum)
end
end
end
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index 3b1c67a2147..f1a191d9410 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -46,19 +46,11 @@ describe Gitlab::Metrics::Transaction do
end
end
- describe '#measure_method' do
- it 'adds a new method if it does not exist already' do
- transaction.measure_method('Foo#bar') { 'foo' }
+ describe '#method_call_for' do
+ it 'returns a MethodCall' do
+ method = transaction.method_call_for('Foo#bar')
- expect(transaction.methods['Foo#bar']).
- to be_an_instance_of(Gitlab::Metrics::MethodCall)
- end
-
- it 'adds timings to an existing method call' do
- transaction.measure_method('Foo#bar') { 'foo' }
- transaction.measure_method('Foo#bar') { 'foo' }
-
- expect(transaction.methods['Foo#bar'].call_count).to eq(2)
+ expect(method).to be_an_instance_of(Gitlab::Metrics::MethodCall)
end
end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 96f7eabbca6..84f9475a0f8 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -147,4 +147,10 @@ describe Gitlab::Metrics do
end
end
end
+
+ describe '#series_prefix' do
+ it 'returns a String' do
+ expect(described_class.series_prefix).to be_an_instance_of(String)
+ end
+ end
end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index 1acb5846fcf..cd5f40fe3d2 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -1,6 +1,62 @@
require 'spec_helper'
describe Ability, lib: true do
+ describe '.can_edit_note?' do
+ let(:project) { create(:empty_project) }
+ let!(:note) { create(:note_on_issue, project: project) }
+
+ context 'using an anonymous user' do
+ it 'returns false' do
+ expect(described_class.can_edit_note?(nil, note)).to be_falsy
+ end
+ end
+
+ context 'using a system note' do
+ it 'returns false' do
+ system_note = create(:note, system: true)
+ user = create(:user)
+
+ expect(described_class.can_edit_note?(user, system_note)).to be_falsy
+ end
+ end
+
+ context 'using users with different access levels' do
+ let(:user) { create(:user) }
+
+ it 'returns true for the author' do
+ expect(described_class.can_edit_note?(note.author, note)).to be_truthy
+ end
+
+ it 'returns false for a guest user' do
+ project.team << [user, :guest]
+
+ expect(described_class.can_edit_note?(user, note)).to be_falsy
+ end
+
+ it 'returns false for a developer' do
+ project.team << [user, :developer]
+
+ expect(described_class.can_edit_note?(user, note)).to be_falsy
+ end
+
+ it 'returns true for a master' do
+ project.team << [user, :master]
+
+ expect(described_class.can_edit_note?(user, note)).to be_truthy
+ end
+
+ it 'returns true for a group owner' do
+ group = create(:group)
+ project.project_group_links.create(
+ group: group,
+ group_access: Gitlab::Access::MASTER)
+ group.add_owner(user)
+
+ expect(described_class.can_edit_note?(user, note)).to be_truthy
+ end
+ end
+ end
+
describe '.users_that_can_read_project' do
context 'using a public project' do
it 'returns all the users' do
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 978ad9c52d5..dc88697199b 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -259,7 +259,7 @@ describe Ci::Build, models: true do
let(:trigger) { create(:ci_trigger, project: project) }
let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
let(:user_trigger_variable) do
- { key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false }
+ { key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false }
end
let(:predefined_trigger_variable) do
{ key: 'CI_BUILD_TRIGGERED', value: 'true', public: true }
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 40181a8b906..44cd3c08718 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -79,6 +79,18 @@ describe Member, models: true do
@accepted_request_member = project.requesters.find_by(user_id: accepted_request_user.id).tap { |m| m.accept_request }
end
+ describe '.access_for_user_ids' do
+ it 'returns the right access levels' do
+ users = [@owner_user.id, @master_user.id]
+ expected = {
+ @owner_user.id => Gitlab::Access::OWNER,
+ @master_user.id => Gitlab::Access::MASTER
+ }
+
+ expect(described_class.access_for_user_ids(users)).to eq(expected)
+ end
+ end
+
describe '.invite' do
it { expect(described_class.invite).not_to include @master }
it { expect(described_class.invite).to include @invited_member }
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 5f618322aab..62ae5f6cf74 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -340,18 +340,36 @@ describe HipchatService, models: true do
end
context "#message_options" do
- it "should be set to the defaults" do
- expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'yellow' })
+ it "is set to the defaults" do
+ expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'yellow' })
end
- it "should set notfiy to true" do
+ it "sets notify to true" do
allow(hipchat).to receive(:notify).and_return('1')
- expect(hipchat.send(:message_options)).to eq({ notify: true, color: 'yellow' })
+
+ expect(hipchat.__send__(:message_options)).to eq({ notify: true, color: 'yellow' })
end
- it "should set the color" do
+ it "sets the color" do
allow(hipchat).to receive(:color).and_return('red')
- expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'red' })
+
+ expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'red' })
+ end
+
+ context 'with a successful build' do
+ it 'uses the green color' do
+ build_data = { object_kind: 'build', commit: { status: 'success' } }
+
+ expect(hipchat.__send__(:message_options, build_data)).to eq({ notify: false, color: 'green' })
+ end
+ end
+
+ context 'with a failed build' do
+ it 'uses the red color' do
+ build_data = { object_kind: 'build', commit: { status: 'failed' } }
+
+ expect(hipchat.__send__(:message_options, build_data)).to eq({ notify: false, color: 'red' })
+ end
end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 9b017288488..72b8a4e25bd 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -245,6 +245,34 @@ describe Project, models: true do
end
end
+ describe "#new_issue_address" do
+ let(:project) { create(:empty_project, path: "somewhere") }
+ let(:user) { create(:user) }
+
+ context 'incoming email enabled' do
+ before do
+ stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
+ end
+
+ it 'returns the address to create a new issue' do
+ token = user.authentication_token
+ address = "p+#{project.namespace.path}/#{project.path}+#{token}@gl.ab"
+
+ expect(project.new_issue_address(user)).to eq(address)
+ end
+ end
+
+ context 'incoming email disabled' do
+ before do
+ stub_incoming_email_setting(enabled: false)
+ end
+
+ it 'returns nil' do
+ expect(project.new_issue_address(user)).to be_nil
+ end
+ end
+ end
+
describe 'last_activity methods' do
let(:project) { create(:project) }
let(:last_event) { double(created_at: Time.now) }
@@ -372,6 +400,24 @@ describe Project, models: true do
it { expect(@project.to_param).to eq('gitlabhq') }
end
+
+ context 'with invalid path' do
+ it 'returns previous path to keep project suitable for use in URLs when persisted' do
+ project = create(:empty_project, path: 'gitlab')
+ project.path = 'foo&bar'
+
+ expect(project).not_to be_valid
+ expect(project.to_param).to eq 'gitlab'
+ end
+
+ it 'returns current path when new record' do
+ project = build(:empty_project, path: 'gitlab')
+ project.path = 'foo&bar'
+
+ expect(project).not_to be_valid
+ expect(project.to_param).to eq 'foo&bar'
+ end
+ end
end
describe '#repository' do
@@ -1244,6 +1290,32 @@ describe Project, models: true do
end
end
+ describe '#add_import_job' do
+ context 'forked' do
+ let(:forked_project_link) { create(:forked_project_link) }
+ let(:forked_from_project) { forked_project_link.forked_from_project }
+ let(:project) { forked_project_link.forked_to_project }
+
+ it 'schedules a RepositoryForkWorker job' do
+ expect(RepositoryForkWorker).to receive(:perform_async).
+ with(project.id, forked_from_project.repository_storage_path,
+ forked_from_project.path_with_namespace, project.namespace.path)
+
+ project.add_import_job
+ end
+ end
+
+ context 'not forked' do
+ let(:project) { create(:project) }
+
+ it 'schedules a RepositoryImportWorker job' do
+ expect(RepositoryImportWorker).to receive(:perform_async).with(project.id)
+
+ project.add_import_job
+ end
+ end
+ end
+
describe '.where_paths_in' do
context 'without any paths' do
it 'returns an empty relation' do
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 9262aeb6ed8..1f42fbd3385 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -151,8 +151,8 @@ describe ProjectTeam, models: true do
it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::MASTER) }
it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
it { expect(project.team.max_member_access(guest.id)).to eq(Gitlab::Access::GUEST) }
- it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
- it { expect(project.team.max_member_access(requester.id)).to be_nil }
+ it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
+ it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
end
context 'when project is shared with group' do
@@ -168,14 +168,14 @@ describe ProjectTeam, models: true do
it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::DEVELOPER) }
it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
- it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
- it { expect(project.team.max_member_access(requester.id)).to be_nil }
+ it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
+ it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
context 'but share_with_group_lock is true' do
before { project.namespace.update(share_with_group_lock: true) }
- it { expect(project.team.max_member_access(master.id)).to be_nil }
- it { expect(project.team.max_member_access(reporter.id)).to be_nil }
+ it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::NO_ACCESS) }
+ it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::NO_ACCESS) }
end
end
end
@@ -194,8 +194,53 @@ describe ProjectTeam, models: true do
it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::MASTER) }
it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
it { expect(project.team.max_member_access(guest.id)).to eq(Gitlab::Access::GUEST) }
- it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
- it { expect(project.team.max_member_access(requester.id)).to be_nil }
+ it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
+ it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
+ end
+ end
+
+ describe "#max_member_access_for_users" do
+ it 'returns correct roles for different users' do
+ master = create(:user)
+ reporter = create(:user)
+ promoted_guest = create(:user)
+ guest = create(:user)
+ project = create(:project)
+
+ project.team << [master, :master]
+ project.team << [reporter, :reporter]
+ project.team << [promoted_guest, :guest]
+ project.team << [guest, :guest]
+
+ group = create(:group)
+ group_developer = create(:user)
+ second_developer = create(:user)
+ project.project_group_links.create(
+ group: group,
+ group_access: Gitlab::Access::DEVELOPER)
+
+ group.add_master(promoted_guest)
+ group.add_developer(group_developer)
+ group.add_developer(second_developer)
+
+ second_group = create(:group)
+ project.project_group_links.create(
+ group: second_group,
+ group_access: Gitlab::Access::MASTER)
+ second_group.add_master(second_developer)
+
+ users = [master, reporter, promoted_guest, guest, group_developer, second_developer].map(&:id)
+
+ expected = {
+ master.id => Gitlab::Access::MASTER,
+ reporter.id => Gitlab::Access::REPORTER,
+ promoted_guest.id => Gitlab::Access::DEVELOPER,
+ guest.id => Gitlab::Access::GUEST,
+ group_developer.id => Gitlab::Access::DEVELOPER,
+ second_developer.id => Gitlab::Access::MASTER
+ }
+
+ expect(project.team.max_member_access_for_user_ids(users)).to eq(expected)
end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 881ab5ff8dc..5bc1bd9a930 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -446,6 +446,43 @@ describe Repository, models: true do
end.to raise_error(GitHooksService::PreReceiveError)
end
end
+
+ context 'when target branch is different from source branch' do
+ before do
+ allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, ''])
+ end
+
+ it 'expires branch cache' do
+ expect(repository).not_to receive(:expire_exists_cache)
+ expect(repository).not_to receive(:expire_root_ref_cache)
+ expect(repository).not_to receive(:expire_emptiness_caches)
+ expect(repository).to receive(:expire_branches_cache)
+ expect(repository).to receive(:expire_has_visible_content_cache)
+ expect(repository).to receive(:expire_branch_count_cache)
+
+ repository.commit_with_hooks(user, 'new-feature') { sample_commit.id }
+ end
+ end
+
+ context 'when repository is empty' do
+ before do
+ allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, ''])
+ end
+
+ it 'expires creation and branch cache' do
+ empty_repository = create(:empty_project, :empty_repo).repository
+
+ expect(empty_repository).to receive(:expire_exists_cache)
+ expect(empty_repository).to receive(:expire_root_ref_cache)
+ expect(empty_repository).to receive(:expire_emptiness_caches)
+ expect(empty_repository).to receive(:expire_branches_cache)
+ expect(empty_repository).to receive(:expire_has_visible_content_cache)
+ expect(empty_repository).to receive(:expire_branch_count_cache)
+
+ empty_repository.commit_file(user, 'CHANGELOG', 'Changelog!',
+ 'Updates file content', 'master', false)
+ end
+ end
end
describe '#exists?' do
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 5219c808791..e4ea8506598 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -73,9 +73,13 @@ describe API::API, api: true do
context "authorized user" do
it "should return a commit by sha" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+
expect(response).to have_http_status(200)
expect(json_response['id']).to eq(project.repository.commit.id)
expect(json_response['title']).to eq(project.repository.commit.title)
+ expect(json_response['stats']['additions']).to eq(project.repository.commit.stats.additions)
+ expect(json_response['stats']['deletions']).to eq(project.repository.commit.stats.deletions)
+ expect(json_response['stats']['total']).to eq(project.repository.commit.stats.total)
end
it "should return a 404 error if not found" do
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 12f2cfa6942..9d3d28e0b91 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -531,10 +531,8 @@ describe API::API, api: true do
describe 'POST /projects/:id/issues with spam filtering' do
before do
- Grape::Endpoint.before_each do |endpoint|
- allow(endpoint).to receive(:check_for_spam?).and_return(true)
- allow(endpoint).to receive(:is_spam?).and_return(true)
- end
+ allow_any_instance_of(Gitlab::AkismetHelper).to receive(:check_for_spam?).and_return(true)
+ allow_any_instance_of(Gitlab::AkismetHelper).to receive(:is_spam?).and_return(true)
end
let(:params) do
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 1c7c60ec644..cf1e8d9b514 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -98,7 +98,7 @@ describe Ci::API::API do
{ "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true },
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
- { "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false }
+ { "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false }
)
end
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index 8b19936ae6d..69eeb45ed71 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -1,6 +1,5 @@
require 'spec_helper'
-# team_update_admin_user PUT /admin/users/:id/team_update(.:format) admin/users#team_update
# block_admin_user PUT /admin/users/:id/block(.:format) admin/users#block
# unblock_admin_user PUT /admin/users/:id/unblock(.:format) admin/users#unblock
# admin_users GET /admin/users(.:format) admin/users#index
@@ -11,10 +10,6 @@ require 'spec_helper'
# PUT /admin/users/:id(.:format) admin/users#update
# DELETE /admin/users/:id(.:format) admin/users#destroy
describe Admin::UsersController, "routing" do
- it "to #team_update" do
- expect(put("/admin/users/1/team_update")).to route_to('admin/users#team_update', id: '1')
- end
-
it "to #block" do
expect(put("/admin/users/1/block")).to route_to('admin/users#block', id: '1')
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 620f328a114..9151cd3aefe 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -135,10 +135,6 @@ describe Projects::RepositoriesController, 'routing' do
it 'to #archive format:tar.bz2' do
expect(get('/gitlab/gitlabhq/repository/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2')
end
-
- it 'to #show' do
- expect(get('/gitlab/gitlabhq/repository')).to route_to('projects/repositories#show', namespace_id: 'gitlab', project_id: 'gitlabhq')
- end
end
describe Projects::BranchesController, 'routing' do
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 0a52c1ab933..1d4df9197f6 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -176,18 +176,10 @@ describe Profiles::KeysController, "routing" do
expect(post("/profile/keys")).to route_to('profiles/keys#create')
end
- it "to #edit" do
- expect(get("/profile/keys/1/edit")).to route_to('profiles/keys#edit', id: '1')
- end
-
it "to #show" do
expect(get("/profile/keys/1")).to route_to('profiles/keys#show', id: '1')
end
- it "to #update" do
- expect(put("/profile/keys/1")).to route_to('profiles/keys#update', id: '1')
- end
-
it "to #destroy" do
expect(delete("/profile/keys/1")).to route_to('profiles/keys#destroy', id: '1')
end
diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb
new file mode 100644
index 00000000000..6f8f7109e14
--- /dev/null
+++ b/spec/simplecov_env.rb
@@ -0,0 +1,54 @@
+require 'simplecov'
+
+module SimpleCovEnv
+ extend self
+
+ def start!
+ return unless ENV['SIMPLECOV']
+
+ configure_profile
+ configure_job
+
+ SimpleCov.start
+ end
+
+ def configure_job
+ SimpleCov.configure do
+ if ENV['CI_BUILD_NAME']
+ coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}"
+ command_name ENV['CI_BUILD_NAME']
+ end
+
+ if ENV['CI']
+ SimpleCov.at_exit do
+ # In CI environment don't generate formatted reports
+ # Only generate .resultset.json
+ SimpleCov.result
+ end
+ end
+ end
+ end
+
+ def configure_profile
+ SimpleCov.configure do
+ load_profile 'test_frameworks'
+ track_files '{app,lib}/**/*.rb'
+
+ add_filter '/vendor/ruby/'
+ add_filter 'config/initializers/'
+
+ add_group 'Controllers', 'app/controllers'
+ add_group 'Models', 'app/models'
+ add_group 'Mailers', 'app/mailers'
+ add_group 'Helpers', 'app/helpers'
+ add_group 'Workers', %w(app/jobs app/workers)
+ add_group 'Libraries', 'lib'
+ add_group 'Services', 'app/services'
+ add_group 'Finders', 'app/finders'
+ add_group 'Uploaders', 'app/uploaders'
+ add_group 'Validators', 'app/validators'
+
+ merge_timeout 7200
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 3638dcbb2d3..4f3aacf55be 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,7 +1,5 @@
-if ENV['SIMPLECOV']
- require 'simplecov'
- SimpleCov.start :rails
-end
+require './spec/simplecov_env'
+SimpleCovEnv.start!
ENV["RAILS_ENV"] ||= 'test'
diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb
index 42220a20c75..464051063d8 100644
--- a/spec/views/projects/builds/show.html.haml_spec.rb
+++ b/spec/views/projects/builds/show.html.haml_spec.rb
@@ -44,9 +44,29 @@ describe 'projects/builds/show' do
it 'shows commit title and not show commit message' do
render
-
+
expect(rendered).to have_css('p.build-light-text.append-bottom-0',
text: /\A\n#{Regexp.escape(commit_title)}\n\Z/)
end
end
+
+ describe 'shows trigger variables in sidebar' do
+ let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline) }
+
+ before do
+ build.trigger_request = trigger_request
+ render
+ end
+
+ it 'shows trigger variables in separate lines' do
+ expect(rendered).to have_css('code', text: variable_regexp('TRIGGER_KEY_1', 'TRIGGER_VALUE_1'))
+ expect(rendered).to have_css('code', text: variable_regexp('TRIGGER_KEY_2', 'TRIGGER_VALUE_2'))
+ end
+ end
+
+ private
+
+ def variable_regexp(key, value)
+ /\A#{Regexp.escape("#{key}=#{value}")}\Z/
+ end
end
diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb
index de40a6f78af..fe70501eeac 100644
--- a/spec/workers/email_receiver_worker_spec.rb
+++ b/spec/workers/email_receiver_worker_spec.rb
@@ -17,7 +17,7 @@ describe EmailReceiverWorker do
context "when an error occurs" do
before do
- allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(Gitlab::Email::Receiver::EmptyEmailError)
+ allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(Gitlab::Email::EmptyEmailError)
end
it "sends out a rejection email" do
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 5f762282b5e..60605460adb 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -14,21 +14,24 @@ describe RepositoryForkWorker do
describe "#perform" do
it "creates a new repository from a fork" do
expect(shell).to receive(:fork_repository).with(
- project.repository_storage_path,
+ '/test/path',
project.path_with_namespace,
+ project.repository_storage_path,
fork_project.namespace.path
).and_return(true)
subject.perform(
project.id,
+ '/test/path',
project.path_with_namespace,
fork_project.namespace.path)
end
it 'flushes various caches' do
expect(shell).to receive(:fork_repository).with(
- project.repository_storage_path,
+ '/test/path',
project.path_with_namespace,
+ project.repository_storage_path,
fork_project.namespace.path
).and_return(true)
@@ -38,7 +41,7 @@ describe RepositoryForkWorker do
expect_any_instance_of(Repository).to receive(:expire_exists_cache).
and_call_original
- subject.perform(project.id, project.path_with_namespace,
+ subject.perform(project.id, '/test/path', project.path_with_namespace,
fork_project.namespace.path)
end
@@ -49,6 +52,7 @@ describe RepositoryForkWorker do
subject.perform(
project.id,
+ '/test/path',
project.path_with_namespace,
fork_project.namespace.path)
end
diff --git a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
index 0b329aaf1c4..00f9541e89b 100644
--- a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
@@ -2,7 +2,7 @@
# The image already has Hex installed. You might want to consider to use `elixir:latest`
image: trenpixster/elixir:latest
-# Pic zero or more services to be used on all builds.
+# Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
services: