Commit ce1843b7 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into diff-line-comment-vuejs

# Conflicts:
#	db/schema.rb
parents 8b1a2e4d b9b0c028
...@@ -28,6 +28,7 @@ stages: ...@@ -28,6 +28,7 @@ stages:
- prepare - prepare
- test - test
- post-test - post-test
- pages
# Prepare and merge knapsack tests # Prepare and merge knapsack tests
.knapsack-state: &knapsack-state .knapsack-state: &knapsack-state
...@@ -40,6 +41,7 @@ stages: ...@@ -40,6 +41,7 @@ stages:
paths: paths:
- knapsack/ - knapsack/
artifacts: artifacts:
expire_in: 31d
paths: paths:
- knapsack/ - knapsack/
...@@ -81,8 +83,10 @@ update-knapsack: ...@@ -81,8 +83,10 @@ update-knapsack:
- cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH} - cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH}
- knapsack rspec - knapsack rspec
artifacts: artifacts:
expire_in: 31d
paths: paths:
- knapsack/ - knapsack/
- coverage/
.spinach-knapsack: &spinach-knapsack .spinach-knapsack: &spinach-knapsack
stage: test stage: test
...@@ -97,8 +101,10 @@ update-knapsack: ...@@ -97,8 +101,10 @@ update-knapsack:
- cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH} - 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)' - knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts: artifacts:
expire_in: 31d
paths: paths:
- knapsack/ - knapsack/
- coverage/
rspec 0 20: *rspec-knapsack rspec 0 20: *rspec-knapsack
rspec 1 20: *rspec-knapsack rspec 1 20: *rspec-knapsack
...@@ -186,14 +192,14 @@ spinach 9 10 ruby23: *spinach-knapsack-ruby23 ...@@ -186,14 +192,14 @@ spinach 9 10 ruby23: *spinach-knapsack-ruby23
# Other generic tests # Other generic tests
.static-analyses-variables: &static-analyses-variables .ruby-static-analysis: &ruby-static-analysis
variables: variables:
SIMPLECOV: "false" SIMPLECOV: "false"
USE_DB: "false" USE_DB: "false"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
.exec: &exec .exec: &exec
<<: *static-analyses-variables <<: *ruby-static-analysis
stage: test stage: test
script: script:
- bundle exec $CI_BUILD_NAME - bundle exec $CI_BUILD_NAME
...@@ -220,12 +226,28 @@ teaspoon: ...@@ -220,12 +226,28 @@ teaspoon:
bundler:audit: bundler:audit:
stage: test stage: test
<<: *static-analyses-variables <<: *ruby-static-analysis
only: only:
- master - master
script: script:
- "bundle exec bundle-audit check --update --ignore OSVDB-115941" - "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 in the end
notify:slack: notify:slack:
...@@ -238,3 +260,18 @@ notify:slack: ...@@ -238,3 +260,18 @@ notify:slack:
- tags@gitlab-org/gitlab-ce - tags@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee - master@gitlab-org/gitlab-ee
- tags@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
# .simplecov
SimpleCov.start 'rails' do
merge_timeout 3600
end
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.11.0 (unreleased) 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) - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
- Fix CI status icon link underline (ClemMakesApps) - Fix CI status icon link underline (ClemMakesApps)
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - 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 - 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 - 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) - Add green outline to New Branch button. !5447 (winniehell)
- Retrieve rendered HTML from cache in one request - 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 - 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) - 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) - 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` - 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 GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska)
- Add the `sprockets-es6` gem - 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 - 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 - 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 - 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 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 - 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 v 8.10.1
- Refactor repository storages documentation. !5428 - Refactor repository storages documentation. !5428
......
...@@ -225,7 +225,7 @@ gem 'addressable', '~> 2.3.8' ...@@ -225,7 +225,7 @@ gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.3.0' gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.6.1' gem 'font-awesome-rails', '~> 4.6.1'
gem 'gemojione', '~> 3.0' gem 'gemojione', '~> 3.0'
gem 'gon', '~> 6.0.1' gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0' gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-ui-rails', '~> 5.0.0' gem 'jquery-ui-rails', '~> 5.0.0'
...@@ -253,7 +253,7 @@ group :development do ...@@ -253,7 +253,7 @@ group :development do
gem 'letter_opener_web', '~> 1.3.0' gem 'letter_opener_web', '~> 1.3.0'
gem 'rerun', '~> 0.11.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 'rblineprof', '~> 0.3.6', platform: :mri, require: false
gem 'web-console', '~> 2.0' gem 'web-console', '~> 2.0'
...@@ -275,7 +275,7 @@ group :development, :test do ...@@ -275,7 +275,7 @@ group :development, :test do
gem 'awesome_print', '~> 1.2.0', require: false gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0' 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 'factory_girl_rails', '~> 4.6.0'
gem 'rspec-rails', '~> 3.5.0' gem 'rspec-rails', '~> 3.5.0'
gem 'rspec-retry', '~> 0.4.5' gem 'rspec-retry', '~> 0.4.5'
...@@ -303,7 +303,7 @@ group :development, :test do ...@@ -303,7 +303,7 @@ group :development, :test do
gem 'rubocop', '~> 0.41.2', require: false gem 'rubocop', '~> 0.41.2', require: false
gem 'rubocop-rspec', '~> 1.5.0', require: false gem 'rubocop-rspec', '~> 1.5.0', require: false
gem 'scss_lint', '~> 0.47.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 'flog', '~> 4.3.2', require: false
gem 'flay', '~> 2.6.1', require: false gem 'flay', '~> 2.6.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false gem 'bundler-audit', '~> 0.5.0', require: false
...@@ -334,6 +334,8 @@ gem 'mail_room', '~> 0.8' ...@@ -334,6 +334,8 @@ gem 'mail_room', '~> 0.8'
gem 'email_reply_parser', '~> 0.5.8' gem 'email_reply_parser', '~> 0.5.8'
gem 'ruby-prof', '~> 0.15.9'
## CI ## CI
gem 'activerecord-session_store', '~> 1.0.0' gem 'activerecord-session_store', '~> 1.0.0'
gem 'nested_form', '~> 0.3.2' gem 'nested_form', '~> 0.3.2'
......
...@@ -59,7 +59,7 @@ GEM ...@@ -59,7 +59,7 @@ GEM
oauth2 (~> 1.0) oauth2 (~> 1.0)
asciidoctor (1.5.3) asciidoctor (1.5.3)
ast (2.3.0) ast (2.3.0)
attr_encrypted (3.0.1) attr_encrypted (3.0.3)
encryptor (~> 3.0.0) encryptor (~> 3.0.0)
attr_required (1.0.0) attr_required (1.0.0)
autoprefixer-rails (6.2.3) autoprefixer-rails (6.2.3)
...@@ -104,9 +104,9 @@ GEM ...@@ -104,9 +104,9 @@ GEM
brakeman (3.3.2) brakeman (3.3.2)
browser (2.2.0) browser (2.2.0)
builder (3.2.2) builder (3.2.2)
bullet (5.0.0) bullet (5.2.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
uniform_notifier (~> 1.9.0) uniform_notifier (~> 1.10.0)
bundler-audit (0.5.0) bundler-audit (0.5.0)
bundler (~> 1.2) bundler (~> 1.2)
thor (~> 0.18) thor (~> 0.18)
...@@ -153,11 +153,11 @@ GEM ...@@ -153,11 +153,11 @@ GEM
d3_rails (3.5.11) d3_rails (3.5.11)
railties (>= 3.1.0) railties (>= 3.1.0)
daemons (1.2.3) daemons (1.2.3)
database_cleaner (1.4.1) database_cleaner (1.5.3)
debug_inspector (0.0.2) debug_inspector (0.0.2)
debugger-ruby_core_source (1.3.8) debugger-ruby_core_source (1.3.8)
default_value_for (3.0.1) default_value_for (3.0.2)
activerecord (>= 3.2.0, < 5.0) activerecord (>= 3.2.0, < 5.1)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
devise (4.1.1) devise (4.1.1)
...@@ -303,7 +303,7 @@ GEM ...@@ -303,7 +303,7 @@ GEM
gollum-rugged_adapter (0.4.2) gollum-rugged_adapter (0.4.2)
mime-types (>= 1.15) mime-types (>= 1.15)
rugged (~> 0.24.0, >= 0.21.3) rugged (~> 0.24.0, >= 0.21.3)
gon (6.0.1) gon (6.1.0)
actionpack (>= 3.0) actionpack (>= 3.0)
json json
multi_json multi_json
...@@ -509,7 +509,7 @@ GEM ...@@ -509,7 +509,7 @@ GEM
rack-cors (0.4.0) rack-cors (0.4.0)
rack-mount (0.8.3) rack-mount (0.8.3)
rack (>= 1.0.0) rack (>= 1.0.0)
rack-oauth2 (1.2.1) rack-oauth2 (1.2.3)
activesupport (>= 2.3) activesupport (>= 2.3)
attr_required (>= 0.0.5) attr_required (>= 0.0.5)
httpclient (>= 2.4) httpclient (>= 2.4)
...@@ -575,7 +575,7 @@ GEM ...@@ -575,7 +575,7 @@ GEM
redis-store (~> 1.1.0) redis-store (~> 1.1.0)
redis-store (1.1.7) redis-store (1.1.7)
redis (>= 2.2) redis (>= 2.2)
request_store (1.3.0) request_store (1.3.1)
rerun (0.11.0) rerun (0.11.0)
listen (~> 3.0) listen (~> 3.0)
responders (2.1.1) responders (2.1.1)
...@@ -620,6 +620,7 @@ GEM ...@@ -620,6 +620,7 @@ GEM
rubocop (>= 0.40.0) rubocop (>= 0.40.0)
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-prof (0.15.9)
ruby-progressbar (1.8.1) ruby-progressbar (1.8.1)
ruby-saml (1.3.0) ruby-saml (1.3.0)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
...@@ -672,9 +673,9 @@ GEM ...@@ -672,9 +673,9 @@ GEM
rufus-scheduler (>= 2.0.24) rufus-scheduler (>= 2.0.24)
sidekiq (>= 4.0.0) sidekiq (>= 4.0.0)
simple_oauth (0.1.9) simple_oauth (0.1.9)
simplecov (0.11.2) simplecov (0.12.0)
docile (~> 1.1.0) docile (~> 1.1.0)
json (~> 1.8) json (>= 1.8, < 3)
simplecov-html (~> 0.10.0) simplecov-html (~> 0.10.0)
simplecov-html (0.10.0) simplecov-html (0.10.0)
sinatra (1.4.7) sinatra (1.4.7)
...@@ -774,7 +775,7 @@ GEM ...@@ -774,7 +775,7 @@ GEM
unicorn-worker-killer (0.4.4) unicorn-worker-killer (0.4.4)
get_process_mem (~> 0) get_process_mem (~> 0)
unicorn (>= 4, < 6) unicorn (>= 4, < 6)
uniform_notifier (1.9.0) uniform_notifier (1.10.0)
uuid (2.3.8) uuid (2.3.8)
macaddr (~> 1.0) macaddr (~> 1.0)
version_sorter (2.0.0) version_sorter (2.0.0)
...@@ -829,7 +830,7 @@ DEPENDENCIES ...@@ -829,7 +830,7 @@ DEPENDENCIES
bootstrap-sass (~> 3.3.0) bootstrap-sass (~> 3.3.0)
brakeman (~> 3.3.0) brakeman (~> 3.3.0)
browser (~> 2.2) browser (~> 2.2)
bullet (~> 5.0.0) bullet (~> 5.2.0)
bundler-audit (~> 0.5.0) bundler-audit (~> 0.5.0)
byebug (~> 8.2.1) byebug (~> 8.2.1)
capybara (~> 2.6.2) capybara (~> 2.6.2)
...@@ -841,7 +842,7 @@ DEPENDENCIES ...@@ -841,7 +842,7 @@ DEPENDENCIES
connection_pool (~> 2.0) connection_pool (~> 2.0)
creole (~> 0.5.0) creole (~> 0.5.0)
d3_rails (~> 3.5.0) d3_rails (~> 3.5.0)
database_cleaner (~> 1.4.0) database_cleaner (~> 1.5.0)
default_value_for (~> 3.0.0) default_value_for (~> 3.0.0)
devise (~> 4.0) devise (~> 4.0)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
...@@ -874,7 +875,7 @@ DEPENDENCIES ...@@ -874,7 +875,7 @@ DEPENDENCIES
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.2) gollum-rugged_adapter (~> 0.4.2)
gon (~> 6.0.1) gon (~> 6.1.0)
grape (~> 0.13.0) grape (~> 0.13.0)
grape-entity (~> 0.4.2) grape-entity (~> 0.4.2)
hamlit (~> 2.5) hamlit (~> 2.5)
...@@ -948,6 +949,7 @@ DEPENDENCIES ...@@ -948,6 +949,7 @@ DEPENDENCIES
rubocop (~> 0.41.2) rubocop (~> 0.41.2)
rubocop-rspec (~> 1.5.0) rubocop-rspec (~> 1.5.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.15.9)
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 5.0.0) sass-rails (~> 5.0.0)
scss_lint (~> 0.47.0) scss_lint (~> 0.47.0)
...@@ -960,7 +962,7 @@ DEPENDENCIES ...@@ -960,7 +962,7 @@ DEPENDENCIES
shoulda-matchers (~> 2.8.0) shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.0) sidekiq (~> 4.0)
sidekiq-cron (~> 0.4.0) sidekiq-cron (~> 0.4.0)
simplecov (~> 0.11.0) simplecov (= 0.12.0)
sinatra (~> 1.4.4) sinatra (~> 1.4.4)
six (~> 0.2.0) six (~> 0.2.0)
slack-notifier (~> 1.2.0) slack-notifier (~> 1.2.0)
......
...@@ -66,4 +66,12 @@ ...@@ -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); }).call(this);
...@@ -189,6 +189,7 @@ ...@@ -189,6 +189,7 @@
_this.groupId = $(select).data('group-id'); _this.groupId = $(select).data('group-id');
_this.showCurrentUser = $(select).data('current-user'); _this.showCurrentUser = $(select).data('current-user');
_this.authorId = $(select).data('author-id'); _this.authorId = $(select).data('author-id');
_this.skipUsers = $(select).data('skip-users');
showNullUser = $(select).data('null-user'); showNullUser = $(select).data('null-user');
showAnyUser = $(select).data('any-user'); showAnyUser = $(select).data('any-user');
showEmailUser = $(select).data('email-user'); showEmailUser = $(select).data('email-user');
...@@ -320,7 +321,8 @@ ...@@ -320,7 +321,8 @@
project_id: this.projectId, project_id: this.projectId,
group_id: this.groupId, group_id: this.groupId,
current_user: this.showCurrentUser, current_user: this.showCurrentUser,
author_id: this.authorId author_id: this.authorId,
skip_users: this.skipUsers
}, },
dataType: "json" dataType: "json"
}).done(function(users) { }).done(function(users) {
......
...@@ -99,3 +99,33 @@ form.edit-issue { ...@@ -99,3 +99,33 @@ form.edit-issue {
.issue-form .select2-container { .issue-form .select2-container {
width: 250px !important; 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;
}
}
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
...@@ -5,6 +5,7 @@ class AutocompleteController < ApplicationController ...@@ -5,6 +5,7 @@ class AutocompleteController < ApplicationController
def users def users
@users ||= User.none @users ||= User.none
@users = @users.search(params[:search]) if params[:search].present? @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.active
@users = @users.reorder(:name) @users = @users.reorder(:name)
@users = @users.page(params[:page]) @users = @users.page(params[:page])
......
class Explore::ApplicationController < ApplicationController class Explore::ApplicationController < ApplicationController
skip_before_action :authenticate_user!, :reject_blocked skip_before_action :authenticate_user!, :reject_blocked!
layout 'explore' layout 'explore'
end end
class HelpController < ApplicationController class HelpController < ApplicationController
skip_before_action :authenticate_user!, :reject_blocked skip_before_action :authenticate_user!, :reject_blocked!
layout 'help' layout 'help'
......
...@@ -6,6 +6,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -6,6 +6,7 @@ class Projects::BranchesController < Projects::ApplicationController
before_action :authorize_push_code!, only: [:new, :create, :destroy] before_action :authorize_push_code!, only: [:new, :create, :destroy]
def index def index
@sort = params[:sort].presence || 'name'
@branches = BranchesFinder.new(@repository, params).execute @branches = BranchesFinder.new(@repository, params).execute
@branches = Kaminari.paginate_array(@branches).page(params[:page]) @branches = Kaminari.paginate_array(@branches).page(params[:page])
......
class Projects::IssuesController < Projects::ApplicationController class Projects::IssuesController < Projects::ApplicationController
include NotesHelper
include ToggleSubscriptionAction include ToggleSubscriptionAction
include IssuableActions include IssuableActions
include ToggleAwardEmoji include ToggleAwardEmoji
...@@ -70,6 +71,8 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -70,6 +71,8 @@ class Projects::IssuesController < Projects::ApplicationController
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@noteable = @issue @noteable = @issue
preload_max_access_for_authors(@notes, @project)
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
...@@ -79,7 +82,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -79,7 +82,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def create 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| respond_to do |format|
format.html do format.html do
...@@ -89,7 +92,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -89,7 +92,7 @@ class Projects::IssuesController < Projects::ApplicationController
render :new render :new
end end
end end
format.js do |format| format.js do
@link = @issue.attachment.url.to_js @link = @issue.attachment.url.to_js
end end
end end
......
...@@ -3,6 +3,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -3,6 +3,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
include DiffForPath include DiffForPath
include DiffHelper include DiffHelper
include IssuableActions include IssuableActions
include NotesHelper
include ToggleAwardEmoji include ToggleAwardEmoji
before_action :module_enabled before_action :module_enabled
...@@ -382,6 +383,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -382,6 +383,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@project_wiki, @project_wiki,
@ref @ref
) )
preload_max_access_for_authors(@notes, @project)
end end
def define_widget_vars def define_widget_vars
...@@ -401,7 +404,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -401,7 +404,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
} }
@use_legacy_diff_notes = !@merge_request.support_new_diff_notes? @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( Banzai::NoteRenderer.render(
@grouped_diff_discussions.values.flat_map(&:notes), @grouped_diff_discussions.values.flat_map(&:notes),
......
...@@ -10,11 +10,12 @@ class Projects::TagsController < Projects::ApplicationController ...@@ -10,11 +10,12 @@ class Projects::TagsController < Projects::ApplicationController
@tags = @repository.tags_sorted_by(@sort) @tags = @repository.tags_sorted_by(@sort)
@tags = Kaminari.paginate_array(@tags).page(params[:page]) @tags = Kaminari.paginate_array(@tags).page(params[:page])
@releases = project.releases.where(tag: @tags) @releases = project.releases.where(tag: @tags.map(&:name))
end end
def show def show
@tag = @repository.find_tag(params[:id]) @tag = @repository.find_tag(params[:id])
@release = @project.releases.find_or_initialize_by(tag: @tag.name) @release = @project.releases.find_or_initialize_by(tag: @tag.name)
@commit = @repository.commit(@tag.target) @commit = @repository.commit(@tag.target)
end end
......
class SearchController < ApplicationController class SearchController < ApplicationController
skip_before_action :authenticate_user!, :reject_blocked skip_before_action :authenticate_user!, :reject_blocked!
include SearchHelper include SearchHelper
......
...@@ -7,7 +7,7 @@ module NotesHelper ...@@ -7,7 +7,7 @@ module NotesHelper
end end
def note_editable?(note) def note_editable?(note)
note.editable? && can?(current_user, :admin_note, note) Ability.can_edit_note?(current_user, note)
end end
def noteable_json(noteable) def noteable_json(noteable)
...@@ -85,14 +85,13 @@ module NotesHelper ...@@ -85,14 +85,13 @@ module NotesHelper
data: data, title: 'Add a reply' data: data, title: 'Add a reply'
end end
def note_max_access_for_user(note) def preload_max_access_for_authors(notes, project)
@max_access_by_user_id ||= Hash.new do |hash, key| user_ids = notes.map(&:author_id)
project = key[:project] project.team.max_member_access_for_user_ids(user_ids)
hash[key] = project.team.human_max_access(key[:user_id]) end
end
full_key = { project: note.project, user_id: note.author_id } def note_max_access_for_user(note)
@max_access_by_user_id[full_key] note.project.team.human_max_access(note.author_id)
end end
def discussion_diff_path(discussion) def discussion_diff_path(discussion)
......
...@@ -5,21 +5,9 @@ module SelectsHelper ...@@ -5,21 +5,9 @@ module SelectsHelper
css_class << "skip_ldap " if opts[:skip_ldap] css_class << "skip_ldap " if opts[:skip_ldap]
css_class << (opts[:class] || '') css_class << (opts[:class] || '')
value = opts[:selected] || '' value = opts[:selected] || ''
first_user = opts[:first_user] && current_user ? current_user.username : false
html = { html = {
class: css_class, class: css_class,
data: { data: 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: first_user,
current_user: opts[:current_user] || false,
"push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
author_id: opts[:author_id] || ''
}
} }
unless opts[:scope] == :all unless opts[:scope] == :all
...@@ -68,4 +56,20 @@ module SelectsHelper ...@@ -68,4 +56,20 @@ module SelectsHelper
hidden_field_tag(id, value, class: css_class) hidden_field_tag(id, value, class: css_class)
end 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 end
...@@ -389,6 +389,18 @@ class Ability ...@@ -389,6 +389,18 @@ class Ability
GroupProjectsFinder.new(group).execute(user).any? GroupProjectsFinder.new(group).execute(user).any?
end 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) def namespace_abilities(user, namespace)
rules = [] rules = []
......
...@@ -331,7 +331,7 @@ module Ci ...@@ -331,7 +331,7 @@ module Ci
end end
def valid_token?(token) def valid_token?(token)
project.valid_runners_token? token project.valid_runners_token?(token)
end end
def has_tags? def has_tags?
......
...@@ -17,7 +17,7 @@ module Issuable ...@@ -17,7 +17,7 @@ module Issuable
belongs_to :assignee, class_name: "User" belongs_to :assignee, class_name: "User"
belongs_to :updated_by, class_name: "User" belongs_to :updated_by, class_name: "User"
belongs_to :milestone 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? def authors_loaded?
# We check first if we're loaded to not load unnecessarily. # We check first if we're loaded to not load unnecessarily.
loaded? && to_a.all? { |note| note.association(:author).loaded? } loaded? && to_a.all? { |note| note.association(:author).loaded? }
......
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
...@@ -6,6 +6,7 @@ class Issue < ActiveRecord::Base ...@@ -6,6 +6,7 @@ class Issue < ActiveRecord::Base
include Referable include Referable
include Sortable include Sortable
include Taskable include Taskable
include Spammable
DueDateStruct = Struct.new(:title, :name).freeze DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
......
...@@ -25,6 +25,14 @@ class LegacyDiffNote < Note ...@@ -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)) @discussion_id ||= Digest::SHA1.hexdigest(self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code))
end 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 def diff_file_hash
line_code.split('_')[0] if line_code line_code.split('_')[0] if line_code
end end
...@@ -34,7 +42,7 @@ class LegacyDiffNote < Note ...@@ -34,7 +42,7 @@ class LegacyDiffNote < Note
end end
def diff_file 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 end
def diff_line def diff_line
......
...@@ -53,6 +53,10 @@ class Member < ActiveRecord::Base ...@@ -53,6 +53,10 @@ class Member < ActiveRecord::Base
default_value_for :notification_level, NotificationSetting.levels[:global] default_value_for :notification_level, NotificationSetting.levels[:global]
class << self 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) def find_by_invite_token(invite_token)
invite_token = Devise.token_generator.digest(self, :invite_token, invite_token) invite_token = Devise.token_generator.digest(self, :invite_token, invite_token)
find_by(invite_token: invite_token) find_by(invite_token: invite_token)
......
...@@ -451,7 +451,9 @@ class Project < ActiveRecord::Base ...@@ -451,7 +451,9 @@ class Project < ActiveRecord::Base
def add_import_job def add_import_job
if forked? 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 else
job_id = RepositoryImportWorker.perform_async(self.id) job_id = RepositoryImportWorker.perform_async(self.id)
end end
...@@ -584,7 +586,11 @@ class Project < ActiveRecord::Base ...@@ -584,7 +586,11 @@ class Project < ActiveRecord::Base
end end
def to_param def to_param
path if persisted? && errors.include?(:path)
path_was
else
path
end
end end
def to_reference(_from_project = nil) def to_reference(_from_project = nil)
...@@ -599,6 +605,13 @@ class Project < ActiveRecord::Base ...@@ -599,6 +605,13 @@ class Project < ActiveRecord::Base
web_url.split('://')[1] web_url.split('://')[1]
end 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) def build_commit_note(commit)
notes.new(commit_id: commit.id, noteable_type: 'Commit') notes.new(commit_id: commit.id, noteable_type: 'Commit')
end end
...@@ -1151,7 +1164,10 @@ class Project < ActiveRecord::Base ...@@ -1151,7 +1164,10 @@ class Project < ActiveRecord::Base
def schedule_delete!(user_id, params) def schedule_delete!(user_id, params)
# Queue this task for after the commit, so once we mark pending_delete it will run # 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) update_attribute(:pending_delete, true)
end end
......
...@@ -46,7 +46,7 @@ class HipchatService < Service ...@@ -46,7 +46,7 @@ class HipchatService < Service
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
message = create_message(data) message = create_message(data)
return unless message.present? return unless message.present?
gate[room].send('GitLab', message, message_options) gate[room].send('GitLab', message, message_options(data))
end end
def test(data) def test(data)
...@@ -67,8 +67,8 @@ class HipchatService < Service ...@@ -67,8 +67,8 @@ class HipchatService < Service
@gate ||= HipChat::Client.new(token, options) @gate ||= HipChat::Client.new(token, options)
end end
def message_options def message_options(data = nil)
{ notify: notify.present? && notify == '1', color: color || 'yellow' } { notify: notify.present? && notify == '1', color: message_color(data) }
end end
def create_message(data) def create_message(data)
...@@ -240,6 +240,21 @@ class HipchatService < Service ...@@ -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)" "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
end 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 def project_name
project.name_with_namespace.gsub(/\s/, '') project.name_with_namespace.gsub(/\s/, '')
end end
......
...@@ -132,39 +132,63 @@ class ProjectTeam ...@@ -132,39 +132,63 @@ class ProjectTeam
Gitlab::Access.options_with_owner.key(max_member_access(user_id)) Gitlab::Access.options_with_owner.key(max_member_access(user_id))
end end
# This method assumes project and group members are eager loaded for optimal # Determine the maximum access level for a group of users in bulk.
# performance. #
def max_member_access(user_id) # Returns a Hash mapping user ID -> maximum access level.
access = [] 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 if user_ids.present?
access += group.members.where(user_id: user_id).has_access.pluck(:access_level) user_ids.each { |id| access[id] = Gitlab::Access::NO_ACCESS }
end
if project.invited_groups.any? && project.allowed_to_share_with_group? member_access = project.members.access_for_user_ids(user_ids)
access << max_invited_level(user_id) 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 end
access.compact.max access
end
def max_member_access(user_id)
max_member_access_for_user_ids([user_id])[user_id]
end end
private private
def max_invited_level(user_id) # For a given group, return the maximum access level for the user. This is the min of
project.project_group_links.map do |group_link| # the invited access level of the group and the access level of the user within the group.
invited_group = group_link.group # For example, if the group has been given DEVELOPER access but the member has MASTER access,
access = invited_group.group_members.find_by(user_id: user_id).try(:access_field) # 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 # If the user is not in the list, assume he/she does not have access
# to max allowed access level missing_users = user_ids - access.keys
if access && access > group_link.group_access missing_users.each { |id| access[id] = Gitlab::Access::NO_ACCESS }
access = group_link.group_access
end
access # Cap the maximum access by the invited level access
end.compact.max access.each { |key, value| access[key] = [value, capped_access_level].min }
end end
def fetch_members(level = nil) def fetch_members(level = nil)
...@@ -215,4 +239,8 @@ class ProjectTeam ...@@ -215,4 +239,8 @@ class ProjectTeam
def group def group
project.group project.group
end end
def merge_max!(first_hash, second_hash)
first_hash.merge!(second_hash) { |_key, old, new| old > new ? old : new }
end
end end
...@@ -211,6 +211,9 @@ class Repository ...@@ -211,6 +211,9 @@ class Repository
rugged.references.create(keep_around_ref_name(sha), sha, force: true) rugged.references.create(keep_around_ref_name(sha), sha, force: true)
rescue Rugged::ReferenceError => ex rescue Rugged::ReferenceError => ex
Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{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
end end
...@@ -612,11 +615,11 @@ class Repository ...@@ -612,11 +615,11 @@ class Repository
case value case value
when 'name' when 'name'
branches.sort_by(&:name) branches.sort_by(&:name)
when 'recently_updated' when 'updated_desc'
branches.sort do |a, b| branches.sort do |a, b|
commit(b.target).committed_date <=> commit(a.target).committed_date commit(b.target).committed_date <=> commit(a.target).committed_date
end end
when 'last_updated' when 'updated_asc'
branches.sort do |a, b| branches.sort do |a, b|
commit(a.target).committed_date <=> commit(b.target).committed_date commit(a.target).committed_date <=> commit(b.target).committed_date
end end
...@@ -985,6 +988,10 @@ class Repository ...@@ -985,6 +988,10 @@ class Repository
if was_empty || !target_branch if was_empty || !target_branch
# Create branch # Create branch
rugged.references.create(ref, newrev) rugged.references.create(ref, newrev)
# If repo was empty expire cache
after_create if was_empty
after_create_branch
else else
# Update head # Update head
current_head = find_branch(branch).target current_head = find_branch(branch).target
......
...@@ -2,10 +2,14 @@ module Issues ...@@ -2,10 +2,14 @@ module Issues
class CreateService < Issues::BaseService class CreateService < Issues::BaseService
def execute def execute
filter_params filter_params
label_params = params[:label_ids] label_params = params.delete(:label_ids)
issue = project.issues.new(params.except(:label_ids)) request = params.delete(:request)
api = params.delete(:api)
issue = project.issues.new(params)
issue.author = params[:author] || current_user issue.author = params[:author] || current_user
issue.spam = spam_check_service.execute(request, api)
if issue.save if issue.save
issue.update_attributes(label_ids: label_params) issue.update_attributes(label_ids: label_params)
notification_service.new_issue(issue, current_user) notification_service.new_issue(issue, current_user)
...@@ -17,5 +21,11 @@ module Issues ...@@ -17,5 +21,11 @@ module Issues
issue issue
end end
private
def spam_check_service
SpamCheckService.new(project, current_user, params)
end
end end
end end
...@@ -3,7 +3,7 @@ module Projects ...@@ -3,7 +3,7 @@ module Projects
def execute def execute
# check that user is allowed to set specified visibility_level # check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level] new_visibility = params[:visibility_level]
if new_visibility && new_visibility.to_i != project.visibility_level if new_visibility && new_visibility.to_i != project.visibility_level
unless can?(current_user, :change_visibility_level, project) && unless can?(current_user, :change_visibility_level, project) &&
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
......
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
...@@ -16,3 +16,7 @@ ...@@ -16,3 +16,7 @@
= link_to admin_health_check_path, title: 'Health Check' do = link_to admin_health_check_path, title: 'Health Check' do
%span %span
Health Check Health Check
= nav_link(controller: :requests_profiles) do
= link_to admin_requests_profiles_path, title: 'Requests Profiles' do
%span
Requests Profiles
- @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
...@@ -74,6 +74,4 @@ ...@@ -74,6 +74,4 @@
= link_to "import flow", status_import_bitbucket_path, "data-no-turbolink" => "true" = link_to "import flow", status_import_bitbucket_path, "data-no-turbolink" => "true"
again. again.
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } }
:javascript
new ImporterStatus("#{jobs_import_bitbucket_path}", "#{import_bitbucket_path}");
...@@ -56,5 +56,4 @@ ...@@ -56,5 +56,4 @@
Import Import
= icon("spinner spin", class: "loading-icon") = icon("spinner spin", class: "loading-icon")
:javascript .js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } }
new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}");
...@@ -55,5 +55,4 @@ ...@@ -55,5 +55,4 @@
Import Import
= icon("spinner spin", class: "loading-icon") = icon("spinner spin", class: "loading-icon")
:javascript .js-importer-status{ data: { jobs_import_path: "#{jobs_import_github_path}", import_path: "#{import_github_path}" } }
new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}");
...@@ -51,5 +51,4 @@ ...@@ -51,5 +51,4 @@
Import Import
= icon("spinner spin", class: "loading-icon") = icon("spinner spin", class: "loading-icon")
:javascript .js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } }
new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}");
...@@ -51,5 +51,4 @@ ...@@ -51,5 +51,4 @@
Import Import
= icon("spinner spin", class: "loading-icon") = icon("spinner spin", class: "loading-icon")
:javascript .js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitorious_path}", import_path: "#{import_gitorious_path}" } }
new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}");
...@@ -77,5 +77,4 @@ ...@@ -77,5 +77,4 @@
= link_to "import flow", new_import_google_code_path = link_to "import flow", new_import_google_code_path
again. again.
:javascript .js-importer-status{ data: { jobs_import_path: "#{jobs_import_google_code_path}", import_path: "#{import_google_code_path}" } }
new ImporterStatus("#{jobs_import_google_code_path}", "#{import_google_code_path}");
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
%span %span
Overview 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 = link_to admin_system_info_path, title: 'Monitoring' do
%span %span
Monitoring Monitoring
......
...@@ -7,28 +7,28 @@ ...@@ -7,28 +7,28 @@
.nav-text .nav-text
Protected branches can be managed in project settings Protected branches can be managed in project settings
- if can? current_user, :push_code, @project .nav-controls
.nav-controls = form_tag(filter_branches_path, method: :get) do
= 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 }
= 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 .dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light %span.light
- if params[:sort].present? = projects_sort_options_hash[@sort]
= params[:sort].humanize %b.caret
- else %ul.dropdown-menu.dropdown-menu-align-right
Name %li
%b.caret = link_to filter_branches_path(sort: sort_value_name) do
%ul.dropdown-menu.dropdown-menu-align-right = sort_title_name
%li = link_to filter_branches_path(sort: sort_value_recently_updated) do
= link_to filter_branches_path(sort: nil) do = sort_title_recently_updated
= sort_title_name = link_to filter_branches_path(sort: sort_value_oldest_updated) do
= link_to filter_branches_path(sort: 'recently_updated') do = sort_title_oldest_updated
= sort_title_recently_updated
= link_to filter_branches_path(sort: 'last_updated') do - if can? current_user, :push_code, @project
= sort_title_oldest_updated
= link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
New branch New branch
- if @branches.any? - if @branches.any?
%ul.content-list.all-branches %ul.content-list.all-branches
- @branches.each do |branch| - @branches.each do |branch|
......
...@@ -88,8 +88,9 @@ ...@@ -88,8 +88,9 @@
%p %p
%span.build-light-text Variables: %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} #{key}=#{value}
.block .block
......
...@@ -22,6 +22,8 @@ ...@@ -22,6 +22,8 @@
- if defined?(ref) && ref - if defined?(ref) && ref
- if build.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" = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
- else - else
.light none .light none
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.content-block.oneline-block.files-changed .content-block.oneline-block.files-changed
.inline-parallel-buttons .inline-parallel-buttons
- if !expand_all_diffs? && diff_files.any? { |diff_file| diff_file.collapsed? } - 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 show_whitespace_toggle
- if current_controller?(:commit) - if current_controller?(:commit)
= commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs') = commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs')
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
%h4.prepend-top-0 %h4.prepend-top-0
Project settings Project settings
.col-lg-9 .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| = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
%fieldset.append-bottom-0 %fieldset.append-bottom-0
.form-group .form-group
...@@ -190,6 +191,7 @@ ...@@ -190,6 +191,7 @@
%h4.prepend-top-0.warning-title %h4.prepend-top-0.warning-title
Rename repository Rename repository
.col-lg-9 .col-lg-9
= render 'projects/errors'
= form_for([@project.namespace.becomes(Namespace), @project]) do |f| = form_for([@project.namespace.becomes(Namespace), @project]) do |f|
.form-group.project_name_holder .form-group.project_name_holder
= f.label :name, class: 'label-light' do = f.label :name, class: 'label-light' do
......
...@@ -19,4 +19,9 @@ ...@@ -19,4 +19,9 @@
] ]
} }
var ctx = $("#build_timesChart").get(0).getContext("2d"); 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);
...@@ -48,4 +48,9 @@ ...@@ -48,4 +48,9 @@
] ]
} }
var ctx = $("##{scope}Chart").get(0).getContext("2d"); 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);
...@@ -59,6 +59,10 @@ ...@@ -59,6 +59,10 @@
var container = $(selector).parent(); var container = $(selector).parent();
var generateChart = function() { var generateChart = function() {
selector.attr('width', $(container).width()); 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); return new Chart(ctx).Bar(data, options);
}; };
// enabling auto-resizing // enabling auto-resizing
......
.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')
- @no_container = true - @no_container = true
- page_title "Issues" - page_title "Issues"
- new_issue_email = @project.new_issue_address(current_user)
= render "projects/issues/head" = render "projects/issues/head"
= content_for :meta_tags do = content_for :meta_tags do
...@@ -23,7 +24,9 @@ ...@@ -23,7 +24,9 @@
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
.issues-holder .issues-holder
= render "issues" = render 'issues'
- if new_issue_email
= render 'issue_by_email', email: new_issue_email
- else - else
.blank-state.blank-state-welcome .blank-state.blank-state-welcome
%h2.blank-state-title.blank-state-welcome-title %h2.blank-state-title.blank-state-welcome-title
...@@ -40,3 +43,5 @@ ...@@ -40,3 +43,5 @@
- if can? current_user, :create_issue, @project - 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 = link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
New Issue New Issue
- if new_issue_email
= render 'issue_by_email', email: new_issue_email
...@@ -6,4 +6,4 @@ ...@@ -6,4 +6,4 @@
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}"); $(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
$('.save-project-loader').hide(); $('.save-project-loader').hide();
$('.project-edit-container').show(); $('.project-edit-container').show();
$('.project-edit-content .btn-save').enable(); $('.edit-project .btn-save').enable();
<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"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
<defs> <g fill="#5C5C5C" fill-rule="evenodd">
<circle id="a" cx="7" cy="7" r="7"/> <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"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white"> <rect width="8" height="2" x="3" y="6" transform="rotate(45 7 7)" rx=".5"/>
<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"/>
</g> </g>
</svg> </svg>
<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"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
<defs> <g fill="#D22852" fill-rule="evenodd">
<circle id="a" cx="7" cy="7" r="7"/> <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"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white"> <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)"/>
<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)"/>
</g> </g>
</svg> </svg>
<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"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
<defs> <g fill="#E75E40" fill-rule="evenodd">
<circle id="a" cx="7" cy="7" r="7"/> <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"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white"> <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"/>
<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"/>
</g> </g>
</svg> </svg>
<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"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
<defs> <g fill="#2D9FD8" fill-rule="evenodd">
<circle id="a" cx="7" cy="7" r="7"/> <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"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white"> <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"/>
<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"/>
</g> </g>
</svg> </svg>
<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"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
<defs> <g fill="#31AF64" fill-rule="evenodd">
<circle id="a" cx="7" cy="7" r="7"/> <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"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white"> <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)"/>
<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>
</g> </g>
</svg> </svg>
<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"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
<defs> <g fill="#FF8A24" fill-rule="evenodd">
<circle id="a" cx="7" cy="7" r="7"/> <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"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white"> <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"/>
<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"/>
</g> </g>
</svg> </svg>
...@@ -21,31 +21,35 @@ class EmailReceiverWorker ...@@ -21,31 +21,35 @@ class EmailReceiverWorker
return unless raw.present? return unless raw.present?
can_retry = false can_retry = false
reason = nil reason =
case e
case e when Gitlab::Email::UnknownIncomingEmail
when Gitlab::Email::Receiver::SentNotificationNotFoundError "We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
reason = "We couldn't figure out what the email is in reply to. Please create your comment through the web interface." when Gitlab::Email::SentNotificationNotFoundError
when Gitlab::Email::Receiver::EmptyEmailError "We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
can_retry = true when Gitlab::Email::ProjectNotFound
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." "We couldn't find the project. Please check if there's any typo."
when Gitlab::Email::Receiver::AutoGeneratedEmailError when Gitlab::Email::EmptyEmailError
reason = "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface." can_retry = true
when Gitlab::Email::Receiver::UserNotFoundError "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
reason = "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface." when Gitlab::Email::AutoGeneratedEmailError
when Gitlab::Email::Receiver::UserBlockedError "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface."
reason = "Your account has been blocked. If you believe this is in error, contact a staff member." when Gitlab::Email::UserNotFoundError
when Gitlab::Email::Receiver::UserNotAuthorizedError "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
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::UserBlockedError
when Gitlab::Email::Receiver::NoteableNotFoundError "Your account has been blocked. If you believe this is in error, contact a staff member."
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::UserNotAuthorizedError
when Gitlab::Email::Receiver::InvalidNoteError "You are not allowed to perform this action. If you believe this is in error, contact a staff member."
can_retry = true when Gitlab::Email::NoteableNotFoundError
reason = e.message "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
else when Gitlab::Email::InvalidNoteError,
return Gitlab::Email::InvalidIssueError
can_retry = true
e.message
end
if reason
EmailRejectionMailer.rejection(reason, raw, can_retry).deliver_later
end end
EmailRejectionMailer.rejection(reason, raw, can_retry).deliver_later
end end
end end
...@@ -4,7 +4,7 @@ class RepositoryForkWorker ...@@ -4,7 +4,7 @@ class RepositoryForkWorker
sidekiq_options queue: :gitlab_shell 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) project = Project.find_by_id(project_id)
unless project.present? unless project.present?
...@@ -12,7 +12,8 @@ class RepositoryForkWorker ...@@ -12,7 +12,8 @@ class RepositoryForkWorker
return return
end 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 unless result
logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}") 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.') project.mark_import_as_failed('The project could not be forked.')
......
class RequestsProfilesWorker
include Sidekiq::Worker
sidekiq_options queue: :default
def perform
Gitlab::RequestProfiler.remove_all_profiles
end
end
...@@ -68,6 +68,25 @@ ...@@ -68,6 +68,25 @@
:why: https://opensource.org/licenses/BSD-2-Clause :why: https://opensource.org/licenses/BSD-2-Clause
:versions: [] :versions: []
:when: 2016-05-02 05:55:09.796363000 Z :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 # LICENSE BLACKLIST
- - :blacklist - - :blacklist
...@@ -175,15 +194,3 @@ ...@@ -175,15 +194,3 @@
:why: https://github.com/jmcnevin/rubypants/blob/master/LICENSE.rdoc :why: https://github.com/jmcnevin/rubypants/blob/master/LICENSE.rdoc
:versions: [] :versions: []
:when: 2016-05-02 05:56:50.696858000 Z :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
...@@ -290,6 +290,9 @@ Settings.cron_jobs['repository_archive_cache_worker']['job_class'] = 'Repository ...@@ -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'] ||= Settingslogic.new({})
Settings.cron_jobs['gitlab_remove_project_export_worker']['cron'] ||= '0 * * * *' Settings.cron_jobs['gitlab_remove_project_export_worker']['cron'] ||= '0 * * * *'
Settings.cron_jobs['gitlab_remove_project_export_worker']['job_class'] = 'GitlabRemoveProjectExportWorker' 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 # GitLab Shell
......
...@@ -26,4 +26,4 @@ def validate_storages ...@@ -26,4 +26,4 @@ def validate_storages
end end
end end
validate_storages unless Rails.env.test? validate_storages unless Rails.env.test? || ENV['SKIP_STORAGE_VALIDATION'] == 'true'
Rails.application.configure do |config|
config.middleware.use(Gitlab::RequestProfiler::Middleware)
end
...@@ -42,10 +42,9 @@ Rails.application.routes.draw do ...@@ -42,10 +42,9 @@ Rails.application.routes.draw do
resource :lint, only: [:show, :create] resource :lint, only: [:show, :create]
resources :projects do resources :projects, only: [:index, :show] do
member do member do
get :status, to: 'projects#badge' get :status, to: 'projects#badge'
get :integration
end end
end end
...@@ -144,13 +143,13 @@ Rails.application.routes.draw do ...@@ -144,13 +143,13 @@ Rails.application.routes.draw do
get :jobs get :jobs
end end
resource :gitlab, only: [:create, :new], controller: :gitlab do resource :gitlab, only: [:create], controller: :gitlab do
get :status get :status
get :callback get :callback
get :jobs get :jobs
end end
resource :bitbucket, only: [:create, :new], controller: :bitbucket do resource :bitbucket, only: [:create], controller: :bitbucket do
get :status get :status
get :callback get :callback
get :jobs get :jobs
...@@ -243,7 +242,6 @@ Rails.application.routes.draw do ...@@ -243,7 +242,6 @@ Rails.application.routes.draw do
get :projects get :projects
get :keys get :keys
get :groups get :groups
put :team_update
put :block put :block
put :unblock put :unblock
put :unlock put :unlock
...@@ -281,6 +279,7 @@ Rails.application.routes.draw do ...@@ -281,6 +279,7 @@ Rails.application.routes.draw do
resource :health_check, controller: 'health_check', only: [:show] resource :health_check, controller: 'health_check', only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show] resource :background_jobs, controller: 'background_jobs', only: [:show]
resource :system_info, controller: 'system_info', 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 resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
root to: 'projects#index', as: :projects root to: 'projects#index', as: :projects
...@@ -300,7 +299,7 @@ Rails.application.routes.draw do ...@@ -300,7 +299,7 @@ Rails.application.routes.draw do
end end
end end
resource :appearances, path: 'appearance' do resource :appearances, only: [:show, :create, :update], path: 'appearance' do
member do member do
get :preview get :preview
delete :logo delete :logo
...@@ -309,7 +308,7 @@ Rails.application.routes.draw do ...@@ -309,7 +308,7 @@ Rails.application.routes.draw do
end end
resource :application_settings, only: [:show, :update] do resource :application_settings, only: [:show, :update] do
resources :services resources :services, only: [:index, :edit, :update]
put :reset_runners_token put :reset_runners_token
put :reset_health_check_token put :reset_health_check_token
put :clear_repository_check_states put :clear_repository_check_states
...@@ -346,7 +345,7 @@ Rails.application.routes.draw do ...@@ -346,7 +345,7 @@ Rails.application.routes.draw do
end end
scope module: :profiles do scope module: :profiles do
resource :account, only: [:show, :update] do resource :account, only: [:show] do
member do member do
delete :unlink delete :unlink
end end
...@@ -358,7 +357,7 @@ Rails.application.routes.draw do ...@@ -358,7 +357,7 @@ Rails.application.routes.draw do
end end
end end
resource :preferences, only: [:show, :update] resource :preferences, only: [:show, :update]
resources :keys resources :keys, only: [:index, :show, :new, :create, :destroy]
resources :emails, only: [:index, :create, :destroy] resources :emails, only: [:index, :create, :destroy]
resource :avatar, only: [:destroy] resource :avatar, only: [:destroy]
...@@ -660,7 +659,7 @@ Rails.application.routes.draw do ...@@ -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' post '/wikis/*id/markdown_preview', to: 'wikis#markdown_preview', constraints: WIKI_SLUG_ID, as: 'wiki_markdown_preview'
end end
resource :repository, only: [:show, :create] do resource :repository, only: [:create] do
member do member do
get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex }
end end
...@@ -789,7 +788,7 @@ Rails.application.routes.draw do ...@@ -789,7 +788,7 @@ Rails.application.routes.draw do
end end
end end
resources :labels, constraints: { id: /\d+/ } do resources :labels, except: [:show], constraints: { id: /\d+/ } do
collection do collection do
post :generate post :generate
post :set_priorities post :set_priorities
...@@ -814,7 +813,7 @@ Rails.application.routes.draw do ...@@ -814,7 +813,7 @@ Rails.application.routes.draw do
end end
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 collection do
delete :leave delete :leave
......
...@@ -20,7 +20,6 @@ Sidekiq::Testing.inline! do ...@@ -20,7 +20,6 @@ Sidekiq::Testing.inline! do
'https://github.com/airbnb/javascript.git', 'https://github.com/airbnb/javascript.git',
'https://github.com/tessalt/echo-chamber-js.git', 'https://github.com/tessalt/echo-chamber-js.git',
'https://github.com/atom/atom.git', 'https://github.com/atom/atom.git',
'https://github.com/ipselon/react-ui-builder.git',
'https://github.com/mattermost/platform.git', 'https://github.com/mattermost/platform.git',
'https://github.com/purifycss/purifycss.git', 'https://github.com/purifycss/purifycss.git',
'https://github.com/facebook/nuclide.git', 'https://github.com/facebook/nuclide.git',
......
...@@ -81,6 +81,11 @@ Example response: ...@@ -81,6 +81,11 @@ Example response:
"parent_ids": [ "parent_ids": [
"ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba" "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
], ],
"stats": {
"additions": 15,
"deletions": 10,
"total": 25
},
"status": "running" "status": "running"
} }
``` ```
......
# Akismet # 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 leverages [Akismet](http://akismet.com) to protect against spam. Currently
GitLab uses Akismet to prevent users who are not members of a project from GitLab uses Akismet to prevent the creation of spam issues on public projects. Issues
creating spam via the GitLab API. Detected spam will be rejected, and created via the WebUI or the API can be submitted to Akismet for review.
an entry in the "Spam Log" section in the Admin page will be created.
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 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 adding a user to a project will disable the Akismet check and prevent this
......
# 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.
...@@ -46,7 +46,7 @@ sudo -u git -H git checkout 8-10-stable-ee ...@@ -46,7 +46,7 @@ sudo -u git -H git checkout 8-10-stable-ee
```bash ```bash
cd /home/git/gitlab-shell cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags 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 ### 5. Update gitlab-workhorse
......
if ENV['SIMPLECOV'] require './spec/simplecov_env'
require 'simplecov' SimpleCovEnv.start!
end
ENV['RAILS_ENV'] = 'test' ENV['RAILS_ENV'] = 'test'
require './config/environment' require './config/environment'
......
...@@ -149,8 +149,13 @@ module API ...@@ -149,8 +149,13 @@ module API
expose :safe_message, as: :message expose :safe_message, as: :message
end end
class RepoCommitStats < Grape::Entity
expose :additions, :deletions, :total
end
class RepoCommitDetail < RepoCommit class RepoCommitDetail < RepoCommit
expose :parent_ids, :committed_date, :authored_date expose :parent_ids, :committed_date, :authored_date
expose :stats, using: Entities::RepoCommitStats
expose :status expose :status
end end
......
...@@ -21,17 +21,6 @@ module API ...@@ -21,17 +21,6 @@ module API
def filter_issues_milestone(issues, milestone) def filter_issues_milestone(issues, milestone)
issues.includes(:milestone).where('milestones.title' => milestone) issues.includes(:milestone).where('milestones.title' => milestone)
end 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 end
resource :issues do resource :issues do
...@@ -168,15 +157,13 @@ module API ...@@ -168,15 +157,13 @@ module API
end end
project = user_project 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) issue = ::Issues::CreateService.new(project, current_user, attrs.merge(request: request, api: true)).execute
create_spam_log(project, current_user, attrs)
if issue.spam?
render_api_error!({ error: 'Spam detected' }, 400) render_api_error!({ error: 'Spam detected' }, 400)
end end
issue = ::Issues::CreateService.new(project, current_user, attrs).execute
if issue.valid? if issue.valid?
# Find or create labels and attach to issue. Labels are valid because # 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 # we already checked its name, so there can't be an error here
......
...@@ -7,6 +7,7 @@ module Gitlab ...@@ -7,6 +7,7 @@ module Gitlab
module Access module Access
class AccessDeniedError < StandardError; end class AccessDeniedError < StandardError; end
NO_ACCESS = 0
GUEST = 10 GUEST = 10
REPORTER = 20 REPORTER = 20
DEVELOPER = 30 DEVELOPER = 30
......
...@@ -17,8 +17,8 @@ module Gitlab ...@@ -17,8 +17,8 @@ module Gitlab
env['HTTP_USER_AGENT'] env['HTTP_USER_AGENT']
end end
def check_for_spam?(project, user) def check_for_spam?(project)
akismet_enabled? && !project.team.member?(user) akismet_enabled? && project.public?
end end
def is_spam?(environment, user, text) def is_spam?(environment, user, text)
......
...@@ -60,16 +60,18 @@ module Gitlab ...@@ -60,16 +60,18 @@ module Gitlab
end end
# Fork repository to new namespace # Fork repository to new namespace
# storage - project's storage path # forked_from_storage - forked-from project's storage path
# path - project path with namespace # path - project path with namespace
# forked_to_storage - forked-to project's storage path
# fork_namespace - namespace for forked project # fork_namespace - namespace for forked project
# #
# Ex. # 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', 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 end
# Remove repository from file system # Remove repository from file system
......
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
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
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
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
require 'gitlab/email/handler'
# Inspired in great part by Discourse's Email::Receiver # Inspired in great part by Discourse's Email::Receiver
module Gitlab module Gitlab
module Email module Email
class Receiver class ProcessingError < StandardError; end
class ProcessingError < StandardError; end class EmailUnparsableError < ProcessingError; end
class EmailUnparsableError < ProcessingError; end class SentNotificationNotFoundError < ProcessingError; end
class SentNotificationNotFoundError < ProcessingError; end class ProjectNotFound < ProcessingError; end
class EmptyEmailError < ProcessingError; end class EmptyEmailError < ProcessingError; end
class AutoGeneratedEmailError < ProcessingError; end class AutoGeneratedEmailError < ProcessingError; end
class UserNotFoundError < ProcessingError; end class UserNotFoundError < ProcessingError; end
class UserBlockedError < ProcessingError; end class UserBlockedError < ProcessingError; end
class UserNotAuthorizedError < ProcessingError; end class UserNotAuthorizedError < ProcessingError; end
class NoteableNotFoundError < ProcessingError; end class NoteableNotFoundError < ProcessingError; end
class InvalidNoteError < ProcessingError; end class InvalidNoteError < ProcessingError; end
class InvalidIssueError < ProcessingError; end
class UnknownIncomingEmail < ProcessingError; end
class Receiver
def initialize(raw) def initialize(raw)
@raw = raw @raw = raw
end end
...@@ -20,91 +26,38 @@ module Gitlab ...@@ -20,91 +26,38 @@ module Gitlab
def execute def execute
raise EmptyEmailError if @raw.blank? raise EmptyEmailError if @raw.blank?
raise SentNotificationNotFoundError unless sent_notification mail = build_mail
mail_key = extract_mail_key(mail)
raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/ handler = Handler.for(mail, mail_key)
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)
unless note.persisted? raise UnknownIncomingEmail unless handler
msg = "The comment could not be created for the following reasons:"
note.errors.full_messages.each do |error|
msg << "\n\n- #{error}"
end
raise InvalidNoteError, msg handler.execute
end
end end
private def build_mail
Mail::Message.new(@raw)
def message rescue Encoding::UndefinedConversionError,
@message ||= Mail::Message.new(@raw) Encoding::InvalidByteSequenceError => e
rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e
raise EmailUnparsableError, e raise EmailUnparsableError, e
end end
def reply_key def extract_mail_key(mail)
key_from_to_header || key_from_additional_headers key_from_to_header(mail) || key_from_additional_headers(mail)
end end
def key_from_to_header def key_from_to_header(mail)
key = nil mail.to.find do |address|
message.to.each do |address|
key = Gitlab::IncomingEmail.key_from_address(address) key = Gitlab::IncomingEmail.key_from_address(address)
break if key break key if key
end end
key
end end
def key_from_additional_headers def key_from_additional_headers(mail)
reply_key = nil Array(mail.references).find do |mail_id|
key = Gitlab::IncomingEmail.key_from_fallback_message_id(mail_id)
Array(message.references).each do |message_id| break key if key
reply_key = Gitlab::IncomingEmail.key_from_fallback_reply_message_id(message_id)
break if reply_key
end 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 end
end end
......
module Gitlab module Gitlab
module IncomingEmail module IncomingEmail
class << self 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? def enabled?
config.enabled && config.address config.enabled && config.address
...@@ -21,8 +21,8 @@ module Gitlab ...@@ -21,8 +21,8 @@ module Gitlab
match[1] match[1]
end end
def key_from_fallback_reply_message_id(message_id) def key_from_fallback_message_id(mail_id)
match = message_id.match(FALLBACK_REPLY_MESSAGE_ID_REGEX) match = mail_id.match(FALLBACK_MESSAGE_ID_REGEX)
return unless match return unless match
match[1] match[1]
......
...@@ -124,6 +124,11 @@ module Gitlab ...@@ -124,6 +124,11 @@ module Gitlab
trans.action = action if trans trans.action = action if trans
end 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 # When enabled this should be set before being used as the usual pattern
# "@foo ||= bar" is _not_ thread-safe. # "@foo ||= bar" is _not_ thread-safe.
if enabled? if enabled?
......
...@@ -9,14 +9,17 @@ module Gitlab ...@@ -9,14 +9,17 @@ module Gitlab
# #
# Gitlab::Metrics::Instrumentation.instrument_method(User, :by_login) # Gitlab::Metrics::Instrumentation.instrument_method(User, :by_login)
module Instrumentation module Instrumentation
SERIES = 'method_calls'
PROXY_IVAR = :@__gitlab_instrumentation_proxy PROXY_IVAR = :@__gitlab_instrumentation_proxy
def self.configure def self.configure
yield self yield self
end 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. # Instruments a class method.
# #
# mod - The module to instrument as a Module/Class. # mod - The module to instrument as a Module/Class.
...@@ -141,15 +144,15 @@ module Gitlab ...@@ -141,15 +144,15 @@ module Gitlab
# generated method _only_ accepts regular arguments if the underlying # generated method _only_ accepts regular arguments if the underlying
# method also accepts them. # method also accepts them.
if method.arity == 0 if method.arity == 0
args_signature = '&block' args_signature = ''
else else
args_signature = '*args, &block' args_signature = '*args'
end end
proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1 proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1
def #{name}(#{args_signature}) def #{name}(#{args_signature})
if trans = Gitlab::Metrics::Instrumentation.transaction if trans = Gitlab::Metrics::Instrumentation.transaction
trans.measure_method(#{label.inspect}) { super } trans.method_call_for(#{label.to_sym.inspect}).measure { super }
else else
super super
end end
......
...@@ -11,8 +11,8 @@ module Gitlab ...@@ -11,8 +11,8 @@ module Gitlab
def initialize(name, series) def initialize(name, series)
@name = name @name = name
@series = series @series = series
@real_time = 0.0 @real_time = 0
@cpu_time = 0.0 @cpu_time = 0
@call_count = 0 @call_count = 0
end end
......
...@@ -35,12 +35,12 @@ module Gitlab ...@@ -35,12 +35,12 @@ module Gitlab
if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID) if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)
def self.cpu_time def self.cpu_time
Process. Process.
clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond).to_f clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond)
end end
else else
def self.cpu_time def self.cpu_time
Process. Process.
clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond).to_f clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond)
end end
end end
...@@ -48,14 +48,14 @@ module Gitlab ...@@ -48,14 +48,14 @@ module Gitlab
# #
# Returns the time as a Float. # Returns the time as a Float.
def self.real_time(precision = :millisecond) def self.real_time(precision = :millisecond)
Process.clock_gettime(Process::CLOCK_REALTIME, precision).to_f Process.clock_gettime(Process::CLOCK_REALTIME, precision)
end end
# Returns the current monotonic clock time in a given precision. # Returns the current monotonic clock time in a given precision.
# #
# Returns the time as a Float. # Returns the time as a Float.
def self.monotonic_time(precision = :millisecond) 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 end
end end
......
...@@ -52,23 +52,16 @@ module Gitlab ...@@ -52,23 +52,16 @@ module Gitlab
end end
def add_metric(series, values, tags = {}) def add_metric(series, values, tags = {})
@metrics << Metric.new("#{series_prefix}#{series}", values, tags) @metrics << Metric.new("#{Metrics.series_prefix}#{series}", values, tags)
end end
# Measures the time it takes to execute a method. # Returns a MethodCall object for the given name.
# def method_call_for(name)
# Multiple calls to the same method add up to the total runtime of the unless method = @methods[name]
# method. @methods[name] = method = MethodCall.new(name, Instrumentation.series)
#
# 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)
end end
@methods[name].measure(&block) method
end end
def increment(name, value) def increment(name, value)
...@@ -115,14 +108,6 @@ module Gitlab ...@@ -115,14 +108,6 @@ module Gitlab
Metrics.submit_metrics(submit_hashes) Metrics.submit_metrics(submit_hashes)
end end
def sidekiq?
Sidekiq.server?
end
def series_prefix
sidekiq? ? 'sidekiq_' : 'rails_'
end
end end
end end
end end
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
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
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
...@@ -7,5 +7,5 @@ end ...@@ -7,5 +7,5 @@ end
unless Rails.env.production? unless Rails.env.production?
desc "GitLab | Run all tests on CI with simplecov" 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 end
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <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> <title>The page you're looking for could not be found (404)</title>
<style> <style>
body { body {
color: #666; color: #666;
text-align: center; text-align: center;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: 0; margin: auto;
width: 800px; font-size: 14px;
margin: auto; }
font-size: 14px;
}
h1 { h1 {
font-size: 56px; font-size: 56px;
line-height: 100px; line-height: 100px;
font-weight: normal; font-weight: normal;
color: #456; color: #456;
} }
h2 { h2 {
font-size: 24px; font-size: 24px;
color: #666; color: #666;
line-height: 1.5em; line-height: 1.5em;
} }
h3 { h3 {
color: #456; color: #456;
font-size: 20px; font-size: 20px;
font-weight: normal; font-weight: normal;
line-height: 28px; line-height: 28px;
} }
hr { hr {
margin: 18px 0; max-width: 800px;
border: 0; margin: 18px auto;
border-top: 1px solid #EEE; border: 0;
border-bottom: 1px solid white; border-top: 1px solid #EEE;
} border-bottom: 1px solid white;
}
img {
max-width: 40vw;
}
.container {
margin: auto 20px;
}
</style> </style>
</head> </head>
<body> <body>
<h1> <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 404
</h1> </h1>
<h3>The page you're looking for could not be found.</h3> <div class="container">
<hr/> <h3>The page you're looking for could not be found.</h3>
<p>Make sure the address is correct and that the page hasn't moved.</p> <hr />
<p>Please contact your GitLab administrator if you think this is a mistake.</p> <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> </body>
</html> </html>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>The change you requested was rejected (422)</title> <title>The change you requested was rejected (422)</title>
<style> <style>
body { body {
color: #666; color: #666;
text-align: center; text-align: center;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: 0; margin: auto;
width: 800px; font-size: 14px;
margin: auto; }
font-size: 14px;
}
h1 { h1 {
font-size: 56px; font-size: 56px;
line-height: 100px; line-height: 100px;
font-weight: normal; font-weight: normal;
color: #456; color: #456;
} }
h2 { h2 {
font-size: 24px; font-size: 24px;
color: #666; color: #666;
line-height: 1.5em; 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 { img {
color: #456; max-width: 40vw;
font-size: 20px; }
font-weight: normal;
line-height: 28px;
}
hr { .container {
margin: 18px 0; margin: auto 20px;
border: 0; }
border-top: 1px solid #EEE; </style>
border-bottom: 1px solid white;
}
</style>
</head> </head>
<body> <body>
<h1> <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 422
</h1> </h1>
<h3>The change you requested was rejected.</h3> <div class="container">
<hr /> <h3>The change you requested was rejected.</h3>
<p>Make sure you have access to the thing you tried to change.</p> <hr />
<p>Please contact your GitLab administrator if you think this is a mistake.</p> <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> </body>
</html> </html>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>Something went wrong (500)</title> <title>Something went wrong (500)</title>
<style> <style>
body { body {
color: #666; color: #666;
text-align: center; text-align: center;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: 0; margin: auto;
width: 800px; font-size: 14px;
margin: auto; }
font-size: 14px;
}
h1 { h1 {
font-size: 56px; font-size: 56px;
line-height: 100px; line-height: 100px;
font-weight: normal; font-weight: normal;
color: #456; color: #456;
} }
h2 { h2 {
font-size: 24px; font-size: 24px;
color: #666; color: #666;
line-height: 1.5em; line-height: 1.5em;
} }
h3 { h3 {
color: #456; color: #456;
font-size: 20px; font-size: 20px;
font-weight: normal; font-weight: normal;
line-height: 28px; line-height: 28px;
} }
hr { hr {
margin: 18px 0; max-width: 800px;
margin: 18px auto;
border: 0; border: 0;
border-top: 1px solid #EEE; border-top: 1px solid #EEE;
border-bottom: 1px solid white; border-bottom: 1px solid white;
} }
img {
max-width: 40vw;
}
.container {
margin: auto 20px;
}
</style> </style>
</head> </head>
<body> <body>
<h1> <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 500
</h1> </h1>
<h3>Whoops, something went wrong on our end.</h3> <div class="container">
<hr/> <h3>Whoops, something went wrong on our end.</h3>
<p>Try refreshing the page, or going back and attempting the action again.</p> <hr />
<p>Please contact your GitLab administrator if this problem persists.</p> <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> </body>
</html> </html>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>GitLab is not responding (502)</title> <title>GitLab is not responding (502)</title>
<style> <style>
body { body {
color: #666; color: #666;
text-align: center; text-align: center;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: 0;
width: 800px;
margin: auto; margin: auto;
font-size: 14px; font-size: 14px;
} }
...@@ -34,21 +33,33 @@ ...@@ -34,21 +33,33 @@
} }
hr { hr {
margin: 18px 0; max-width: 800px;
margin: 18px auto;
border: 0; border: 0;
border-top: 1px solid #EEE; border-top: 1px solid #EEE;
border-bottom: 1px solid white; border-bottom: 1px solid white;
} }
img {
max-width: 40vw;
}
.container {
margin: auto 20px;
}
</style> </style>
</head> </head>
<body> <body>
<h1> <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 502
</h1> </h1>
<h3>Whoops, GitLab is taking too much time to respond.</h3> <div class="container">
<hr/> <h3>Whoops, GitLab is taking too much time to respond.</h3>
<p>Try refreshing the page, or going back and attempting the action again.</p> <hr />
<p>Please contact your GitLab administrator if this problem persists.</p> <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> </body>
</html> </html>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>GitLab is not responding (503)</title> <title>GitLab is not responding (503)</title>
<style> <style>
body { body {
color: #666; color: #666;
text-align: center; text-align: center;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: 0;
width: 800px;
margin: auto; margin: auto;
font-size: 14px; font-size: 14px;
} }
...@@ -34,21 +33,33 @@ ...@@ -34,21 +33,33 @@
} }
hr { hr {
margin: 18px 0; max-width: 800px;
margin: 18px auto;
border: 0; border: 0;
border-top: 1px solid #EEE; border-top: 1px solid #EEE;
border-bottom: 1px solid white; border-bottom: 1px solid white;
} }
img {
max-width: 40vw;
}
.container {
margin: auto 20px;
}
</style> </style>
</head> </head>
<body> <body>
<h1> <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 503
</h1> </h1>
<h3>Whoops, GitLab is currently unavailable.</h3> <div class="container">
<hr/> <h3>Whoops, GitLab is currently unavailable.</h3>
<p>Try refreshing the page, or going back and attempting the action again.</p> <hr />
<p>Please contact your GitLab administrator if this problem persists.</p> <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> </body>
</html> </html>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Deploy in progress</title> <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<style> <title>Deploy in progress</title>
body { <style>
color: #666; body {
text-align: center; color: #666;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; text-align: center;
margin: 0; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
width: 800px; margin: auto;
margin: auto; font-size: 14px;
font-size: 14px; }
}
h1 { h1 {
font-size: 56px; font-size: 56px;
line-height: 100px; line-height: 100px;
font-weight: normal; font-weight: normal;
color: #456; color: #456;
} }
h2 { h2 {
font-size: 24px; font-size: 24px;
color: #666; color: #666;
line-height: 1.5em; line-height: 1.5em;
} }
h3 { h3 {
color: #456; color: #456;
font-size: 20px; font-size: 20px;
font-weight: normal; font-weight: normal;
line-height: 28px; line-height: 28px;
} }
hr { hr {
margin: 18px 0; max-width: 800px;
border: 0; margin: 18px auto;
border-top: 1px solid #EEE; border: 0;
border-bottom: 1px solid white; border-top: 1px solid #EEE;
} border-bottom: 1px solid white;
</style> }
</head>
<body> img {
<h1> max-width: 40vw;
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br /> }
Deploy in progress
</h1> .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> <h3>Please try again in a few minutes.</h3>
<hr/> <hr />
<p>Please contact your GitLab administrator if this problem persists.</p> <p>Please contact your GitLab administrator if this problem persists.</p>
</body> </div>
</body>
</html> </html>
#!/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!
...@@ -163,4 +163,17 @@ describe AutocompleteController do ...@@ -163,4 +163,17 @@ describe AutocompleteController do
expect(body.collect { |u| u['id'] }).not_to include(99999) expect(body.collect { |u| u['id'] }).not_to include(99999)
end end
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 end
...@@ -243,6 +243,37 @@ describe Projects::IssuesController do ...@@ -243,6 +243,37 @@ describe Projects::IssuesController do
end end
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 describe "DELETE #destroy" do
context "when the user is a developer" do context "when the user is a developer" do
before { sign_in(user) } before { sign_in(user) }
......
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
...@@ -5,7 +5,8 @@ FactoryGirl.define do ...@@ -5,7 +5,8 @@ FactoryGirl.define do
variables do variables do
{ {
TRIGGER_KEY: 'TRIGGER_VALUE' TRIGGER_KEY_1: 'TRIGGER_VALUE_1',
TRIGGER_KEY_2: 'TRIGGER_VALUE_2'
} }
end end
end end
......
...@@ -524,6 +524,35 @@ describe 'Issues', feature: true do ...@@ -524,6 +524,35 @@ describe 'Issues', feature: true do
end end
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 describe 'due date' do
context 'update due on issue#show', js: true do context 'update due on issue#show', js: true do
let(:issue) { create(:issue, project: project, author: @user, assignee: @user) } let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
......
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
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
...@@ -16,7 +16,7 @@ describe BranchesFinder do ...@@ -16,7 +16,7 @@ describe BranchesFinder do
end end
it 'sorts by recently_updated' do 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 result = branches_finder.execute
...@@ -24,7 +24,7 @@ describe BranchesFinder do ...@@ -24,7 +24,7 @@ describe BranchesFinder do
end end
it 'sorts by last_updated' do 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 result = branches_finder.execute
...@@ -53,7 +53,7 @@ describe BranchesFinder do ...@@ -53,7 +53,7 @@ describe BranchesFinder do
context 'filter and sort' do context 'filter and sort' do
it 'filters branches by name and sorts by recently_updated' 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) branches_finder = described_class.new(repository, params)
result = branches_finder.execute result = branches_finder.execute
...@@ -63,7 +63,7 @@ describe BranchesFinder do ...@@ -63,7 +63,7 @@ describe BranchesFinder do
end end
it 'filters branches by name and sorts by last_updated' do 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) branches_finder = described_class.new(repository, params)
result = branches_finder.execute result = branches_finder.execute
......
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.
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
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
require "spec_helper" require "spec_helper"
describe NotesHelper do describe NotesHelper do
describe "#notes_max_access_for_users" do let(:owner) { create(:owner) }
let(:owner) { create(:owner) } let(:group) { create(:group) }
let(:group) { create(:group) } let(:project) { create(:empty_project, namespace: group) }
let(:project) { create(:empty_project, namespace: group) } let(:master) { create(:user) }
let(:master) { create(:user) } let(:reporter) { create(:user) }
let(:reporter) { create(:user) } let(:guest) { 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
it 'return human access levels' do let(:owner_note) { create(:note, author: owner, project: project) }
original_method = project.team.method(:human_max_access) let(:master_note) { create(:note, author: master, project: project) }
expect_any_instance_of(ProjectTeam).to receive(:human_max_access).exactly(3).times do |*args| let(:reporter_note) { create(:note, author: reporter, project: project) }
original_method.call(args[1]) let!(:notes) { [owner_note, master_note, reporter_note] }
end
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(owner_note)).to eq('Owner')
expect(helper.note_max_access_for_user(master_note)).to eq('Master') expect(helper.note_max_access_for_user(master_note)).to eq('Master')
expect(helper.note_max_access_for_user(reporter_note)).to eq('Reporter') 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 end
it 'handles access in different projects' do it 'handles access in different projects' do
...@@ -43,4 +36,16 @@ describe NotesHelper do ...@@ -43,4 +36,16 @@ describe NotesHelper do
expect(helper.note_max_access_for_user(other_note)).to eq('Reporter') expect(helper.note_max_access_for_user(other_note)).to eq('Reporter')
end end
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 end
require 'spec_helper' require 'spec_helper'
describe Gitlab::AkismetHelper, type: :helper do describe Gitlab::AkismetHelper, type: :helper do
let(:project) { create(:project) } let(:project) { create(:project, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
...@@ -11,13 +11,13 @@ describe Gitlab::AkismetHelper, type: :helper do ...@@ -11,13 +11,13 @@ describe Gitlab::AkismetHelper, type: :helper do
end end
describe '#check_for_spam?' do describe '#check_for_spam?' do
it 'returns true for non-member' do it 'returns true for public project' do
expect(helper.check_for_spam?(project, user)).to eq(true) expect(helper.check_for_spam?(project)).to eq(true)
end end
it 'returns false for member' do it 'returns false for private project' do
project.team << [user, :guest] project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
expect(helper.check_for_spam?(project, user)).to eq(false) expect(helper.check_for_spam?(project)).to eq(false)
end end
end end
......
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
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
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
require "spec_helper" require 'spec_helper'
require_relative 'email_shared_blocks'
describe Gitlab::Email::Receiver, lib: true do describe Gitlab::Email::Receiver, lib: true do
before do include_context :email_shared_context
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
context "when no sent notificiation for the reply key could be found" do context "when we cannot find a capable handler" do
let(:email_raw) { fixture_file('emails/wrong_reply_key.eml') } let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "!!!") }
it "raises a SentNotificationNotFoundError" do it "raises a UnknownIncomingEmail" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError) expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
end end
end end
...@@ -36,128 +16,7 @@ describe Gitlab::Email::Receiver, lib: true do ...@@ -36,128 +16,7 @@ describe Gitlab::Email::Receiver, lib: true do
let(:email_raw) { "" } let(:email_raw) { "" }
it "raises an EmptyEmailError" do it "raises an EmptyEmailError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError) expect { receiver.execute }.to raise_error(Gitlab::Email::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
end end
end end
end end
...@@ -43,9 +43,9 @@ describe Gitlab::IncomingEmail, lib: true do ...@@ -43,9 +43,9 @@ describe Gitlab::IncomingEmail, lib: true do
end end
end end
context 'self.key_from_fallback_reply_message_id' do context 'self.key_from_fallback_message_id' do
it 'returns reply key' 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 end
end end
...@@ -39,6 +39,12 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -39,6 +39,12 @@ describe Gitlab::Metrics::Instrumentation do
allow(@dummy).to receive(:name).and_return('Dummy') allow(@dummy).to receive(:name).and_return('Dummy')
end end
describe '.series' do
it 'returns a String' do
expect(described_class.series).to be_an_instance_of(String)
end
end
describe '.configure' do describe '.configure' do
it 'yields self' do it 'yields self' do
described_class.configure do |c| described_class.configure do |c|
...@@ -78,8 +84,7 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -78,8 +84,7 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction). allow(described_class).to receive(:transaction).
and_return(transaction) and_return(transaction)
expect(transaction).to receive(:measure_method). expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure)
with('Dummy.foo')
@dummy.foo @dummy.foo
end end
...@@ -157,8 +162,7 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -157,8 +162,7 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction). allow(described_class).to receive(:transaction).
and_return(transaction) and_return(transaction)
expect(transaction).to receive(:measure_method). expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure)
with('Dummy#bar')
@dummy.new.bar @dummy.new.bar
end end
......
...@@ -28,20 +28,20 @@ describe Gitlab::Metrics::System do ...@@ -28,20 +28,20 @@ describe Gitlab::Metrics::System do
end end
describe '.cpu_time' do describe '.cpu_time' do
it 'returns a Float' do it 'returns a Fixnum' do
expect(described_class.cpu_time).to be_an_instance_of(Float) expect(described_class.cpu_time).to be_an_instance_of(Fixnum)
end end
end end
describe '.real_time' do describe '.real_time' do
it 'returns a Float' do it 'returns a Fixnum' do
expect(described_class.real_time).to be_an_instance_of(Float) expect(described_class.real_time).to be_an_instance_of(Fixnum)
end end
end end
describe '.monotonic_time' do describe '.monotonic_time' do
it 'returns a Float' do it 'returns a Fixnum' do
expect(described_class.monotonic_time).to be_an_instance_of(Float) expect(described_class.monotonic_time).to be_an_instance_of(Fixnum)
end end
end end
end end
...@@ -46,19 +46,11 @@ describe Gitlab::Metrics::Transaction do ...@@ -46,19 +46,11 @@ describe Gitlab::Metrics::Transaction do
end end
end end
describe '#measure_method' do describe '#method_call_for' do
it 'adds a new method if it does not exist already' do it 'returns a MethodCall' do
transaction.measure_method('Foo#bar') { 'foo' } method = transaction.method_call_for('Foo#bar')
expect(transaction.methods['Foo#bar']). expect(method).to be_an_instance_of(Gitlab::Metrics::MethodCall)
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)
end end
end end
......
...@@ -147,4 +147,10 @@ describe Gitlab::Metrics do ...@@ -147,4 +147,10 @@ describe Gitlab::Metrics do
end end
end 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 end
require 'spec_helper' require 'spec_helper'
describe Ability, lib: true do 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 describe '.users_that_can_read_project' do
context 'using a public project' do context 'using a public project' do
it 'returns all the users' do it 'returns all the users' do
......
...@@ -259,7 +259,7 @@ describe Ci::Build, models: true do ...@@ -259,7 +259,7 @@ describe Ci::Build, models: true do
let(:trigger) { create(:ci_trigger, project: project) } let(:trigger) { create(:ci_trigger, project: project) }
let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) } let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
let(:user_trigger_variable) do let(:user_trigger_variable) do
{ key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false } { key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false }
end end
let(:predefined_trigger_variable) do let(:predefined_trigger_variable) do
{ key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true }
......
...@@ -79,6 +79,18 @@ describe Member, models: true do ...@@ -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 } @accepted_request_member = project.requesters.find_by(user_id: accepted_request_user.id).tap { |m| m.accept_request }
end 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 describe '.invite' do
it { expect(described_class.invite).not_to include @master } it { expect(described_class.invite).not_to include @master }
it { expect(described_class.invite).to include @invited_member } it { expect(described_class.invite).to include @invited_member }
......
...@@ -340,18 +340,36 @@ describe HipchatService, models: true do ...@@ -340,18 +340,36 @@ describe HipchatService, models: true do
end end
context "#message_options" do context "#message_options" do
it "should be set to the defaults" do it "is set to the defaults" do
expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'yellow' }) expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'yellow' })
end end
it "should set notfiy to true" do it "sets notify to true" do
allow(hipchat).to receive(:notify).and_return('1') 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 end
it "should set the color" do it "sets the color" do
allow(hipchat).to receive(:color).and_return('red') 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 end
end end
......
...@@ -245,6 +245,34 @@ describe Project, models: true do ...@@ -245,6 +245,34 @@ describe Project, models: true do
end end
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 describe 'last_activity methods' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:last_event) { double(created_at: Time.now) } let(:last_event) { double(created_at: Time.now) }
...@@ -372,6 +400,24 @@ describe Project, models: true do ...@@ -372,6 +400,24 @@ describe Project, models: true do
it { expect(@project.to_param).to eq('gitlabhq') } it { expect(@project.to_param).to eq('gitlabhq') }
end 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 end
describe '#repository' do describe '#repository' do
...@@ -1244,6 +1290,32 @@ describe Project, models: true do ...@@ -1244,6 +1290,32 @@ describe Project, models: true do
end end
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 describe '.where_paths_in' do
context 'without any paths' do context 'without any paths' do
it 'returns an empty relation' do it 'returns an empty relation' do
......
...@@ -151,8 +151,8 @@ describe ProjectTeam, models: true do ...@@ -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(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(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(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(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
it { expect(project.team.max_member_access(requester.id)).to be_nil } it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
end end
context 'when project is shared with group' do context 'when project is shared with group' do
...@@ -168,14 +168,14 @@ describe ProjectTeam, models: true 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(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(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(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
it { expect(project.team.max_member_access(requester.id)).to be_nil } it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
context 'but share_with_group_lock is true' do context 'but share_with_group_lock is true' do
before { project.namespace.update(share_with_group_lock: true) } 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(master.id)).to eq(Gitlab::Access::NO_ACCESS) }
it { expect(project.team.max_member_access(reporter.id)).to be_nil } it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::NO_ACCESS) }
end end
end end
end end
...@@ -194,8 +194,53 @@ describe ProjectTeam, models: true do ...@@ -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(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(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(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(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
it { expect(project.team.max_member_access(requester.id)).to be_nil } 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 end
end end
...@@ -446,6 +446,43 @@ describe Repository, models: true do ...@@ -446,6 +446,43 @@ describe Repository, models: true do
end.to raise_error(GitHooksService::PreReceiveError) end.to raise_error(GitHooksService::PreReceiveError)
end end
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 end
describe '#exists?' do describe '#exists?' do
......
...@@ -73,9 +73,13 @@ describe API::API, api: true do ...@@ -73,9 +73,13 @@ describe API::API, api: true do
context "authorized user" do context "authorized user" do
it "should return a commit by sha" do it "should return a commit by sha" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['id']).to eq(project.repository.commit.id) expect(json_response['id']).to eq(project.repository.commit.id)
expect(json_response['title']).to eq(project.repository.commit.title) 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 end
it "should return a 404 error if not found" do it "should return a 404 error if not found" do
......
...@@ -531,10 +531,8 @@ describe API::API, api: true do ...@@ -531,10 +531,8 @@ describe API::API, api: true do
describe 'POST /projects/:id/issues with spam filtering' do describe 'POST /projects/:id/issues with spam filtering' do
before do before do
Grape::Endpoint.before_each do |endpoint| allow_any_instance_of(Gitlab::AkismetHelper).to receive(:check_for_spam?).and_return(true)
allow(endpoint).to receive(:check_for_spam?).and_return(true) allow_any_instance_of(Gitlab::AkismetHelper).to receive(:is_spam?).and_return(true)
allow(endpoint).to receive(:is_spam?).and_return(true)
end
end end
let(:params) do let(:params) do
......
...@@ -98,7 +98,7 @@ describe Ci::API::API do ...@@ -98,7 +98,7 @@ describe Ci::API::API do
{ "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true }, { "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true }, { "key" => "DB_NAME", "value" => "postgres", "public" => true },
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }, { "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 end
......
require 'spec_helper' 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 # block_admin_user PUT /admin/users/:id/block(.:format) admin/users#block
# unblock_admin_user PUT /admin/users/:id/unblock(.:format) admin/users#unblock # unblock_admin_user PUT /admin/users/:id/unblock(.:format) admin/users#unblock
# admin_users GET /admin/users(.:format) admin/users#index # admin_users GET /admin/users(.:format) admin/users#index
...@@ -11,10 +10,6 @@ require 'spec_helper' ...@@ -11,10 +10,6 @@ require 'spec_helper'
# PUT /admin/users/:id(.:format) admin/users#update # PUT /admin/users/:id(.:format) admin/users#update
# DELETE /admin/users/:id(.:format) admin/users#destroy # DELETE /admin/users/:id(.:format) admin/users#destroy
describe Admin::UsersController, "routing" do 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 it "to #block" do
expect(put("/admin/users/1/block")).to route_to('admin/users#block', id: '1') expect(put("/admin/users/1/block")).to route_to('admin/users#block', id: '1')
end end
......
...@@ -135,10 +135,6 @@ describe Projects::RepositoriesController, 'routing' do ...@@ -135,10 +135,6 @@ describe Projects::RepositoriesController, 'routing' do
it 'to #archive format:tar.bz2' 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') expect(get('/gitlab/gitlabhq/repository/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2')
end end
it 'to #show' do
expect(get('/gitlab/gitlabhq/repository')).to route_to('projects/repositories#show', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
end end
describe Projects::BranchesController, 'routing' do describe Projects::BranchesController, 'routing' do
......
...@@ -176,18 +176,10 @@ describe Profiles::KeysController, "routing" do ...@@ -176,18 +176,10 @@ describe Profiles::KeysController, "routing" do
expect(post("/profile/keys")).to route_to('profiles/keys#create') expect(post("/profile/keys")).to route_to('profiles/keys#create')
end end
it "to #edit" do
expect(get("/profile/keys/1/edit")).to route_to('profiles/keys#edit', id: '1')
end
it "to #show" do it "to #show" do
expect(get("/profile/keys/1")).to route_to('profiles/keys#show', id: '1') expect(get("/profile/keys/1")).to route_to('profiles/keys#show', id: '1')
end end
it "to #update" do
expect(put("/profile/keys/1")).to route_to('profiles/keys#update', id: '1')
end
it "to #destroy" do it "to #destroy" do
expect(delete("/profile/keys/1")).to route_to('profiles/keys#destroy', id: '1') expect(delete("/profile/keys/1")).to route_to('profiles/keys#destroy', id: '1')
end end
......
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
if ENV['SIMPLECOV'] require './spec/simplecov_env'
require 'simplecov' SimpleCovEnv.start!
SimpleCov.start :rails
end
ENV["RAILS_ENV"] ||= 'test' ENV["RAILS_ENV"] ||= 'test'
......
...@@ -44,9 +44,29 @@ describe 'projects/builds/show' do ...@@ -44,9 +44,29 @@ describe 'projects/builds/show' do
it 'shows commit title and not show commit message' do it 'shows commit title and not show commit message' do
render render
expect(rendered).to have_css('p.build-light-text.append-bottom-0', expect(rendered).to have_css('p.build-light-text.append-bottom-0',
text: /\A\n#{Regexp.escape(commit_title)}\n\Z/) text: /\A\n#{Regexp.escape(commit_title)}\n\Z/)
end end
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 end
...@@ -17,7 +17,7 @@ describe EmailReceiverWorker do ...@@ -17,7 +17,7 @@ describe EmailReceiverWorker do
context "when an error occurs" do context "when an error occurs" do
before 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 end
it "sends out a rejection email" do it "sends out a rejection email" do
......
...@@ -14,21 +14,24 @@ describe RepositoryForkWorker do ...@@ -14,21 +14,24 @@ describe RepositoryForkWorker do
describe "#perform" do describe "#perform" do
it "creates a new repository from a fork" do it "creates a new repository from a fork" do
expect(shell).to receive(:fork_repository).with( expect(shell).to receive(:fork_repository).with(
project.repository_storage_path, '/test/path',
project.path_with_namespace, project.path_with_namespace,
project.repository_storage_path,
fork_project.namespace.path fork_project.namespace.path
).and_return(true) ).and_return(true)
subject.perform( subject.perform(
project.id, project.id,
'/test/path',
project.path_with_namespace, project.path_with_namespace,
fork_project.namespace.path) fork_project.namespace.path)
end end
it 'flushes various caches' do it 'flushes various caches' do
expect(shell).to receive(:fork_repository).with( expect(shell).to receive(:fork_repository).with(
project.repository_storage_path, '/test/path',
project.path_with_namespace, project.path_with_namespace,
project.repository_storage_path,
fork_project.namespace.path fork_project.namespace.path
).and_return(true) ).and_return(true)
...@@ -38,7 +41,7 @@ describe RepositoryForkWorker do ...@@ -38,7 +41,7 @@ describe RepositoryForkWorker do
expect_any_instance_of(Repository).to receive(:expire_exists_cache). expect_any_instance_of(Repository).to receive(:expire_exists_cache).
and_call_original 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) fork_project.namespace.path)
end end
...@@ -49,6 +52,7 @@ describe RepositoryForkWorker do ...@@ -49,6 +52,7 @@ describe RepositoryForkWorker do
subject.perform( subject.perform(
project.id, project.id,
'/test/path',
project.path_with_namespace, project.path_with_namespace,
fork_project.namespace.path) fork_project.namespace.path)
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# The image already has Hex installed. You might want to consider to use `elixir:latest` # The image already has Hex installed. You might want to consider to use `elixir:latest`
image: trenpixster/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. # 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 # Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
services: services:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment