summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml3
-rw-r--r--CHANGELOG65
-rw-r--r--CONTRIBUTING.md13
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock41
-rw-r--r--README.md2
-rw-r--r--app/controllers/admin/application_controller.rb8
-rw-r--r--app/controllers/admin/impersonation_controller.rb38
-rw-r--r--app/controllers/admin/impersonations_controller.rb24
-rw-r--r--app/controllers/admin/users_controller.rb16
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/projects/commits_controller.rb2
-rw-r--r--app/controllers/projects/graphs_controller.rb4
-rw-r--r--app/controllers/projects/wikis_controller.rb4
-rw-r--r--app/controllers/registrations_controller.rb7
-rw-r--r--app/finders/snippets_finder.rb2
-rw-r--r--app/helpers/issues_helper.rb48
-rw-r--r--app/mailers/emails/notes.rb8
-rw-r--r--app/models/project_services/buildkite_service.rb4
-rw-r--r--app/models/project_services/issue_tracker_service.rb2
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/project_services/slack_service.rb2
-rw-r--r--app/models/project_snippet.rb2
-rw-r--r--app/models/repository.rb22
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/create_branch_service.rb5
-rw-r--r--app/services/create_tag_service.rb44
-rw-r--r--app/services/merge_requests/build_service.rb3
-rw-r--r--app/services/notes/create_service.rb11
-rw-r--r--app/services/wiki_pages/create_service.rb3
-rw-r--r--app/views/devise/sessions/two_factor.html.haml4
-rw-r--r--app/views/devise/shared/_signup_box.html.haml4
-rw-r--r--app/views/events/_event.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/notify/note_snippet_email.html.haml1
-rw-r--r--app/views/notify/note_snippet_email.text.erb8
-rw-r--r--app/workers/repository_check/single_repository_worker.rb34
-rwxr-xr-xbin/background_jobs2
-rwxr-xr-xbin/web4
-rw-r--r--config/initializers/devise_async.rb1
-rw-r--r--config/initializers/metrics.rb37
-rw-r--r--config/initializers/session_store.rb2
-rw-r--r--config/routes.rb6
-rw-r--r--doc/api/commits.md2
-rw-r--r--doc/api/notes.md10
-rw-r--r--doc/ci/ssh_keys/README.md2
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/performance.md258
-rw-r--r--doc/install/installation.md56
-rw-r--r--doc/update/patch_versions.md4
-rw-r--r--features/steps/project/commits/tags.rb4
-rw-r--r--features/steps/user.rb2
-rw-r--r--lib/api/commits.rb8
-rw-r--r--lib/api/helpers.rb16
-rw-r--r--lib/api/milestones.rb10
-rw-r--r--lib/api/project_snippets.rb15
-rw-r--r--lib/gitlab/backend/shell.rb18
-rw-r--r--lib/gitlab/push_data_builder.rb2
-rw-r--r--spec/controllers/admin/impersonation_controller_spec.rb19
-rw-r--r--spec/controllers/admin/impersonations_controller_spec.rb95
-rw-r--r--spec/controllers/admin/users_controller_spec.rb49
-rw-r--r--spec/features/login_spec.rb2
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb10
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb83
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb44
-rw-r--r--spec/features/signup_spec.rb24
-rw-r--r--spec/features/users_spec.rb16
-rw-r--r--spec/helpers/issues_helper_spec.rb36
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb95
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb17
-rw-r--r--spec/models/project_services/builds_email_service_spec.rb81
-rw-r--r--spec/models/project_services/campfire_service_spec.rb42
-rw-r--r--spec/models/project_services/custom_issue_tracker_service_spec.rb49
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb13
-rw-r--r--spec/models/project_services/emails_on_push_service_spec.rb17
-rw-r--r--spec/models/project_services/external_wiki_service_spec.rb (renamed from spec/models/external_wiki_service_spec.rb)17
-rw-r--r--spec/models/project_services/flowdock_service_spec.rb14
-rw-r--r--spec/models/project_services/gemnasium_service_spec.rb16
-rw-r--r--spec/models/project_services/gitlab_issue_tracker_service_spec.rb14
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb14
-rw-r--r--spec/models/project_services/irker_service_spec.rb14
-rw-r--r--spec/models/project_services/jira_service_spec.rb26
-rw-r--r--spec/models/project_services/pivotaltracker_service_spec.rb42
-rw-r--r--spec/models/project_services/pushover_service_spec.rb20
-rw-r--r--spec/models/project_services/redmine_service_spec.rb49
-rw-r--r--spec/models/project_services/slack_service_spec.rb17
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb95
-rw-r--r--spec/models/repository_spec.rb29
-rw-r--r--spec/requests/api/commits_spec.rb35
-rw-r--r--spec/requests/api/milestones_spec.rb31
-rw-r--r--spec/requests/api/notes_spec.rb41
-rw-r--r--spec/requests/api/project_snippets_spec.rb87
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/tags_spec.rb4
-rw-r--r--spec/services/create_tag_service_spec.rb53
-rw-r--r--spec/services/notification_service_spec.rb65
-rw-r--r--spec/spec_helper.rb6
-rw-r--r--spec/support/issue_tracker_service_shared_example.rb7
-rw-r--r--spec/workers/repository_check/single_repository_worker_spec.rb33
99 files changed, 1809 insertions, 521 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 83ed6c38678..9f179efa3ce 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -953,10 +953,9 @@ Performance/DoubleStartEndWith:
Performance/EndWith:
Enabled: false
-# TODO: Enable LstripRstrip Cop.
# Use `strip` instead of `lstrip.rstrip`.
Performance/LstripRstrip:
- Enabled: false
+ Enabled: true
# TODO: Enable RangeInclude Cop.
# Use `Range#cover?` instead of `Range#include?`.
diff --git a/CHANGELOG b/CHANGELOG
index f1f93a3cb0a..b2f5c283a1a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,21 +4,29 @@ v 8.8.0 (unreleased)
- Project#open_branches has been cleaned up and no longer loads entire records into memory.
- Make build status canceled if any of the jobs was canceled and none failed
- Remove future dates from contribution calendar graph.
+ - Support e-mail notifications for comments on project snippets
- Use ActionDispatch Remote IP for Akismet checking
- Fix error when visiting commit builds page before build was updated
- Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project
- Updated search UI
- Display informative message when new milestone is created
- - Replace Devise Async with Devise ActiveJob integration. !3902 (Connor Shea)
- Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea)
- Added button to toggle whitespaces changes on diff view
- Backport GitLab Enterprise support from EE
+ - Create tags using Rugged for performance reasons. !3745
- Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718
- Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes)
- Added multiple colors for labels in dropdowns when dups happen.
+ - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea)
+ - API support for the 'since' and 'until' operators on commit requests (Paco Guzman)
+
+v 8.7.3
+ - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented
v 8.7.2
- The "New Branch" button is now loaded asynchronously
+ - Fix error 500 when trying to create a wiki page
+ - Updated spacing between notification label and button
v 8.7.1
- Throttle the update of `project.last_activity_at` to 1 minute. !3848
@@ -146,6 +154,19 @@ v 8.7.0
- Add RAW build trace output and button on build page
- Add incremental build trace update into CI API
+v 8.6.8
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via Git branch and tag names
+ - Prevent XSS via custom issue tracker URL
+ - Prevent XSS via `window.opener`
+ - Prevent XSS via label drop-down
+ - Prevent information disclosure via milestone API
+ - Prevent information disclosure via snippet API
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
v 8.6.7
- Fix persistent XSS vulnerability in `commit_person_link` helper
- Fix persistent XSS vulnerability in Label and Milestone dropdowns
@@ -287,6 +308,17 @@ v 8.6.0
- Trigger a todo for mentions on commits page
- Let project owners and admins soft delete issues and merge requests
+v 8.5.12
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via Git branch and tag names
+ - Prevent XSS via custom issue tracker URL
+ - Prevent XSS via `window.opener`
+ - Prevent information disclosure via snippet API
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
v 8.5.11
- Fix persistent XSS vulnerability in `commit_person_link` helper
@@ -437,6 +469,17 @@ v 8.5.0
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
- Add Todos
+v 8.4.10
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via Git branch and tag names
+ - Prevent XSS via custom issue tracker URL
+ - Prevent XSS via `window.opener`
+ - Prevent information disclosure via snippet API
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
v 8.4.9
- Fix persistent XSS vulnerability in `commit_person_link` helper
@@ -562,6 +605,15 @@ v 8.4.0
- Add IP check against DNSBLs at account sign-up
- Added cache:key to .gitlab-ci.yml allowing to fine tune the caching
+v 8.3.9
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via custom issue tracker URL
+ - Prevent XSS via `window.opener`
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
v 8.3.8
- Fix persistent XSS vulnerability in `commit_person_link` helper
@@ -671,6 +723,17 @@ v 8.3.0
- Expose Git's version in the admin area
- Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye)
+v 8.2.5
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via `window.opener`
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
+v 8.2.4
+ - Bump Git version requirement to 2.7.4
+
v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu)
- Fix Error 500s when creating global milestones with Unicode characters (Stan Hu)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 084c2d616a9..9fe4cf7b0f6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -142,6 +142,16 @@ code snippet right after your description in a new line: `~"feature proposal"`.
Please keep feature proposals as small and simple as possible, complex ones
might be edited to make them small and simple.
+You are encouraged to use the template below for feature proposals.
+
+```
+## Description including problem, use cases, benefits, and/or goals
+
+## Proposal
+
+## Links / references
+```
+
For changes in the interface, it can be helpful to create a mockup first.
If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab.
@@ -349,7 +359,7 @@ on your merge request feel free to mention one of the Merge Marshalls in the
Please ensure that your merge request meets the contribution acceptance criteria.
When having your code reviewed and when reviewing merge requests please take the
-[Thoughtbot code review guide] into account.
+[code review guidelines](doc/development/code_review.md) into account.
### Merge request description format
@@ -523,4 +533,3 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
[`gitlab1.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/gitlab1.atype/
-[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
diff --git a/Gemfile b/Gemfile
index 25c13fda575..c3fcd8d3f24 100644
--- a/Gemfile
+++ b/Gemfile
@@ -20,6 +20,7 @@ gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
gem 'devise', '~> 3.5.4'
gem 'doorkeeper', '~> 3.1'
+gem 'devise-async', '~> 0.9.0'
gem 'omniauth', '~> 1.3.1'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
@@ -269,7 +270,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails', '~> 4.6.0'
- gem 'rspec-rails', '~> 3.3.0'
+ gem 'rspec-rails', '~> 3.4.0'
gem 'rspec-retry'
gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index b1e954e0884..2d20a30e677 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -164,6 +164,8 @@ GEM
responders
thread_safe (~> 0.1)
warden (~> 1.2.3)
+ devise-async (0.9.0)
+ devise (~> 3.2)
devise-two-factor (2.0.1)
activesupport
attr_encrypted (~> 1.3.2)
@@ -351,7 +353,7 @@ GEM
posix-spawn (~> 0.3)
gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1)
- gitlab_git (10.0.1)
+ gitlab_git (10.0.2)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
@@ -662,29 +664,29 @@ GEM
chunky_png
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
- rspec (3.3.0)
- rspec-core (~> 3.3.0)
- rspec-expectations (~> 3.3.0)
- rspec-mocks (~> 3.3.0)
- rspec-core (3.3.2)
- rspec-support (~> 3.3.0)
- rspec-expectations (3.3.1)
+ rspec (3.4.0)
+ rspec-core (~> 3.4.0)
+ rspec-expectations (~> 3.4.0)
+ rspec-mocks (~> 3.4.0)
+ rspec-core (3.4.4)
+ rspec-support (~> 3.4.0)
+ rspec-expectations (3.4.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.3.0)
- rspec-mocks (3.3.2)
+ rspec-support (~> 3.4.0)
+ rspec-mocks (3.4.1)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.3.0)
- rspec-rails (3.3.3)
+ rspec-support (~> 3.4.0)
+ rspec-rails (3.4.2)
actionpack (>= 3.0, < 4.3)
activesupport (>= 3.0, < 4.3)
railties (>= 3.0, < 4.3)
- rspec-core (~> 3.3.0)
- rspec-expectations (~> 3.3.0)
- rspec-mocks (~> 3.3.0)
- rspec-support (~> 3.3.0)
+ rspec-core (~> 3.4.0)
+ rspec-expectations (~> 3.4.0)
+ rspec-mocks (~> 3.4.0)
+ rspec-support (~> 3.4.0)
rspec-retry (0.4.5)
rspec-core
- rspec-support (3.3.0)
+ rspec-support (3.4.1)
rubocop (0.38.0)
parser (>= 2.3.0.6, < 3.0)
powerpack (~> 0.1)
@@ -920,6 +922,7 @@ DEPENDENCIES
database_cleaner (~> 1.4.0)
default_value_for (~> 3.0.0)
devise (~> 3.5.4)
+ devise-async (~> 0.9.0)
devise-two-factor (~> 2.0.0)
diffy (~> 3.0.3)
doorkeeper (~> 3.1)
@@ -1011,7 +1014,7 @@ DEPENDENCIES
responders (~> 2.0)
rouge (~> 1.10.1)
rqrcode-rails3 (~> 0.1.7)
- rspec-rails (~> 3.3.0)
+ rspec-rails (~> 3.4.0)
rspec-retry
rubocop (~> 0.38.0)
ruby-fogbugz (~> 0.2.1)
@@ -1058,4 +1061,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.11.2
+ 1.12.1
diff --git a/README.md b/README.md
index afa60116ebb..36f4bb12df0 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,7 @@ There are a lot of [third-party applications integrating with GitLab](https://ab
## GitLab release cycle
-For more information about the release process see the [release documentation](http://doc.gitlab.com/ce/release/).
+For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/release-tools/blob/master/README.md).
## Upgrading
diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb
index 9083bfb41cf..cf795d977ce 100644
--- a/app/controllers/admin/application_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -6,12 +6,6 @@ class Admin::ApplicationController < ApplicationController
layout 'admin'
def authenticate_admin!
- return render_404 unless current_user.is_admin?
- end
-
- def authorize_impersonator!
- if session[:impersonator_id]
- User.find_by!(username: session[:impersonator_id]).admin?
- end
+ render_404 unless current_user.is_admin?
end
end
diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb
deleted file mode 100644
index bf98af78615..00000000000
--- a/app/controllers/admin/impersonation_controller.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-class Admin::ImpersonationController < Admin::ApplicationController
- skip_before_action :authenticate_admin!, only: :destroy
-
- before_action :user
- before_action :authorize_impersonator!
-
- def create
- if @user.blocked?
- flash[:alert] = "You cannot impersonate a blocked user"
-
- redirect_to admin_user_path(@user)
- else
- session[:impersonator_id] = current_user.username
- session[:impersonator_return_to] = admin_user_path(@user)
-
- warden.set_user(user, scope: 'user')
-
- flash[:alert] = "You are impersonating #{user.username}."
-
- redirect_to root_path
- end
- end
-
- def destroy
- redirect = session[:impersonator_return_to]
-
- warden.set_user(user, scope: 'user')
-
- session[:impersonator_return_to] = nil
- session[:impersonator_id] = nil
-
- redirect_to redirect || root_path
- end
-
- def user
- @user ||= User.find_by!(username: params[:id] || session[:impersonator_id])
- end
-end
diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb
new file mode 100644
index 00000000000..2db824c87ef
--- /dev/null
+++ b/app/controllers/admin/impersonations_controller.rb
@@ -0,0 +1,24 @@
+class Admin::ImpersonationsController < Admin::ApplicationController
+ skip_before_action :authenticate_admin!
+ before_action :authenticate_impersonator!
+
+ def destroy
+ original_user = current_user
+
+ warden.set_user(impersonator, scope: :user)
+
+ session[:impersonator_id] = nil
+
+ redirect_to admin_user_path(original_user)
+ end
+
+ private
+
+ def impersonator
+ @impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
+ end
+
+ def authenticate_impersonator!
+ render_404 unless impersonator && impersonator.is_admin? && !impersonator.blocked?
+ end
+end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 9abf08d0e19..b8976fa09a9 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -31,6 +31,22 @@ class Admin::UsersController < Admin::ApplicationController
user
end
+ def impersonate
+ if user.blocked?
+ flash[:alert] = "You cannot impersonate a blocked user"
+
+ redirect_to admin_user_path(user)
+ else
+ session[:impersonator_id] = current_user.id
+
+ warden.set_user(user, scope: :user)
+
+ flash[:alert] = "You are now impersonating #{user.username}"
+
+ redirect_to root_path
+ end
+ end
+
def block
if user.block
redirect_back_or_admin_user(notice: "Successfully blocked")
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1c53b0b21a3..17b3f49aed1 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -117,7 +117,7 @@ class ApplicationController < ActionController::Base
end
def after_sign_out_path_for(resource)
- current_application_settings.after_sign_out_path || new_user_session_path
+ current_application_settings.after_sign_out_path.presence || new_user_session_path
end
def abilities
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 1420b96840c..a52c614b259 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -15,7 +15,7 @@ class Projects::CommitsController < Projects::ApplicationController
if search.present?
@repository.find_commits_by_message(search, @ref, @path, @limit, @offset).compact
else
- @repository.commits(@ref, @path, @limit, @offset)
+ @repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
end
@note_counts = project.notes.where(commit_id: @commits.map(&:id)).
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index d13ea9f34b6..092ef32e6e3 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -17,7 +17,7 @@ class Projects::GraphsController < Projects::ApplicationController
end
def commits
- @commits = @project.repository.commits(@ref, nil, 2000, 0, true)
+ @commits = @project.repository.commits(@ref, limit: 2000, skip_merges: true)
@commits_graph = Gitlab::Graphs::Commits.new(@commits)
@commits_per_week_days = @commits_graph.commits_per_week_days
@commits_per_time = @commits_graph.commits_per_time
@@ -55,7 +55,7 @@ class Projects::GraphsController < Projects::ApplicationController
private
def fetch_graph
- @commits = @project.repository.commits(@ref, nil, 6000, 0, true)
+ @commits = @project.repository.commits(@ref, limit: 6000, skip_merges: true)
@log = []
@commits.each do |commit|
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index c02bc28acef..0d6c32fabd2 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -40,10 +40,10 @@ class Projects::WikisController < Projects::ApplicationController
end
def update
- @page = @project_wiki.find_page(params[:id])
-
return render('empty') unless can?(current_user, :create_wiki, @project)
+ @page = @project_wiki.find_page(params[:id])
+
if @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page)
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page),
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 059b88e2253..352bff19383 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -8,6 +8,13 @@ class RegistrationsController < Devise::RegistrationsController
def create
if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
+ # To avoid duplicate form fields on the login page, the registration form
+ # names fields using `new_user`, but Devise still wants the params in
+ # `user`.
+ if params["new_#{resource_name}"].present? && params[resource_name].blank?
+ params[resource_name] = params.delete(:"new_#{resource_name}")
+ end
+
super
else
flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code."
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index a41172816b8..01cbf91c658 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -51,7 +51,7 @@ class SnippetsFinder
snippets = project.snippets.fresh
if current_user
- if project.team.member?(current_user.id)
+ if project.team.member?(current_user.id) || current_user.admin?
snippets
else
snippets.public_and_internal
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index afe1e11a0da..198d39455d7 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -16,31 +16,49 @@ module IssuesHelper
def url_for_project_issues(project = @project, options = {})
return '' if project.nil?
- if options[:only_path]
- project.issues_tracker.project_path
- else
- project.issues_tracker.project_url
- end
+ url =
+ if options[:only_path]
+ project.issues_tracker.project_path
+ else
+ project.issues_tracker.project_url
+ end
+
+ # Ensure we return a valid URL to prevent possible XSS.
+ URI.parse(url).to_s
+ rescue URI::InvalidURIError
+ ''
end
def url_for_new_issue(project = @project, options = {})
return '' if project.nil?
- if options[:only_path]
- project.issues_tracker.new_issue_path
- else
- project.issues_tracker.new_issue_url
- end
+ url =
+ if options[:only_path]
+ project.issues_tracker.new_issue_path
+ else
+ project.issues_tracker.new_issue_url
+ end
+
+ # Ensure we return a valid URL to prevent possible XSS.
+ URI.parse(url).to_s
+ rescue URI::InvalidURIError
+ ''
end
def url_for_issue(issue_iid, project = @project, options = {})
return '' if project.nil?
- if options[:only_path]
- project.issues_tracker.issue_path(issue_iid)
- else
- project.issues_tracker.issue_url(issue_iid)
- end
+ url =
+ if options[:only_path]
+ project.issues_tracker.issue_path(issue_iid)
+ else
+ project.issues_tracker.issue_url(issue_iid)
+ end
+
+ # Ensure we return a valid URL to prevent possible XSS.
+ URI.parse(url).to_s
+ rescue URI::InvalidURIError
+ ''
end
def bulk_update_milestone_options
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index cdc40b81ee1..96116e916dd 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -28,6 +28,14 @@ module Emails
mail_answer_thread(@merge_request, note_thread_options(recipient_id))
end
+ def note_snippet_email(recipient_id, note_id)
+ setup_note_mail(note_id, recipient_id)
+
+ @snippet = @note.noteable
+ @target_url = namespace_project_snippet_url(*note_target_url_options)
+ mail_answer_thread(@snippet, note_thread_options(recipient_id))
+ end
+
private
def note_target_url_options
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 3efbfd2eec3..861cc974ec4 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -26,7 +26,7 @@ class BuildkiteService < CiService
prop_accessor :project_url, :token, :enable_ssl_verification
- validates :project_url, presence: true, if: :activated?
+ validates :project_url, presence: true, url: true, if: :activated?
validates :token, presence: true, if: :activated?
after_save :compose_service_hook, if: :activated?
@@ -91,7 +91,7 @@ class BuildkiteService < CiService
{ type: 'text',
name: 'project_url',
placeholder: "#{ENDPOINT}/example/project" },
-
+
{ type: 'checkbox',
name: 'enable_ssl_verification',
title: "Enable SSL verification" }
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 25045224ce5..c5501e06411 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -21,7 +21,7 @@
class IssueTrackerService < Service
- validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated?
+ validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated?
default_value_for :category, 'issue_tracker'
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 1ed42c4f3e7..b4418ba9284 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -28,6 +28,8 @@ class JiraService < IssueTrackerService
prop_accessor :username, :password, :api_url, :jira_issue_transition_id,
:title, :description, :project_url, :issues_url, :new_issue_url
+ validates :api_url, presence: true, url: true, if: :activated?
+
before_validation :set_api_url, :set_jira_issue_transition_id
before_update :reset_password
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index fd65027f084..7092b757549 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -22,7 +22,7 @@
class SlackService < Service
prop_accessor :webhook, :username, :channel
boolean_accessor :notify_only_broken_builds
- validates :webhook, presence: true, if: :activated?
+ validates :webhook, presence: true, url: true, if: :activated?
def initialize_properties
if properties.nil?
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index 1f7d85a5f3d..d48f0546159 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -22,4 +22,6 @@ class ProjectSnippet < Snippet
# Scopes
scope :fresh, -> { order("created_at DESC") }
+
+ participant :author, :notes
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index b4319297e43..7aebfe279fb 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -87,13 +87,15 @@ class Repository
nil
end
- def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
+ def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil)
options = {
repo: raw_repository,
ref: ref,
path: path,
limit: limit,
offset: offset,
+ after: after,
+ before: before,
# --follow doesn't play well with --skip. See:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
follow: false,
@@ -146,10 +148,20 @@ class Repository
find_branch(branch_name)
end
- def add_tag(tag_name, ref, message = nil)
- before_push_tag
+ def add_tag(user, tag_name, target, message = nil)
+ oldrev = Gitlab::Git::BLANK_SHA
+ ref = Gitlab::Git::TAG_REF_PREFIX + tag_name
+ target = commit(target).try(:id)
+
+ return false unless target
+
+ options = { message: message, tagger: user_to_committer(user) } if message
+
+ GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
+ rugged.tags.create(tag_name, target, options)
+ end
- gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
+ find_tag(tag_name)
end
def rm_branch(user, branch_name)
@@ -575,7 +587,7 @@ class Repository
end
def contributors
- commits = self.commits(nil, nil, 2000, 0, true)
+ commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
commits.group_by(&:author_email).map do |email, commits|
contributor = Gitlab::Contributor.new
diff --git a/app/models/user.rb b/app/models/user.rb
index b6f405c6981..ab48f8f1960 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -91,7 +91,7 @@ class User < ActiveRecord::Base
devise :two_factor_backupable, otp_number_of_backup_codes: 10
serialize :otp_backup_codes, JSON
- devise :lockable, :recoverable, :rememberable, :trackable,
+ devise :lockable, :async, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable
attr_accessor :force_random_password
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index 707c2f7ff85..9f4481a8153 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -43,9 +43,4 @@ class CreateBranchService < BaseService
out[:branch] = branch
out
end
-
- def build_push_data(project, user, branch)
- Gitlab::PushDataBuilder.
- build(project, user, Gitlab::Git::BLANK_SHA, branch.target, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
- end
end
diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb
index 55985380d31..91ed0e354d0 100644
--- a/app/services/create_tag_service.rb
+++ b/app/services/create_tag_service.rb
@@ -1,50 +1,30 @@
require_relative 'base_service'
class CreateTagService < BaseService
- def execute(tag_name, ref, message, release_description = nil)
+ def execute(tag_name, target, message, release_description = nil)
valid_tag = Gitlab::GitRefValidator.validate(tag_name)
- if valid_tag == false
- return error('Tag name invalid')
- end
+ return error('Tag name invalid') unless valid_tag
repository = project.repository
- existing_tag = repository.find_tag(tag_name)
- if existing_tag
- return error('Tag already exists')
- end
-
message.strip! if message
- repository.add_tag(tag_name, ref, message)
- new_tag = repository.find_tag(tag_name)
+ new_tag = nil
+ begin
+ new_tag = repository.add_tag(current_user, tag_name, target, message)
+ rescue Rugged::TagError
+ return error("Tag #{tag_name} already exists")
+ rescue GitHooksService::PreReceiveError
+ return error('Tag creation was rejected by Git hook')
+ end
if new_tag
- push_data = create_push_data(project, current_user, new_tag)
- EventCreateService.new.push(project, current_user, push_data)
- project.execute_hooks(push_data.dup, :tag_push_hooks)
- project.execute_services(push_data.dup, :tag_push_hooks)
- CreateCommitBuildsService.new.execute(project, current_user, push_data)
-
if release_description
CreateReleaseService.new(@project, @current_user).
execute(tag_name, release_description)
end
-
- success(new_tag)
+ success.merge(tag: new_tag)
else
- error('Invalid reference name')
+ error("Target #{target} is invalid")
end
end
-
- def success(branch)
- out = super()
- out[:tag] = branch
- out
- end
-
- def create_push_data(project, user, tag)
- commits = [project.commit(tag.target)].compact
- Gitlab::PushDataBuilder.
- build(project, user, Gitlab::Git::BLANK_SHA, tag.target, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", commits, tag.message)
- end
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index fa34753c4fd..3544752d47a 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -7,6 +7,9 @@ module MergeRequests
merge_request.can_be_created = false
merge_request.compare_commits = []
merge_request.source_project = project unless merge_request.source_project
+
+ merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project)
+
merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 2bb312bb252..01586994813 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -5,6 +5,8 @@ module Notes
note.author = current_user
note.system = false
+ return unless valid_project?(note)
+
if note.save
# Finish the harder work in the background
NewNoteWorker.perform_in(2.seconds, note.id, params)
@@ -13,5 +15,14 @@ module Notes
note
end
+
+ private
+
+ def valid_project?(note)
+ return false unless project
+ return true if note.for_commit?
+
+ note.noteable.try(:project) == project
+ end
end
end
diff --git a/app/services/wiki_pages/create_service.rb b/app/services/wiki_pages/create_service.rb
index 988c663b9d0..24a817c06c9 100644
--- a/app/services/wiki_pages/create_service.rb
+++ b/app/services/wiki_pages/create_service.rb
@@ -1,7 +1,8 @@
module WikiPages
class CreateService < WikiPages::BaseService
def execute
- page = WikiPage.new(@project.wiki)
+ project_wiki = ProjectWiki.new(@project, current_user)
+ page = WikiPage.new(project_wiki)
if page.create(@params)
execute_hooks(page, 'create')
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index 22b2c1a186b..c9d1e454a5e 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -4,7 +4,7 @@
%h3 Two-factor Authentication
.login-body
= form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
- = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor authentication code', required: true, autofocus: true
- %p.help-block.hint If you've lost your phone, you may enter one of your recovery codes.
+ = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor Authentication code', required: true, autofocus: true
+ %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.
.prepend-top-20
= f.submit "Verify code", class: "btn btn-save"
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index e5607dacd0d..510215bb8cd 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -6,7 +6,7 @@
.login-heading
%h3 Create an account
.login-body
- = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
+ = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name)) do |f|
.devise-errors
= devise_error_messages!
%div
@@ -16,7 +16,7 @@
%div
= f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
.form-group.append-bottom-20#password-strength
- = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true
+ = f.password_field :password, class: "form-control bottom", placeholder: "Password", required: true
%div
- if current_application_settings.recaptcha_enabled
= recaptcha_tags
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 5d622582088..e4629bae0e6 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -5,7 +5,7 @@
= cache [event, current_application_settings, "v2.2"] do
- if event.author
- = link_to user_path(event.author.username) do
+ = link_to user_path(event.author) do
= image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
- else
= image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 3beb8ff7c0d..cde9e1b918b 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -15,7 +15,7 @@
- if current_user
- if session[:impersonator_id]
%li.impersonation
- = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+ = link_to admin_impersonation_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.is_admin?
%li
diff --git a/app/views/notify/note_snippet_email.html.haml b/app/views/notify/note_snippet_email.html.haml
new file mode 100644
index 00000000000..2fa2f784661
--- /dev/null
+++ b/app/views/notify/note_snippet_email.html.haml
@@ -0,0 +1 @@
+= render 'note_message'
diff --git a/app/views/notify/note_snippet_email.text.erb b/app/views/notify/note_snippet_email.text.erb
new file mode 100644
index 00000000000..4d5a406f4b0
--- /dev/null
+++ b/app/views/notify/note_snippet_email.text.erb
@@ -0,0 +1,8 @@
+New comment for Snippet <%= @snippet.id %>
+
+<%= url_for(namespace_project_snippet_url(@snippet.project.namespace, @snippet.project, @snippet, anchor: "note_#{@note.id}")) %>
+
+
+Author: <%= @note.author_name %>
+
+<%= @note.note %>
diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb
index a76729e3c74..f2d12ba5a7d 100644
--- a/app/workers/repository_check/single_repository_worker.rb
+++ b/app/workers/repository_check/single_repository_worker.rb
@@ -1,9 +1,9 @@
module RepositoryCheck
class SingleRepositoryWorker
include Sidekiq::Worker
-
+
sidekiq_options retry: false
-
+
def perform(project_id)
project = Project.find(project_id)
project.update_columns(
@@ -11,20 +11,32 @@ module RepositoryCheck
last_repository_check_at: Time.now,
)
end
-
+
private
-
+
def check(project)
- repositories = [project.repository]
- repositories << project.wiki.repository if project.wiki_enabled?
- # Use 'map do', not 'all? do', to prevent short-circuiting
- repositories.map { |repository| git_fsck(repository.path_to_repo) }.all?
+ if !git_fsck(project.repository)
+ false
+ elsif project.wiki_enabled?
+ # Historically some projects never had their wiki repos initialized;
+ # this happens on project creation now. Let's initialize an empty repo
+ # if it is not already there.
+ begin
+ project.create_wiki
+ rescue Rugged::RepositoryError
+ end
+
+ git_fsck(project.wiki.repository)
+ else
+ true
+ end
end
-
- def git_fsck(path)
+
+ def git_fsck(repository)
+ path = repository.path_to_repo
cmd = %W(nice git --git-dir=#{path} fsck)
output, status = Gitlab::Popen.popen(cmd)
-
+
if status.zero?
true
else
diff --git a/bin/background_jobs b/bin/background_jobs
index 1f67d732949..25a578a1c49 100755
--- a/bin/background_jobs
+++ b/bin/background_jobs
@@ -37,7 +37,7 @@ start_no_deamonize()
start_sidekiq()
{
- bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile "$@"
+ exec bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile "$@"
}
load_ok()
diff --git a/bin/web b/bin/web
index 03fe7a6354b..ecd0bbd10b0 100755
--- a/bin/web
+++ b/bin/web
@@ -19,12 +19,12 @@ get_unicorn_pid()
start()
{
- $unicorn_cmd -D
+ exec $unicorn_cmd -D
}
start_foreground()
{
- $unicorn_cmd
+ exec $unicorn_cmd
}
stop()
diff --git a/config/initializers/devise_async.rb b/config/initializers/devise_async.rb
new file mode 100644
index 00000000000..05a1852cdbd
--- /dev/null
+++ b/config/initializers/devise_async.rb
@@ -0,0 +1 @@
+Devise::Async.backend = :sidekiq
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index 283936d0efc..b2d08d87bac 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -61,12 +61,30 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(const)
end
- Dir[Rails.root.join('app', 'finders', '*.rb')].each do |path|
- const = File.basename(path, '.rb').camelize.constantize
-
- config.instrument_instance_methods(const)
+ # Path to search => prefix to strip from constant
+ paths_to_instrument = {
+ ['app', 'finders'] => ['app', 'finders'],
+ ['app', 'mailers', 'emails'] => ['app', 'mailers'],
+ ['app', 'services', '**'] => ['app', 'services'],
+ ['lib', 'gitlab', 'diff'] => ['lib'],
+ ['lib', 'gitlab', 'email', 'message'] => ['lib']
+ }
+
+ paths_to_instrument.each do |(path, prefix)|
+ prefix = Rails.root.join(*prefix)
+
+ Dir[Rails.root.join(*path + ['*.rb'])].each do |file_path|
+ path = Pathname.new(file_path).relative_path_from(prefix)
+ const = path.to_s.sub('.rb', '').camelize.constantize
+
+ config.instrument_methods(const)
+ config.instrument_instance_methods(const)
+ end
end
+ config.instrument_methods(Premailer::Adapter::Nokogiri)
+ config.instrument_instance_methods(Premailer::Adapter::Nokogiri)
+
[
:Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository,
:Tag, :TagCollection, :Tree
@@ -97,17 +115,6 @@ if Gitlab::Metrics.enabled?
config.instrument_methods(Gitlab::ReferenceExtractor)
config.instrument_instance_methods(Gitlab::ReferenceExtractor)
- # Instrument all service classes
- services = Rails.root.join('app', 'services')
-
- Dir[services.join('**', '*.rb')].each do |file_path|
- path = Pathname.new(file_path).relative_path_from(services)
- const = path.to_s.sub('.rb', '').camelize.constantize
-
- config.instrument_methods(const)
- config.instrument_instance_methods(const)
- end
-
# Instrument the classes used for checking if somebody has push access.
config.instrument_instance_methods(Gitlab::GitAccess)
config.instrument_instance_methods(Gitlab::GitAccessWiki)
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 88cb859871c..599dabb9e50 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -22,7 +22,7 @@ else
key: '_gitlab_session',
secure: Gitlab.config.gitlab.https,
httponly: true,
- expire_after: Settings.gitlab['session_expire_delay'] * 60,
+ expires_in: Settings.gitlab['session_expire_delay'] * 60,
path: (Rails.application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root
)
end
diff --git a/config/routes.rb b/config/routes.rb
index 5ce1f49ec6a..dafecc94648 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -214,8 +214,6 @@ Rails.application.routes.draw do
resources :keys, only: [:show, :destroy]
resources :identities, except: [:show]
- delete 'stop_impersonation' => 'impersonation#destroy', on: :collection
-
member do
get :projects
get :keys
@@ -225,12 +223,14 @@ Rails.application.routes.draw do
put :unblock
put :unlock
put :confirm
- post 'impersonate' => 'impersonation#create'
+ post :impersonate
patch :disable_two_factor
delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
end
end
+ resource :impersonation, only: :destroy
+
resources :abuse_reports, only: [:index, :destroy]
resources :spam_logs, only: [:index, :destroy]
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 6341440c58b..57c2e1d9b87 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -12,6 +12,8 @@ GET /projects/:id/repository/commits
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
+| `since` | string | no | Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
+| `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits"
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 7aa1c2155bf..a6b5b1787fd 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -15,7 +15,7 @@ GET /projects/:id/issues/:issue_id/notes
Parameters:
- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of an issue
+- `issue_id` (required) - The IID of an issue (not ID)
```json
[
@@ -73,7 +73,7 @@ GET /projects/:id/issues/:issue_id/notes/:note_id
Parameters:
- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of a project issue
+- `issue_id` (required) - The IID of a project issue (not ID)
- `note_id` (required) - The ID of an issue note
### Create new issue note
@@ -87,7 +87,7 @@ POST /projects/:id/issues/:issue_id/notes
Parameters:
- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of an issue
+- `issue_id` (required) - The IID of an issue (not ID)
- `body` (required) - The content of a note
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
@@ -102,7 +102,7 @@ PUT /projects/:id/issues/:issue_id/notes/:note_id
Parameters:
- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of an issue
+- `issue_id` (required) - The IID of an issue (not ID)
- `note_id` (required) - The ID of a note
- `body` (required) - The content of a note
@@ -120,7 +120,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of an issue |
+| `issue_id` | integer | yes | The IID of an issue |
| `note_id` | integer | yes | The ID of a note |
```bash
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index 7f825e6a065..7c0fb225dac 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -57,7 +57,7 @@ before_script:
# WARNING: Use this only with the Docker executor, if you use it with shell
# you will overwrite your user's SSH config.
- mkdir -p ~/.ssh
- - '[[ -f /.dockerinit ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
+ - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
```
As a final step, add the _public_ key from the one you created earlier to the
diff --git a/doc/development/README.md b/doc/development/README.md
index 3f3ef068f96..aa7d54c01d0 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -8,6 +8,7 @@
- [How to dump production data to staging](db_dump.md)
- [Instrumentation](instrumentation.md)
- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
+- [Performance guidelines](performance.md)
- [Rake tasks](rake_tasks.md) for development
- [Shell commands](shell_commands.md) in the GitLab codebase
- [Sidekiq debugging](sidekiq_debugging.md)
diff --git a/doc/development/performance.md b/doc/development/performance.md
new file mode 100644
index 00000000000..fb37b3a889c
--- /dev/null
+++ b/doc/development/performance.md
@@ -0,0 +1,258 @@
+# Performance Guidelines
+
+This document describes various guidelines to follow to ensure good and
+consistent performance of GitLab.
+
+## Workflow
+
+The process of solving performance problems is roughly as follows:
+
+1. Make sure there's an issue open somewhere (e.g., on the GitLab CE issue
+ tracker), create one if there isn't. See [#15607][#15607] for an example.
+2. Measure the performance of the code in a production environment such as
+ GitLab.com (see the [Tooling](#tooling) section below). Performance should be
+ measured over a period of _at least_ 24 hours.
+3. Add your findings based on the measurement period (screenshots of graphs,
+ timings, etc) to the issue mentioned in step 1.
+4. Solve the problem.
+5. Create a merge request, assign the "performance" label and ping the right
+ people (e.g. [@yorickpeterse][yorickpeterse] and [@joshfng][joshfng]).
+6. Once a change has been deployed make sure to _again_ measure for at least 24
+ hours to see if your changes have any impact on the production environment.
+7. Repeat until you're done.
+
+When providing timings make sure to provide:
+
+* The 95th percentile
+* The 99th percentile
+* The mean
+
+When providing screenshots of graphs, make sure that both the X and Y axes and
+the legend are clearly visible. If you happen to have access to GitLab.com's own
+monitoring tools you should also provide a link to any relevant
+graphs/dashboards.
+
+## Tooling
+
+GitLab provides two built-in tools to aid the process of improving performance:
+
+* [Sherlock](doc/development/profiling.md#sherlock)
+* [GitLab Performance Monitoring](doc/monitoring/performance/monitoring.md)
+
+GitLab employees can use GitLab.com's performance monitoring systems located at
+<http://performance.gitlab.net>, this requires you to log in using your
+`@gitlab.com` Email address. Non-GitLab employees are advised to set up their
+own InfluxDB + Grafana stack.
+
+## Benchmarks
+
+Benchmarks are almost always useless. Benchmarks usually only test small bits of
+code in isolation and often only measure the best case scenario. On top of that,
+benchmarks for libraries (e.g., a Gem) tend to be biased in favour of the
+library. After all there's little benefit to an author publishing a benchmark
+that shows they perform worse than their competitors.
+
+Benchmarks are only really useful when you need a rough (emphasis on "rough")
+understanding of the impact of your changes. For example, if a certain method is
+slow a benchmark can be used to see if the changes you're making have any impact
+on the method's performance. However, even when a benchmark shows your changes
+improve performance there's no guarantee the performance also improves in a
+production environment.
+
+When writing benchmarks you should almost always use
+[benchmark-ips](https://github.com/evanphx/benchmark-ips). Ruby's `Benchmark`
+module that comes with the standard library is rarely useful as it runs either a
+single iteration (when using `Benchmark.bm`) or two iterations (when using
+`Benchmark.bmbm`). Running this few iterations means external factors (e.g. a
+video streaming in the background) can very easily skew the benchmark
+statistics.
+
+Another problem with the `Benchmark` module is that it displays timings, not
+iterations. This means that if a piece of code completes in a very short period
+of time it can be very difficult to compare the timings before and after a
+certain change. This in turn leads to patterns such as the following:
+
+```ruby
+Benchmark.bmbm(10) do |bench|
+ bench.report 'do something' do
+ 100.times do
+ ... work here ...
+ end
+ end
+end
+```
+
+This however leads to the question: how many iterations should we run to get
+meaningful statistics?
+
+The benchmark-ips Gem basically takes care of all this and much more, and as a
+result of this should be used instead of the `Benchmark` module.
+
+In short:
+
+1. Don't trust benchmarks you find on the internet.
+2. Never make claims based on just benchmarks, always measure in production to
+ confirm your findings.
+3. X being N times faster than Y is meaningless if you don't know what impact it
+ will actually have on your production environment.
+4. A production environment is the _only_ benchmark that always tells the truth
+ (unless your performance monitoring systems are not set up correctly).
+5. If you must write a benchmark use the benchmark-ips Gem instead of Ruby's
+ `Benchmark` module.
+
+## Importance of Changes
+
+When working on performance improvements, it's important to always ask yourself
+the question "How important is it to improve the performance of this piece of
+code?". Not every piece of code is equally important and it would be a waste to
+spend a week trying to improve something that only impacts a tiny fraction of
+our users. For example, spending a week trying to squeeze 10 milliseconds out of
+a method is a waste of time when you could have spent a week squeezing out 10
+seconds elsewhere.
+
+There is no clear set of steps that you can follow to determine if a certain
+piece of code is worth optimizing. The only two things you can do are:
+
+1. Think about what the code does, how it's used, how many times it's called and
+ how much time is spent in it relative to the total execution time (e.g., the
+ total time spent in a web request).
+2. Ask others (preferably in the form of an issue).
+
+Some examples of changes that aren't really important/worth the effort:
+
+* Replacing double quotes with single quotes.
+* Replacing usage of Array with Set when the list of values is very small.
+* Replacing library A with library B when both only take up 0.1% of the total
+ execution time.
+* Calling `freeze` on every string (see [String Freezing](#string-freezing)).
+
+## Slow Operations & Sidekiq
+
+Slow operations (e.g. merging branches) or operations that are prone to errors
+(using external APIs) should be performed in a Sidekiq worker instead of
+directly in a web request as much as possible. This has numerous benefits such
+as:
+
+1. An error won't prevent the request from completing.
+2. The process being slow won't affect the loading time of a page.
+3. In case of a failure it's easy to re-try the process (Sidekiq takes care of
+ this automatically).
+4. By isolating the code from a web request it will hopefully be easier to test
+ and maintain.
+
+It's especially important to use Sidekiq as much as possible when dealing with
+Git operations as these operations can take quite some time to complete
+depending on the performance of the underlying storage system.
+
+## Git Operations
+
+Care should be taken to not run unnecessary Git operations. For example,
+retrieving the list of branch names using `Repository#branch_names` can be done
+without an explicit check if a repository exists or not. In other words, instead
+of this:
+
+```ruby
+if repository.exists?
+ repository.branch_names.each do |name|
+ ...
+ end
+end
+```
+
+You can just write:
+
+```ruby
+repository.branch_names.each do |name|
+ ...
+end
+```
+
+## Caching
+
+Operations that will often return the same result should be cached using Redis,
+in particular Git operations. When caching data in Redis, make sure the cache is
+flushed whenever needed. For example, a cache for the list of tags should be
+flushed whenever a new tag is pushed or a tag is removed.
+
+When adding cache expiration code for repositories, this code should be placed
+in one of the before/after hooks residing in the Repository class. For example,
+if a cache should be flushed after importing a repository this code should be
+added to `Repository#after_import`. This ensures the cache logic stays within
+the Repository class instead of leaking into other classes.
+
+When caching data, make sure to also memoize the result in an instance variable.
+While retrieving data from Redis is much faster than raw Git operations, it still
+has overhead. By caching the result in an instance variable, repeated calls to
+the same method won't end up retrieving data from Redis upon every call. When
+memoizing cached data in an instance variable, make sure to also reset the
+instance variable when flushing the cache. An example:
+
+
+```ruby
+def first_branch
+ @first_branch ||= cache.fetch(:first_branch) { branches.first }
+end
+
+def expire_first_branch_cache
+ cache.expire(:first_branch)
+ @first_branch = nil
+end
+```
+
+## Anti-Patterns
+
+This is a collection of [anti-patterns][anti-pattern] that should be avoided
+unless these changes have a measurable, significant and positive impact on
+production environments.
+
+### String Freezing
+
+In recent Ruby versions calling `freeze` on a String leads to it being allocated
+only once and re-used. For example, on Ruby 2.3 this will only allocate the
+"foo" String once:
+
+```ruby
+10.times do
+ 'foo'.freeze
+end
+```
+
+Blindly adding a `.freeze` call to every String is an anti-pattern that should
+be avoided unless one can prove (using production data) the call actually has a
+positive impact on performance.
+
+This feature of Ruby wasn't really meant to make things faster directly, instead
+it was meant to reduce the number of allocations. Depending on the size of the
+String and how frequently it would be allocated (before the `.freeze` call was
+added), this _may_ make things faster, but there's no guarantee it will.
+
+Another common flavour of this is to not only freeze a String, but also assign
+it to a constant, for example:
+
+```ruby
+SOME_CONSTANT = 'foo'.freeze
+
+9000.times do
+ SOME_CONSTANT
+end
+```
+
+The only reason you should be doing this is to prevent somebody from mutating
+the global String. However, since you can just re-assign constants in Ruby
+there's nothing stopping somebody from doing this elsewhere in the code:
+
+```ruby
+SOME_CONSTANT = 'bar'
+```
+
+### Moving Allocations to Constants
+
+Storing an object as a constant so you only allocate it once _may_ improve
+performance, but there's no guarantee this will. Looking up constants has an
+impact on runtime performance, and as such, using a constant instead of
+referencing an object directly may even slow code down.
+
+[#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607
+[yorickpeterse]: https://gitlab.com/u/yorickpeterse
+[joshfng]: https://gitlab.com/u/joshfng
+[anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern
diff --git a/doc/install/installation.md b/doc/install/installation.md
index e721e70a596..e3af3022262 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -157,22 +157,64 @@ Create a `git` user for GitLab:
## 5. Database
-We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](database_mysql.md). *Note*: because we need to make use of extensions you need at least pgsql 9.1.
+We recommend using a PostgreSQL database. For MySQL check the
+[MySQL setup guide](database_mysql.md).
- # Install the database packages
- sudo apt-get install -y postgresql postgresql-client libpq-dev
+> **Note**: because we need to make use of extensions you need at least pgsql 9.1.
- # Create a user for GitLab
+1. Install the database packages:
+
+ ```bash
+ sudo apt-get install -y postgresql postgresql-client libpq-dev postgresql-contrib
+ ```
+
+1. Create a database user for GitLab:
+
+ ```bash
sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;"
+ ```
+
+1. Create the GitLab production database and grant all privileges on database:
- # Create the GitLab production database & grant all privileges on database
+ ```bash
sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;"
+ ```
+
+1. Create the `pg_trgm` extension (required for GitLab 8.6+):
+
+ ```bash
+ sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
+ ```
+
+1. Try connecting to the new database with the new user:
- # Try connecting to the new database with the new user
+ ```bash
sudo -u git -H psql -d gitlabhq_production
+ ```
+
+1. Check if the `pg_trgm` extension is enabled:
+
+ ```bash
+ SELECT true AS enabled
+ FROM pg_available_extensions
+ WHERE name = 'pg_trgm'
+ AND installed_version IS NOT NULL;
+ ```
+
+ If the extension is enabled this will produce the following output:
- # Quit the database session
+ ```
+ enabled
+ ---------
+ t
+ (1 row)
+ ```
+
+1. Quit the database session:
+
+ ```bash
gitlabhq_production> \q
+ ```
## 6. Redis
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 60729316cde..b4283a526f3 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -57,10 +57,10 @@ sudo -u git -H make
cd /home/git/gitlab
# PostgreSQL
-sudo -u git -H bundle install --without development test mysql --deployment
+sudo -u git -H bundle install --without development test mysql --with postgres --deployment
# MySQL
-sudo -u git -H bundle install --without development test postgres --deployment
+sudo -u git -H bundle install --without development test postgres --with mysql --deployment
# Optional: clean up old gems
sudo -u git -H bundle clean
diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb
index eff4234a44a..912ba580efd 100644
--- a/features/steps/project/commits/tags.rb
+++ b/features/steps/project/commits/tags.rb
@@ -57,11 +57,11 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
end
step 'I should see new an error that tag ref is invalid' do
- expect(page).to have_content 'Invalid reference name'
+ expect(page).to have_content 'Target foo is invalid'
end
step 'I should see new an error that tag already exists' do
- expect(page).to have_content 'Tag already exists'
+ expect(page).to have_content 'Tag v1.0.0 already exists'
end
step "I visit tag 'v1.1.0' page" do
diff --git a/features/steps/user.rb b/features/steps/user.rb
index 3230234cb6d..39bbb59343b 100644
--- a/features/steps/user.rb
+++ b/features/steps/user.rb
@@ -12,7 +12,7 @@ class Spinach::Features::User < Spinach::FeatureSteps
user = User.find_by(name: 'John Doe')
project = contributed_project
- # Issue controbution
+ # Issue contribution
issue_params = { title: 'Bug in old browser' }
Issues::CreateService.new(project, user, issue_params).execute
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 4544a41b1e3..93a3a5ce089 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -12,14 +12,20 @@ module API
# Parameters:
# id (required) - The ID of a project
# ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
+ # since (optional) - Only commits after or in this date will be returned
+ # until (optional) - Only commits before or in this date will be returned
# Example Request:
# GET /projects/:id/repository/commits
get ":id/repository/commits" do
+ datetime_attributes! :since, :until
+
page = (params[:page] || 0).to_i
per_page = (params[:per_page] || 20).to_i
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ after = params[:since]
+ before = params[:until]
- commits = user_project.repository.commits(ref, nil, per_page, page * per_page)
+ commits = user_project.repository.commits(ref, limit: per_page, offset: page * per_page, after: after, before: before)
present commits, with: Entities::RepoCommit
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 5bbf721321d..40c967453fb 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -183,6 +183,22 @@ module API
Gitlab::Access.options_with_owner.values.include? level.to_i
end
+ # Checks the occurrences of datetime attributes, each attribute if present in the params hash must be in ISO 8601
+ # format (YYYY-MM-DDTHH:MM:SSZ) or a Bad Request error is invoked.
+ #
+ # Parameters:
+ # keys (required) - An array consisting of elements that must be parseable as dates from the params hash
+ def datetime_attributes!(*keys)
+ keys.each do |key|
+ begin
+ params[key] = Time.xmlschema(params[key]) if params[key].present?
+ rescue ArgumentError
+ message = "\"" + key.to_s + "\" must be a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ"
+ render_api_error!(message, 400)
+ end
+ end
+ end
+
def issuable_order_by
if params["order_by"] == 'updated_at'
'updated_at'
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index 84b4d4cdd6d..132043cf3f7 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -105,7 +105,15 @@ module API
authorize! :read_milestone, user_project
@milestone = user_project.milestones.find(params[:milestone_id])
- present paginate(@milestone.issues), with: Entities::Issue, current_user: current_user
+
+ finder_params = {
+ project_id: user_project.id,
+ milestone_title: @milestone.title,
+ state: 'all'
+ }
+
+ issues = IssuesFinder.new(current_user, finder_params).execute
+ present paginate(issues), with: Entities::Issue, current_user: current_user
end
end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 22ce3c6a066..ce1bf0d26d2 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -11,6 +11,11 @@ module API
end
not_found!
end
+
+ def snippets_for_current_user
+ finder_params = { filter: :by_project, project: user_project }
+ SnippetsFinder.new.execute(current_user, finder_params)
+ end
end
# Get a project snippets
@@ -20,7 +25,7 @@ module API
# Example Request:
# GET /projects/:id/snippets
get ":id/snippets" do
- present paginate(user_project.snippets), with: Entities::ProjectSnippet
+ present paginate(snippets_for_current_user), with: Entities::ProjectSnippet
end
# Get a project snippet
@@ -31,7 +36,7 @@ module API
# Example Request:
# GET /projects/:id/snippets/:snippet_id
get ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
+ @snippet = snippets_for_current_user.find(params[:snippet_id])
present @snippet, with: Entities::ProjectSnippet
end
@@ -73,7 +78,7 @@ module API
# Example Request:
# PUT /projects/:id/snippets/:snippet_id
put ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
+ @snippet = snippets_for_current_user.find(params[:snippet_id])
authorize! :update_project_snippet, @snippet
attrs = attributes_for_keys [:title, :file_name, :visibility_level]
@@ -97,7 +102,7 @@ module API
# DELETE /projects/:id/snippets/:snippet_id
delete ":id/snippets/:snippet_id" do
begin
- @snippet = user_project.snippets.find(params[:snippet_id])
+ @snippet = snippets_for_current_user.find(params[:snippet_id])
authorize! :update_project_snippet, @snippet
@snippet.destroy
rescue
@@ -113,7 +118,7 @@ module API
# Example Request:
# GET /projects/:id/snippets/:snippet_id/raw
get ":id/snippets/:snippet_id/raw" do
- @snippet = user_project.snippets.find(params[:snippet_id])
+ @snippet = snippets_for_current_user.find(params[:snippet_id])
env['api.format'] = :txt
content_type 'text/plain'
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 5e2fb863a8f..132f9cd1966 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -79,24 +79,6 @@ module Gitlab
'rm-project', "#{name}.git"])
end
- # Add repository tag from passed ref
- #
- # path - project path with namespace
- # tag_name - new tag name
- # ref - HEAD for new tag
- # message - optional message for tag (annotated tag)
- #
- # Ex.
- # add_tag("gitlab/gitlab-ci", "v4.0", "master")
- # add_tag("gitlab/gitlab-ci", "v4.0", "master", "message")
- #
- def add_tag(path, tag_name, ref, message = nil)
- cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git
- #{tag_name} #{ref})
- cmd << message unless message.nil? || message.empty?
- Gitlab::Utils.system_silent(cmd)
- end
-
# Gc repository
#
# path - project path with namespace
diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb
index 67622f321a6..c8f12577112 100644
--- a/lib/gitlab/push_data_builder.rb
+++ b/lib/gitlab/push_data_builder.rb
@@ -66,7 +66,7 @@ module Gitlab
# This method provide a sample data generated with
# existing project and commits to test webhooks
def build_sample(project, user)
- commits = project.repository.commits(project.default_branch, nil, 3)
+ commits = project.repository.commits(project.default_branch, limit: 3)
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
build(project, user, commits.last.id, commits.first.id, ref, commits)
end
diff --git a/spec/controllers/admin/impersonation_controller_spec.rb b/spec/controllers/admin/impersonation_controller_spec.rb
deleted file mode 100644
index d7a7ba1c5b6..00000000000
--- a/spec/controllers/admin/impersonation_controller_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-
-describe Admin::ImpersonationController do
- let(:admin) { create(:admin) }
-
- before do
- sign_in(admin)
- end
-
- describe 'CREATE #impersonation when blocked' do
- let(:blocked_user) { create(:user, state: :blocked) }
-
- it 'does not allow impersonation' do
- post :create, id: blocked_user.username
-
- expect(flash[:alert]).to eq 'You cannot impersonate a blocked user'
- end
- end
-end
diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb
new file mode 100644
index 00000000000..eb82476b179
--- /dev/null
+++ b/spec/controllers/admin/impersonations_controller_spec.rb
@@ -0,0 +1,95 @@
+require 'spec_helper'
+
+describe Admin::ImpersonationsController do
+ let(:impersonator) { create(:admin) }
+ let(:user) { create(:user) }
+
+ describe "DELETE destroy" do
+ context "when not signed in" do
+ it "redirects to the sign in page" do
+ delete :destroy
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ context "when signed in" do
+ before do
+ sign_in(user)
+ end
+
+ context "when not impersonating" do
+ it "responds with status 404" do
+ delete :destroy
+
+ expect(response.status).to eq(404)
+ end
+
+ it "doesn't sign us in" do
+ delete :destroy
+
+ expect(warden.user).to eq(user)
+ end
+ end
+
+ context "when impersonating" do
+ before do
+ session[:impersonator_id] = impersonator.id
+ end
+
+ context "when the impersonator is not admin (anymore)" do
+ before do
+ impersonator.admin = false
+ impersonator.save
+ end
+
+ it "responds with status 404" do
+ delete :destroy
+
+ expect(response.status).to eq(404)
+ end
+
+ it "doesn't sign us in as the impersonator" do
+ delete :destroy
+
+ expect(warden.user).to eq(user)
+ end
+ end
+
+ context "when the impersonator is admin" do
+ context "when the impersonator is blocked" do
+ before do
+ impersonator.block!
+ end
+
+ it "responds with status 404" do
+ delete :destroy
+
+ expect(response.status).to eq(404)
+ end
+
+ it "doesn't sign us in as the impersonator" do
+ delete :destroy
+
+ expect(warden.user).to eq(user)
+ end
+ end
+
+ context "when the impersonator is not blocked" do
+ it "redirects to the impersonated user's page" do
+ delete :destroy
+
+ expect(response).to redirect_to(admin_user_path(user))
+ end
+
+ it "signs us in as the impersonator" do
+ delete :destroy
+
+ expect(warden.user).to eq(impersonator)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 9ef8ba1b097..ce2a62ae1fd 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -2,9 +2,10 @@ require 'spec_helper'
describe Admin::UsersController do
let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
before do
- sign_in(create(:admin))
+ sign_in(admin)
end
describe 'DELETE #user with projects' do
@@ -112,4 +113,50 @@ describe Admin::UsersController do
patch :disable_two_factor, id: user.to_param
end
end
+
+ describe "POST impersonate" do
+ context "when the user is blocked" do
+ before do
+ user.block!
+ end
+
+ it "shows a notice" do
+ post :impersonate, id: user.username
+
+ expect(flash[:alert]).to eq("You cannot impersonate a blocked user")
+ end
+
+ it "doesn't sign us in as the user" do
+ post :impersonate, id: user.username
+
+ expect(warden.user).to eq(admin)
+ end
+ end
+
+ context "when the user is not blocked" do
+ it "stores the impersonator in the session" do
+ post :impersonate, id: user.username
+
+ expect(session[:impersonator_id]).to eq(admin.id)
+ end
+
+ it "signs us in as the user" do
+ post :impersonate, id: user.username
+
+ expect(warden.user).to eq(user)
+ end
+
+ it "redirects to root" do
+ post :impersonate, id: user.username
+
+ expect(response).to redirect_to(root_path)
+ end
+
+ it "shows a notice" do
+ post :impersonate, id: user.username
+
+ expect(flash[:alert]).to eq("You are now impersonating #{user.username}")
+ end
+ end
+ end
end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 4433ef2d6f1..8c38dd5b122 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -37,7 +37,7 @@ feature 'Login', feature: true do
end
def enter_code(code)
- fill_in 'Two-factor authentication code', with: code
+ fill_in 'Two-factor Authentication code', with: code
click_button 'Verify code'
end
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index 00b60bd0e75..e296078bad8 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -30,4 +30,14 @@ feature 'Create New Merge Request', feature: true, js: true do
expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
end
+
+ context 'when target project cannot be viewed by the current user' do
+ it 'does not leak the private project name & namespace' do
+ private_project = create(:project, :private)
+
+ visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id })
+
+ expect(page).not_to have_content private_project.to_reference
+ end
+ end
end
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
new file mode 100644
index 00000000000..7e6eef65873
--- /dev/null
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+feature 'Projects > Wiki > User creates wiki page', feature: true do
+ let(:user) { create(:user) }
+
+ background do
+ project.team << [user, :master]
+ login_as(user)
+
+ visit namespace_project_path(project.namespace, project)
+ click_link 'Wiki'
+ end
+
+ context 'in the user namespace' do
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ context 'when wiki is empty' do
+ scenario 'directly from the wiki home page' do
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Home')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+
+ context 'when wiki is not empty' do
+ before do
+ WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ end
+
+ scenario 'via the "new wiki page" page', js: true do
+ click_link 'New Page'
+
+ fill_in :new_wiki_path, with: 'foo'
+ click_button 'Create Page'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Foo')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+ end
+
+ context 'in a group namespace' do
+ let(:project) { create(:project, namespace: create(:group, :public)) }
+
+ context 'when wiki is empty' do
+ scenario 'directly from the wiki home page' do
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Home')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+
+ context 'when wiki is not empty' do
+ before do
+ WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ end
+
+ scenario 'via the "new wiki page" page', js: true do
+ click_link 'New Page'
+
+ fill_in :new_wiki_path, with: 'foo'
+ click_button 'Create Page'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Foo')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
new file mode 100644
index 00000000000..ef82d2375dd
--- /dev/null
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+feature 'Projects > Wiki > User updates wiki page', feature: true do
+ let(:user) { create(:user) }
+
+ background do
+ project.team << [user, :master]
+ login_as(user)
+
+ visit namespace_project_path(project.namespace, project)
+ WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ click_link 'Wiki'
+ end
+
+ context 'in the user namespace' do
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ scenario 'the home page' do
+ click_link 'Edit'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Save changes'
+
+ expect(page).to have_content('Home')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+
+ context 'in a group namespace' do
+ let(:project) { create(:project, namespace: create(:group, :public)) }
+
+ scenario 'the home page' do
+ click_link 'Edit'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Save changes'
+
+ expect(page).to have_content('Home')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+end
diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb
index 51b754ff85c..58aabd913eb 100644
--- a/spec/features/signup_spec.rb
+++ b/spec/features/signup_spec.rb
@@ -7,10 +7,10 @@ feature 'Signup', feature: true do
visit root_path
- fill_in 'user_name', with: user.name
- fill_in 'user_username', with: user.username
- fill_in 'user_email', with: user.email
- fill_in 'user_password_sign_up', with: user.password
+ fill_in 'new_user_name', with: user.name
+ fill_in 'new_user_username', with: user.username
+ fill_in 'new_user_email', with: user.email
+ fill_in 'new_user_password', with: user.password
click_button "Sign up"
expect(current_path).to eq users_almost_there_path
@@ -25,10 +25,10 @@ feature 'Signup', feature: true do
visit root_path
- fill_in 'user_name', with: user.name
- fill_in 'user_username', with: user.username
- fill_in 'user_email', with: existing_user.email
- fill_in 'user_password_sign_up', with: user.password
+ fill_in 'new_user_name', with: user.name
+ fill_in 'new_user_username', with: user.username
+ fill_in 'new_user_email', with: existing_user.email
+ fill_in 'new_user_password', with: user.password
click_button "Sign up"
expect(current_path).to eq user_registration_path
@@ -42,10 +42,10 @@ feature 'Signup', feature: true do
visit root_path
- fill_in 'user_name', with: user.name
- fill_in 'user_username', with: user.username
- fill_in 'user_email', with: existing_user.email
- fill_in 'user_password_sign_up', with: user.password
+ fill_in 'new_user_name', with: user.name
+ fill_in 'new_user_username', with: user.username
+ fill_in 'new_user_email', with: existing_user.email
+ fill_in 'new_user_password', with: user.password
click_button "Sign up"
expect(current_path).to eq user_registration_path
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index c1248162031..cf116040394 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -5,10 +5,10 @@ feature 'Users', feature: true do
scenario 'GET /users/sign_in creates a new user account' do
visit new_user_session_path
- fill_in 'user_name', with: 'Name Surname'
- fill_in 'user_username', with: 'Great'
- fill_in 'user_email', with: 'name@mail.com'
- fill_in 'user_password_sign_up', with: 'password1234'
+ fill_in 'new_user_name', with: 'Name Surname'
+ fill_in 'new_user_username', with: 'Great'
+ fill_in 'new_user_email', with: 'name@mail.com'
+ fill_in 'new_user_password', with: 'password1234'
expect { click_button 'Sign up' }.to change { User.count }.by(1)
end
@@ -31,10 +31,10 @@ feature 'Users', feature: true do
scenario 'Should show one error if email is already taken' do
visit new_user_session_path
- fill_in 'user_name', with: 'Another user name'
- fill_in 'user_username', with: 'anotheruser'
- fill_in 'user_email', with: user.email
- fill_in 'user_password_sign_up', with: '12341234'
+ fill_in 'new_user_name', with: 'Another user name'
+ fill_in 'new_user_username', with: 'anotheruser'
+ fill_in 'new_user_email', with: user.email
+ fill_in 'new_user_password', with: '12341234'
expect { click_button 'Sign up' }.to change { User.count }.by(0)
expect(page).to have_text('Email has already been taken')
expect(number_of_errors_on_page(page)).to be(1), 'errors on page:\n #{errors_on_page page}'
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 543593cf389..bffe2c18b6f 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -30,6 +30,18 @@ describe IssuesHelper do
expect(url_for_project_issues).to eq ""
end
+ it 'returns an empty string if project_url is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.project_url') { 'javascript:alert("foo");' }
+
+ expect(url_for_project_issues(project)).to eq ''
+ end
+
+ it 'returns an empty string if project_path is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.project_path') { 'javascript:alert("foo");' }
+
+ expect(url_for_project_issues(project, only_path: true)).to eq ''
+ end
+
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
@@ -68,6 +80,18 @@ describe IssuesHelper do
expect(url_for_issue(issue.iid)).to eq ""
end
+ it 'returns an empty string if issue_url is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.issue_url') { 'javascript:alert("foo");' }
+
+ expect(url_for_issue(issue.iid, project)).to eq ''
+ end
+
+ it 'returns an empty string if issue_path is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.issue_path') { 'javascript:alert("foo");' }
+
+ expect(url_for_issue(issue.iid, project, only_path: true)).to eq ''
+ end
+
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
@@ -105,6 +129,18 @@ describe IssuesHelper do
expect(url_for_new_issue).to eq ""
end
+ it 'returns an empty string if issue_url is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.new_issue_url') { 'javascript:alert("foo");' }
+
+ expect(url_for_new_issue(project)).to eq ''
+ end
+
+ it 'returns an empty string if issue_path is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.new_issue_path') { 'javascript:alert("foo");' }
+
+ expect(url_for_new_issue(project, only_path: true)).to eq ''
+ end
+
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index 31b2c90122d..e771f35811e 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -27,86 +27,51 @@ describe BambooService, models: true do
end
describe 'Validations' do
- describe '#bamboo_url' do
- it 'does not validate the presence of bamboo_url if service is not active' do
- bamboo_service = service
- bamboo_service.active = false
-
- expect(bamboo_service).not_to validate_presence_of(:bamboo_url)
- end
-
- it 'validates the presence of bamboo_url if service is active' do
- bamboo_service = service
- bamboo_service.active = true
-
- expect(bamboo_service).to validate_presence_of(:bamboo_url)
- end
- end
+ subject { service }
- describe '#build_key' do
- it 'does not validate the presence of build_key if service is not active' do
- bamboo_service = service
- bamboo_service.active = false
+ context 'when service is active' do
+ before { subject.active = true }
- expect(bamboo_service).not_to validate_presence_of(:build_key)
- end
+ it { is_expected.to validate_presence_of(:build_key) }
+ it { is_expected.to validate_presence_of(:bamboo_url) }
+ it_behaves_like 'issue tracker service URL attribute', :bamboo_url
- it 'validates the presence of build_key if service is active' do
- bamboo_service = service
- bamboo_service.active = true
+ describe '#username' do
+ it 'does not validate the presence of username if password is nil' do
+ subject.password = nil
- expect(bamboo_service).to validate_presence_of(:build_key)
- end
- end
+ expect(subject).not_to validate_presence_of(:username)
+ end
- describe '#username' do
- it 'does not validate the presence of username if service is not active' do
- bamboo_service = service
- bamboo_service.active = false
+ it 'validates the presence of username if password is present' do
+ subject.password = 'secret'
- expect(bamboo_service).not_to validate_presence_of(:username)
+ expect(subject).to validate_presence_of(:username)
+ end
end
- it 'does not validate the presence of username if username is nil' do
- bamboo_service = service
- bamboo_service.active = true
- bamboo_service.password = nil
+ describe '#password' do
+ it 'does not validate the presence of password if username is nil' do
+ subject.username = nil
- expect(bamboo_service).not_to validate_presence_of(:username)
- end
+ expect(subject).not_to validate_presence_of(:password)
+ end
- it 'validates the presence of username if service is active and username is present' do
- bamboo_service = service
- bamboo_service.active = true
- bamboo_service.password = 'secret'
+ it 'validates the presence of password if username is present' do
+ subject.username = 'john'
- expect(bamboo_service).to validate_presence_of(:username)
+ expect(subject).to validate_presence_of(:password)
+ end
end
end
- describe '#password' do
- it 'does not validate the presence of password if service is not active' do
- bamboo_service = service
- bamboo_service.active = false
-
- expect(bamboo_service).not_to validate_presence_of(:password)
- end
-
- it 'does not validate the presence of password if username is nil' do
- bamboo_service = service
- bamboo_service.active = true
- bamboo_service.username = nil
-
- expect(bamboo_service).not_to validate_presence_of(:password)
- end
-
- it 'validates the presence of password if service is active and username is present' do
- bamboo_service = service
- bamboo_service.active = true
- bamboo_service.username = 'john'
+ context 'when service is inactive' do
+ before { subject.active = false }
- expect(bamboo_service).to validate_presence_of(:password)
- end
+ it { is_expected.not_to validate_presence_of(:build_key) }
+ it { is_expected.not_to validate_presence_of(:bamboo_url) }
+ it { is_expected.not_to validate_presence_of(:username) }
+ it { is_expected.not_to validate_presence_of(:password) }
end
end
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 88cd624877a..60364df2015 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -26,6 +26,23 @@ describe BuildkiteService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:token) }
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+
describe 'commits methods' do
before do
@project = Project.new
diff --git a/spec/models/project_services/builds_email_service_spec.rb b/spec/models/project_services/builds_email_service_spec.rb
index 7c23c2efccd..236df8f047d 100644
--- a/spec/models/project_services/builds_email_service_spec.rb
+++ b/spec/models/project_services/builds_email_service_spec.rb
@@ -1,76 +1,71 @@
require 'spec_helper'
describe BuildsEmailService do
- let(:build) { create(:ci_build) }
- let(:data) { Gitlab::BuildDataBuilder.build(build) }
- let!(:project) { create(:project, :public, ci_id: 1) }
- let(:service) { described_class.new(project: project, active: true) }
+ let(:data) { Gitlab::BuildDataBuilder.build(create(:ci_build)) }
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:recipients) }
+
+ context 'when pusher is added' do
+ before { subject.add_pusher = true }
+
+ it { is_expected.not_to validate_presence_of(:recipients) }
+ end
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:recipients) }
+ end
+ end
describe '#execute' do
it 'sends email' do
- service.recipients = 'test@gitlab.com'
+ subject.recipients = 'test@gitlab.com'
data[:build_status] = 'failed'
+
expect(BuildEmailWorker).to receive(:perform_async)
- service.execute(data)
+
+ subject.execute(data)
end
it 'does not send email with succeeded build and notify_only_broken_builds on' do
- expect(service).to receive(:notify_only_broken_builds).and_return(true)
+ expect(subject).to receive(:notify_only_broken_builds).and_return(true)
data[:build_status] = 'success'
+
expect(BuildEmailWorker).not_to receive(:perform_async)
- service.execute(data)
+
+ subject.execute(data)
end
it 'does not send email with failed build and build_allow_failure is true' do
data[:build_status] = 'failed'
data[:build_allow_failure] = true
+
expect(BuildEmailWorker).not_to receive(:perform_async)
- service.execute(data)
+
+ subject.execute(data)
end
it 'does not send email with unknown build status' do
data[:build_status] = 'foo'
- expect(BuildEmailWorker).not_to receive(:perform_async)
- service.execute(data)
- end
- it 'does not send email when recipients list is empty' do
- service.recipients = ' ,, '
- data[:build_status] = 'failed'
expect(BuildEmailWorker).not_to receive(:perform_async)
- service.execute(data)
- end
- end
-
- describe 'validations' do
-
- context 'when pusher is not added' do
- before { service.add_pusher = false }
-
- it 'does not allow empty recipient input' do
- service.recipients = ''
- expect(service.valid?).to be false
- end
-
- it 'does allow non-empty recipient input' do
- service.recipients = 'test@example.com'
- expect(service.valid?).to be true
- end
+ subject.execute(data)
end
- context 'when pusher is added' do
- before { service.add_pusher = true }
+ it 'does not send email when recipients list is empty' do
+ subject.recipients = ' ,, '
+ data[:build_status] = 'failed'
- it 'does allow empty recipient input' do
- service.recipients = ''
- expect(service.valid?).to be true
- end
+ expect(BuildEmailWorker).not_to receive(:perform_async)
- it 'does allow non-empty recipient input' do
- service.recipients = 'test@example.com'
- expect(service.valid?).to be true
- end
+ subject.execute(data)
end
end
end
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
new file mode 100644
index 00000000000..3e6da42803b
--- /dev/null
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -0,0 +1,42 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe CampfireService, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+end
diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb
new file mode 100644
index 00000000000..ff976f8ec59
--- /dev/null
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -0,0 +1,49 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe CustomIssueTrackerService, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it { is_expected.to validate_presence_of(:new_issue_url) }
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ it_behaves_like 'issue tracker service URL attribute', :new_issue_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ it { is_expected.not_to validate_presence_of(:new_issue_url) }
+ end
+ end
+end
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index a2cf68a9e38..3a8e67438fc 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -28,25 +28,18 @@ describe DroneCiService, models: true do
describe 'validations' do
context 'active' do
- before { allow(subject).to receive(:activated?).and_return(true) }
+ before { subject.active = true }
it { is_expected.to validate_presence_of(:token) }
it { is_expected.to validate_presence_of(:drone_url) }
- it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
- it { is_expected.to allow_value('http://ci.example.com').for(:drone_url) }
- it { is_expected.not_to allow_value('this is not url').for(:drone_url) }
- it { is_expected.not_to allow_value('http//noturl').for(:drone_url) }
- it { is_expected.not_to allow_value('ftp://ci.example.com').for(:drone_url) }
+ it_behaves_like 'issue tracker service URL attribute', :drone_url
end
context 'inactive' do
- before { allow(subject).to receive(:activated?).and_return(false) }
+ before { subject.active = false }
it { is_expected.not_to validate_presence_of(:token) }
it { is_expected.not_to validate_presence_of(:drone_url) }
- it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
- it { is_expected.to allow_value('http://drone.example.com').for(:drone_url) }
- it { is_expected.to allow_value('ftp://drone.example.com').for(:drone_url) }
end
end
diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb
new file mode 100644
index 00000000000..e6f78898c82
--- /dev/null
+++ b/spec/models/project_services/emails_on_push_service_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe EmailsOnPushService do
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:recipients) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:recipients) }
+ end
+ end
+end
diff --git a/spec/models/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index d37978720bf..5fe5ea7d2df 100644
--- a/spec/models/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -28,13 +28,18 @@ describe ExternalWikiService, models: true do
it { should have_one :service_hook }
end
- describe "Validations" do
- context "active" do
- before do
- subject.active = true
- end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:external_wiki_url) }
+ it_behaves_like 'issue tracker service URL attribute', :external_wiki_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
- it { should validate_presence_of :external_wiki_url }
+ it { is_expected.not_to validate_presence_of(:external_wiki_url) }
end
end
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index ff7fbcaa004..b7e627e6518 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -26,6 +26,20 @@ describe FlowdockService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb
index ecb3ccb1673..a08f1ac229f 100644
--- a/spec/models/project_services/gemnasium_service_spec.rb
+++ b/spec/models/project_services/gemnasium_service_spec.rb
@@ -26,6 +26,22 @@ describe GemnasiumService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ it { is_expected.to validate_presence_of(:api_key) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ it { is_expected.not_to validate_presence_of(:api_key) }
+ end
+ end
+
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index 3518dbd1728..7a1f106d6e3 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -26,6 +26,20 @@ describe GitlabIssueTrackerService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ subject { described_class.new(project: create(:project), active: true) }
+
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ end
+
+ context 'when service is inactive' do
+ subject { described_class.new(project: create(:project), active: false) }
+
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ end
+ end
describe 'project and issue urls' do
let(:project) { create(:project) }
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index d878162a220..6fb5cad5011 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -26,6 +26,20 @@ describe HipchatService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+
describe "Execute" do
let(:hipchat) { HipchatService.new }
let(:user) { create(:user, username: 'username') }
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index b783b1a576e..4ee022a5171 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -29,14 +29,16 @@ describe IrkerService, models: true do
end
describe 'Validations' do
- before do
- subject.active = true
- subject.properties['recipients'] = _recipients
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:recipients) }
end
- context 'active' do
- let(:_recipients) { nil }
- it { should validate_presence_of :recipients }
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:recipients) }
end
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 2f8193170ae..5309cfb99ff 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -26,6 +26,30 @@ describe JiraService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:api_url) }
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it { is_expected.to validate_presence_of(:new_issue_url) }
+ it_behaves_like 'issue tracker service URL attribute', :api_url
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ it_behaves_like 'issue tracker service URL attribute', :new_issue_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:api_url) }
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ it { is_expected.not_to validate_presence_of(:new_issue_url) }
+ end
+ end
+
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
@@ -72,7 +96,7 @@ describe JiraService, models: true do
context "when a password was previously set" do
before do
- @jira_service = JiraService.create(
+ @jira_service = JiraService.create!(
project: create(:project),
properties: {
api_url: 'http://jira.example.com/rest/api/2',
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
new file mode 100644
index 00000000000..f37edd4d970
--- /dev/null
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -0,0 +1,42 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe PivotaltrackerService, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+end
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 96039f9491b..555d9757b47 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -27,14 +27,20 @@ describe PushoverService, models: true do
end
describe 'Validations' do
- context 'active' do
- before do
- subject.active = true
- end
+ context 'when service is active' do
+ before { subject.active = true }
- it { is_expected.to validate_presence_of :api_key }
- it { is_expected.to validate_presence_of :user_key }
- it { is_expected.to validate_presence_of :priority }
+ it { is_expected.to validate_presence_of(:api_key) }
+ it { is_expected.to validate_presence_of(:user_key) }
+ it { is_expected.to validate_presence_of(:priority) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:api_key) }
+ it { is_expected.not_to validate_presence_of(:user_key) }
+ it { is_expected.not_to validate_presence_of(:priority) }
end
end
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
new file mode 100644
index 00000000000..7d14f6e8280
--- /dev/null
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -0,0 +1,49 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe RedmineService, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it { is_expected.to validate_presence_of(:new_issue_url) }
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ it_behaves_like 'issue tracker service URL attribute', :new_issue_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ it { is_expected.not_to validate_presence_of(:new_issue_url) }
+ end
+ end
+end
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index 478d59be08b..a97b7560137 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -26,13 +26,18 @@ describe SlackService, models: true do
it { is_expected.to have_one :service_hook }
end
- describe "Validations" do
- context "active" do
- before do
- subject.active = true
- end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
- it { is_expected.to validate_presence_of :webhook }
+ it { is_expected.to validate_presence_of(:webhook) }
+ it_behaves_like 'issue tracker service URL attribute', :webhook
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:webhook) }
end
end
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index bc7423cee69..ad24b895170 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -27,86 +27,51 @@ describe TeamcityService, models: true do
end
describe 'Validations' do
- describe '#teamcity_url' do
- it 'does not validate the presence of teamcity_url if service is not active' do
- teamcity_service = service
- teamcity_service.active = false
-
- expect(teamcity_service).not_to validate_presence_of(:teamcity_url)
- end
-
- it 'validates the presence of teamcity_url if service is active' do
- teamcity_service = service
- teamcity_service.active = true
-
- expect(teamcity_service).to validate_presence_of(:teamcity_url)
- end
- end
+ subject { service }
- describe '#build_type' do
- it 'does not validate the presence of build_type if service is not active' do
- teamcity_service = service
- teamcity_service.active = false
+ context 'when service is active' do
+ before { subject.active = true }
- expect(teamcity_service).not_to validate_presence_of(:build_type)
- end
+ it { is_expected.to validate_presence_of(:build_type) }
+ it { is_expected.to validate_presence_of(:teamcity_url) }
+ it_behaves_like 'issue tracker service URL attribute', :teamcity_url
- it 'validates the presence of build_type if service is active' do
- teamcity_service = service
- teamcity_service.active = true
+ describe '#username' do
+ it 'does not validate the presence of username if password is nil' do
+ subject.password = nil
- expect(teamcity_service).to validate_presence_of(:build_type)
- end
- end
+ expect(subject).not_to validate_presence_of(:username)
+ end
- describe '#username' do
- it 'does not validate the presence of username if service is not active' do
- teamcity_service = service
- teamcity_service.active = false
+ it 'validates the presence of username if password is present' do
+ subject.password = 'secret'
- expect(teamcity_service).not_to validate_presence_of(:username)
+ expect(subject).to validate_presence_of(:username)
+ end
end
- it 'does not validate the presence of username if username is nil' do
- teamcity_service = service
- teamcity_service.active = true
- teamcity_service.password = nil
+ describe '#password' do
+ it 'does not validate the presence of password if username is nil' do
+ subject.username = nil
- expect(teamcity_service).not_to validate_presence_of(:username)
- end
+ expect(subject).not_to validate_presence_of(:password)
+ end
- it 'validates the presence of username if service is active and username is present' do
- teamcity_service = service
- teamcity_service.active = true
- teamcity_service.password = 'secret'
+ it 'validates the presence of password if username is present' do
+ subject.username = 'john'
- expect(teamcity_service).to validate_presence_of(:username)
+ expect(subject).to validate_presence_of(:password)
+ end
end
end
- describe '#password' do
- it 'does not validate the presence of password if service is not active' do
- teamcity_service = service
- teamcity_service.active = false
-
- expect(teamcity_service).not_to validate_presence_of(:password)
- end
-
- it 'does not validate the presence of password if username is nil' do
- teamcity_service = service
- teamcity_service.active = true
- teamcity_service.username = nil
-
- expect(teamcity_service).not_to validate_presence_of(:password)
- end
-
- it 'validates the presence of password if service is active and username is present' do
- teamcity_service = service
- teamcity_service.active = true
- teamcity_service.username = 'john'
+ context 'when service is inactive' do
+ before { subject.active = false }
- expect(teamcity_service).to validate_presence_of(:password)
- end
+ it { is_expected.not_to validate_presence_of(:build_type) }
+ it { is_expected.not_to validate_presence_of(:teamcity_url) }
+ it { is_expected.not_to validate_presence_of(:username) }
+ it { is_expected.not_to validate_presence_of(:password) }
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 397bb5a8028..34a13f9b5c9 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -561,7 +561,7 @@ describe Repository, models: true do
end
describe :skip_merged_commit do
- subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", nil, 100, 0, true).map{ |k| k.id } }
+ subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map{ |k| k.id } }
it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') }
end
@@ -858,13 +858,30 @@ describe Repository, models: true do
end
describe '#add_tag' do
- it 'adds a tag' do
- expect(repository).to receive(:before_push_tag)
+ context 'with a valid target' do
+ let(:user) { build_stubbed(:user) }
- expect_any_instance_of(Gitlab::Shell).to receive(:add_tag).
- with(repository.path_with_namespace, '8.5', 'master', 'foo')
+ it 'creates the tag using rugged' do
+ expect(repository.rugged.tags).to receive(:create).
+ with('8.5', repository.commit('master').id,
+ hash_including(message: 'foo',
+ tagger: hash_including(name: user.name, email: user.email))).
+ and_call_original
- repository.add_tag('8.5', 'master', 'foo')
+ repository.add_tag(user, '8.5', 'master', 'foo')
+ end
+
+ it 'returns a Gitlab::Git::Tag object' do
+ tag = repository.add_tag(user, '8.5', 'master', 'foo')
+
+ expect(tag).to be_a(Gitlab::Git::Tag)
+ end
+ end
+
+ context 'with an invalid target' do
+ it 'returns false' do
+ expect(repository.add_tag(user, '8.5', 'bar', 'foo')).to be false
+ end
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index e28998d51b5..cb82ca7802d 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -32,6 +32,41 @@ describe API::API, api: true do
expect(response.status).to eq(401)
end
end
+
+ context "since optional parameter" do
+ it "should return project commits since provided parameter" do
+ commits = project.repository.commits("master")
+ since = commits.second.created_at
+
+ get api("/projects/#{project.id}/repository/commits?since=#{since.utc.iso8601}", user)
+
+ expect(json_response.size).to eq 2
+ expect(json_response.first["id"]).to eq(commits.first.id)
+ expect(json_response.second["id"]).to eq(commits.second.id)
+ end
+ end
+
+ context "until optional parameter" do
+ it "should return project commits until provided parameter" do
+ commits = project.repository.commits("master")
+ before = commits.second.created_at
+
+ get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user)
+
+ expect(json_response.size).to eq(commits.size - 1)
+ expect(json_response.first["id"]).to eq(commits.second.id)
+ expect(json_response.second["id"]).to eq(commits.third.id)
+ end
+ end
+
+ context "invalid xmlschema date parameters" do
+ it "should return an invalid parameter error message" do
+ get api("/projects/#{project.id}/repository/commits?since=invalid-date", user)
+
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to include "\"since\" must be a timestamp in ISO 8601 format"
+ end
+ end
end
describe "GET /projects:id/repository/commits/:sha" do
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index 344f0fe0b7f..241995041bb 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -127,7 +127,7 @@ describe API::API, api: true do
describe 'GET /projects/:id/milestones/:milestone_id/issues' do
before do
- milestone.issues << create(:issue)
+ milestone.issues << create(:issue, project: project)
end
it 'should return project issues for a particular milestone' do
get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
@@ -140,5 +140,34 @@ describe API::API, api: true do
get api("/projects/#{project.id}/milestones/#{milestone.id}/issues")
expect(response.status).to eq(401)
end
+
+ describe 'confidential issues' do
+ let(:public_project) { create(:project, :public) }
+ let(:milestone) { create(:milestone, project: public_project) }
+ let(:issue) { create(:issue, project: public_project) }
+ let(:confidential_issue) { create(:issue, confidential: true, project: public_project) }
+ before do
+ public_project.team << [user, :developer]
+ milestone.issues << issue << confidential_issue
+ end
+
+ it 'returns confidential issues to team members' do
+ get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id)
+ end
+
+ it 'does not return confidential issues to regular users' do
+ get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user))
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response.map { |issue| issue['id'] }).to include(issue.id)
+ end
+ end
end
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index ec9eda0a2ed..49091fc0f49 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
- let!(:project) { create(:project, namespace: user.namespace ) }
+ let!(:project) { create(:project, namespace: user.namespace) }
let!(:issue) { create(:issue, project: project, author: user) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
let!(:snippet) { create(:project_snippet, project: project, author: user) }
@@ -45,7 +45,7 @@ describe API::API, api: true do
end
it "should return a 404 error when issue id not found" do
- get api("/projects/#{project.id}/issues/123/notes", user)
+ get api("/projects/#{project.id}/issues/12345/notes", user)
expect(response.status).to eq(404)
end
@@ -106,7 +106,7 @@ describe API::API, api: true do
end
it "should return a 404 error if issue note not found" do
- get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
+ get api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
expect(response.status).to eq(404)
end
@@ -134,7 +134,7 @@ describe API::API, api: true do
end
it "should return a 404 error if snippet note not found" do
- get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/123", user)
+ get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user)
expect(response.status).to eq(404)
end
end
@@ -191,6 +191,27 @@ describe API::API, api: true do
expect(response.status).to eq(401)
end
end
+
+ context 'when user does not have access to create noteable' do
+ let(:private_issue) { create(:issue, project: create(:project, :private)) }
+
+ ##
+ # We are posting to project user has access to, but we use issue id
+ # from a different project, see #15577
+ #
+ before do
+ post api("/projects/#{project.id}/issues/#{private_issue.id}/notes", user),
+ body: 'Hi!'
+ end
+
+ it 'responds with 500' do
+ expect(response.status).to eq 500
+ end
+
+ it 'does not create new note' do
+ expect(private_issue.notes.reload).to be_empty
+ end
+ end
end
describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do
@@ -211,7 +232,7 @@ describe API::API, api: true do
end
it 'should return a 404 error when note id not found' do
- put api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user),
+ put api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user),
body: 'Hello!'
expect(response.status).to eq(404)
end
@@ -233,7 +254,7 @@ describe API::API, api: true do
it 'should return a 404 error when note id not found' do
put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
- "notes/123", user), body: "Hello!"
+ "notes/12345", user), body: "Hello!"
expect(response.status).to eq(404)
end
end
@@ -248,7 +269,7 @@ describe API::API, api: true do
it 'should return a 404 error when note id not found' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
- "notes/123", user), body: "Hello!"
+ "notes/12345", user), body: "Hello!"
expect(response.status).to eq(404)
end
end
@@ -268,7 +289,7 @@ describe API::API, api: true do
end
it 'returns a 404 error when note id not found' do
- delete api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
+ delete api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
expect(response.status).to eq(404)
end
@@ -288,7 +309,7 @@ describe API::API, api: true do
it 'returns a 404 error when note id not found' do
delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\
- "notes/123", user)
+ "notes/12345", user)
expect(response.status).to eq(404)
end
@@ -308,7 +329,7 @@ describe API::API, api: true do
it 'returns a 404 error when note id not found' do
delete api("/projects/#{project.id}/merge_requests/"\
- "#{merge_request.id}/notes/123", user)
+ "#{merge_request.id}/notes/12345", user)
expect(response.status).to eq(404)
end
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 3722ddf5a33..9706d060cfa 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -15,4 +15,91 @@ describe API::API, api: true do
expect(json_response['expires_at']).to be_nil
end
end
+
+ describe 'GET /projects/:project_id/snippets/' do
+ it 'all snippets available to team member' do
+ project = create(:project, :public)
+ user = create(:user)
+ project.team << [user, :developer]
+ public_snippet = create(:project_snippet, :public, project: project)
+ internal_snippet = create(:project_snippet, :internal, project: project)
+ private_snippet = create(:project_snippet, :private, project: project)
+
+ get api("/projects/#{project.id}/snippets/", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response.size).to eq(3)
+ expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
+ end
+
+ it 'hides private snippets from regular user' do
+ project = create(:project, :public)
+ user = create(:user)
+ create(:project_snippet, :private, project: project)
+
+ get api("/projects/#{project.id}/snippets/", user)
+ expect(response.status).to eq(200)
+ expect(json_response.size).to eq(0)
+ end
+ end
+
+ describe 'POST /projects/:project_id/snippets/' do
+ it 'creates a new snippet' do
+ admin = create(:admin)
+ project = create(:project)
+ params = {
+ title: 'Test Title',
+ file_name: 'test.rb',
+ code: 'puts "hello world"',
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ }
+
+ post api("/projects/#{project.id}/snippets/", admin), params
+
+ expect(response.status).to eq(201)
+ snippet = ProjectSnippet.find(json_response['id'])
+ expect(snippet.content).to eq(params[:code])
+ expect(snippet.title).to eq(params[:title])
+ expect(snippet.file_name).to eq(params[:file_name])
+ expect(snippet.visibility_level).to eq(params[:visibility_level])
+ end
+ end
+
+ describe 'PUT /projects/:project_id/snippets/:id/' do
+ it 'updates snippet' do
+ admin = create(:admin)
+ snippet = create(:project_snippet, author: admin)
+ new_content = 'New content'
+
+ put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content
+
+ expect(response.status).to eq(200)
+ snippet.reload
+ expect(snippet.content).to eq(new_content)
+ end
+ end
+
+ describe 'DELETE /projects/:project_id/snippets/:id/' do
+ it 'deletes snippet' do
+ admin = create(:admin)
+ snippet = create(:project_snippet, author: admin)
+
+ delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin)
+
+ expect(response.status).to eq(200)
+ end
+ end
+
+ describe 'GET /projects/:project_id/snippets/:id/raw' do
+ it 'returns raw text' do
+ admin = create(:admin)
+ snippet = create(:project_snippet, author: admin)
+
+ get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin)
+
+ expect(response.status).to eq(200)
+ expect(response.content_type).to eq 'text/plain'
+ expect(response.body).to eq(snippet.content)
+ end
+ end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index fccd08bd6da..66193eac051 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -11,7 +11,7 @@ describe API::API, api: true do
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) }
- let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
+ let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
let(:project_member) { create(:project_member, :master, user: user, project: project) }
let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index edcb2bedbf7..12e170b232f 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -147,7 +147,7 @@ describe API::API, api: true do
tag_name: 'v8.0.0',
ref: 'master'
expect(response.status).to eq(400)
- expect(json_response['message']).to eq('Tag already exists')
+ expect(json_response['message']).to eq('Tag v8.0.0 already exists')
end
it 'should return 400 if ref name is invalid' do
@@ -155,7 +155,7 @@ describe API::API, api: true do
tag_name: 'mytag',
ref: 'foo'
expect(response.status).to eq(400)
- expect(json_response['message']).to eq('Invalid reference name')
+ expect(json_response['message']).to eq('Target foo is invalid')
end
end
diff --git a/spec/services/create_tag_service_spec.rb b/spec/services/create_tag_service_spec.rb
new file mode 100644
index 00000000000..91f9e663b66
--- /dev/null
+++ b/spec/services/create_tag_service_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe CreateTagService, services: true do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:user) { create(:user) }
+ let(:service) { described_class.new(project, user) }
+
+ describe '#execute' do
+ it 'creates the tag and returns success' do
+ response = service.execute('v42.42.42', 'master', 'Foo')
+
+ expect(response[:status]).to eq(:success)
+ expect(response[:tag]).to be_a Gitlab::Git::Tag
+ expect(response[:tag].name).to eq('v42.42.42')
+ end
+
+ context 'when target is invalid' do
+ it 'returns an error' do
+ response = service.execute('v1.1.0', 'foo', 'Foo')
+
+ expect(response).to eq(status: :error,
+ message: 'Target foo is invalid')
+ end
+ end
+
+ context 'when tag already exists' do
+ it 'returns an error' do
+ expect(repository).to receive(:add_tag).
+ with(user, 'v1.1.0', 'master', 'Foo').
+ and_raise(Rugged::TagError)
+
+ response = service.execute('v1.1.0', 'master', 'Foo')
+
+ expect(response).to eq(status: :error,
+ message: 'Tag v1.1.0 already exists')
+ end
+ end
+
+ context 'when pre-receive hook fails' do
+ it 'returns an error' do
+ expect(repository).to receive(:add_tag).
+ with(user, 'v1.1.0', 'master', 'Foo').
+ and_raise(GitHooksService::PreReceiveError)
+
+ response = service.execute('v1.1.0', 'master', 'Foo')
+
+ expect(response).to eq(status: :error,
+ message: 'Tag creation was rejected by Git hook')
+ end
+ end
+ end
+end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index d7c72dc0811..4bbc4ddc3ab 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -10,7 +10,7 @@ describe NotificationService, services: true do
end
describe 'Keys' do
- describe :new_key do
+ describe '#new_key' do
let!(:key) { create(:personal_key) }
it { expect(notification.new_key(key)).to be_truthy }
@@ -22,7 +22,7 @@ describe NotificationService, services: true do
end
describe 'Email' do
- describe :new_email do
+ describe '#new_email' do
let!(:email) { create(:email) }
it { expect(notification.new_email(email)).to be_truthy }
@@ -147,8 +147,8 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
- describe :new_note do
- it do
+ describe '#new_note' do
+ it 'notifies the team members' do
notification.new_note(note)
# Notify all team members
@@ -177,6 +177,39 @@ describe NotificationService, services: true do
end
end
+ context 'project snippet note' do
+ let(:project) { create(:empty_project, :public) }
+ let(:snippet) { create(:project_snippet, project: project, author: create(:user)) }
+ let(:note) { create(:note_on_project_snippet, noteable: snippet, project_id: snippet.project.id, note: '@all mentioned') }
+
+ before do
+ build_team(note.project)
+ note.project.team << [note.author, :master]
+ ActionMailer::Base.deliveries.clear
+ end
+
+ describe '#new_note' do
+ it 'notifies the team members' do
+ notification.new_note(note)
+
+ # Notify all team members
+ note.project.team.members.each do |member|
+ # User with disabled notification should not be notified
+ next if member.id == @u_disabled.id
+ # Author should not be notified
+ next if member.id == note.author.id
+ should_email(member)
+ end
+
+ should_email(note.noteable.author)
+ should_not_email(note.author)
+ should_email(@u_mentioned)
+ should_not_email(@u_disabled)
+ should_email(@u_not_mentioned)
+ end
+ end
+ end
+
context 'commit note' do
let(:project) { create(:project, :public) }
let(:note) { create(:note_on_commit, project: project) }
@@ -187,7 +220,7 @@ describe NotificationService, services: true do
allow_any_instance_of(Commit).to receive(:author).and_return(@u_committer)
end
- describe :new_note, :perform_enqueued_jobs do
+ describe '#new_note, #perform_enqueued_jobs' do
it do
notification.new_note(note)
@@ -230,7 +263,7 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
- describe :new_issue do
+ describe '#new_issue' do
it do
notification.new_issue(issue, @u_disabled)
@@ -289,7 +322,7 @@ describe NotificationService, services: true do
end
end
- describe :reassigned_issue do
+ describe '#reassigned_issue' do
it 'emails new assignee' do
notification.reassigned_issue(issue, @u_disabled)
@@ -419,7 +452,7 @@ describe NotificationService, services: true do
end
end
- describe :close_issue do
+ describe '#close_issue' do
it 'should sent email to issue assignee and issue author' do
notification.close_issue(issue, @u_disabled)
@@ -435,7 +468,7 @@ describe NotificationService, services: true do
end
end
- describe :reopen_issue do
+ describe '#reopen_issue' do
it 'should send email to issue assignee and issue author' do
notification.reopen_issue(issue, @u_disabled)
@@ -461,7 +494,7 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
- describe :new_merge_request do
+ describe '#new_merge_request' do
it do
notification.new_merge_request(merge_request, @u_disabled)
@@ -483,7 +516,7 @@ describe NotificationService, services: true do
end
end
- describe :reassigned_merge_request do
+ describe '#reassigned_merge_request' do
it do
notification.reassigned_merge_request(merge_request, merge_request.author)
@@ -498,7 +531,7 @@ describe NotificationService, services: true do
end
end
- describe :relabel_merge_request do
+ describe '#relabel_merge_request' do
let(:label) { create(:label, merge_requests: [merge_request]) }
let(:label2) { create(:label) }
let!(:subscriber_to_label) { create(:user).tap { |u| label.toggle_subscription(u) } }
@@ -527,7 +560,7 @@ describe NotificationService, services: true do
end
end
- describe :closed_merge_request do
+ describe '#closed_merge_request' do
it do
notification.close_mr(merge_request, @u_disabled)
@@ -542,7 +575,7 @@ describe NotificationService, services: true do
end
end
- describe :merged_merge_request do
+ describe '#merged_merge_request' do
it do
notification.merge_mr(merge_request, @u_disabled)
@@ -557,7 +590,7 @@ describe NotificationService, services: true do
end
end
- describe :reopen_merge_request do
+ describe '#reopen_merge_request' do
it do
notification.reopen_mr(merge_request, @u_disabled)
@@ -581,7 +614,7 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
- describe :project_was_moved do
+ describe '#project_was_moved' do
it do
notification.project_was_moved(project, "gitlab/gitlab")
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 596d607f2a1..576d16e7ea3 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -51,10 +51,4 @@ FactoryGirl::SyntaxRunner.class_eval do
include RSpec::Mocks::ExampleMethods
end
-# Work around a Rails 4.2.5.1 issue
-# See https://github.com/rspec/rspec-rails/issues/1532
-RSpec::Rails::ViewRendering::EmptyTemplatePathSetDecorator.class_eval do
- alias_method :find_all_anywhere, :find_all
-end
-
ActiveRecord::Migration.maintain_test_schema!
diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/issue_tracker_service_shared_example.rb
new file mode 100644
index 00000000000..b6d7436c360
--- /dev/null
+++ b/spec/support/issue_tracker_service_shared_example.rb
@@ -0,0 +1,7 @@
+RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr|
+ it { is_expected.to allow_value('https://example.com').for(url_attr) }
+
+ it { is_expected.not_to allow_value('example.com').for(url_attr) }
+ it { is_expected.not_to allow_value('ftp://example.com').for(url_attr) }
+ it { is_expected.not_to allow_value('herp-and-derp').for(url_attr) }
+end
diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb
index 087e4c667d8..5a03bb77ebd 100644
--- a/spec/workers/repository_check/single_repository_worker_spec.rb
+++ b/spec/workers/repository_check/single_repository_worker_spec.rb
@@ -12,7 +12,7 @@ describe RepositoryCheck::SingleRepositoryWorker do
subject.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(false)
- destroy_wiki(project)
+ break_wiki(project)
subject.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(true)
@@ -20,15 +20,38 @@ describe RepositoryCheck::SingleRepositoryWorker do
it 'skips wikis when disabled' do
project = create(:project_empty_repo, wiki_enabled: false)
- # Make sure the test would fail if it checked the wiki repo
- destroy_wiki(project)
+ # Make sure the test would fail if the wiki repo was checked
+ break_wiki(project)
subject.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(false)
end
- def destroy_wiki(project)
- FileUtils.rm_rf(project.wiki.repository.path_to_repo)
+ it 'creates missing wikis' do
+ project = create(:project_empty_repo, wiki_enabled: true)
+ FileUtils.rm_rf(wiki_path(project))
+
+ subject.perform(project.id)
+
+ expect(project.reload.last_repository_check_failed).to eq(false)
+ end
+
+ it 'does not create a wiki if the main repo does not exist at all' do
+ project = create(:project_empty_repo)
+ FileUtils.rm_rf(project.repository.path_to_repo)
+ FileUtils.rm_rf(wiki_path(project))
+
+ subject.perform(project.id)
+
+ expect(File.exist?(wiki_path(project))).to eq(false)
+ end
+
+ def break_wiki(project)
+ FileUtils.rm_rf(wiki_path(project) + '/objects')
+ end
+
+ def wiki_path(project)
+ project.wiki.repository.path_to_repo
end
end