summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2016-05-16 17:13:14 -0500
committerKamil Trzcinski <ayufan@ayufan.eu>2016-05-16 17:13:14 -0500
commitd0eb9438e9b258d2fa399a7e54bdfd332e307252 (patch)
treec588717a5b722252d912b7c95b2b32594c9a56ac
parent7657d202160e04e6b5e5412d949977bc48cdacea (diff)
parent4ade5ff42511d7931bebb28098c2dc2192caba3e (diff)
downloadgitlab-ce-d0eb9438e9b258d2fa399a7e54bdfd332e307252.tar.gz
Merge remote-tracking branch 'origin/master' into with-pipeline-view
-rw-r--r--CHANGELOG11
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock3
-rw-r--r--app/assets/javascripts/notes.js.coffee2
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss10
-rw-r--r--app/assets/stylesheets/pages/notes.scss3
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/admin/users_controller.rb1
-rw-r--r--app/controllers/jwt_controller.rb87
-rw-r--r--app/controllers/projects/commit_controller.rb12
-rw-r--r--app/controllers/projects/compare_controller.rb3
-rw-r--r--app/controllers/projects/merge_requests_controller.rb7
-rw-r--r--app/controllers/projects/notes_controller.rb6
-rw-r--r--app/controllers/projects_controller.rb3
-rw-r--r--app/controllers/registrations_controller.rb4
-rw-r--r--app/finders/issuable_finder.rb4
-rw-r--r--app/finders/notes_finder.rb2
-rw-r--r--app/helpers/diff_helper.rb22
-rw-r--r--app/helpers/events_helper.rb61
-rw-r--r--app/helpers/notes_helper.rb29
-rw-r--r--app/models/ability.rb11
-rw-r--r--app/models/application_setting.rb3
-rw-r--r--app/models/event.rb35
-rw-r--r--app/models/legacy_diff_note.rb157
-rw-r--r--app/models/merge_request.rb10
-rw-r--r--app/models/merge_request_diff.rb26
-rw-r--r--app/models/milestone.rb14
-rw-r--r--app/models/note.rb213
-rw-r--r--app/models/project.rb9
-rw-r--r--app/models/project_wiki.rb2
-rw-r--r--app/models/repository.rb4
-rw-r--r--app/models/user.rb5
-rw-r--r--app/services/auth/container_registry_authentication_service.rb70
-rw-r--r--app/views/admin/application_settings/_form.html.haml6
-rw-r--r--app/views/events/event/_common.html.haml2
-rw-r--r--app/views/notify/note_merge_request_email.html.haml4
-rw-r--r--app/views/projects/diffs/_line.html.haml2
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml14
-rw-r--r--app/views/projects/diffs/_text_file.html.haml9
-rw-r--r--app/views/projects/edit.html.haml10
-rw-r--r--app/views/projects/notes/_commit_discussion.html.haml0
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply.html.haml18
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml20
-rw-r--r--app/views/projects/notes/_discussion.html.haml47
-rw-r--r--app/views/projects/notes/_form.html.haml1
-rw-r--r--app/views/projects/notes/_note.html.haml9
-rw-r--r--app/views/projects/notes/_notes.html.haml9
-rw-r--r--app/views/projects/notes/discussions/_active.html.haml16
-rw-r--r--app/views/projects/notes/discussions/_commit.html.haml25
-rw-r--r--app/views/projects/notes/discussions/_diff.html.haml28
-rw-r--r--app/views/projects/notes/discussions/_diff_with_notes.html.haml30
-rw-r--r--app/views/projects/notes/discussions/_notes.html.haml7
-rw-r--r--app/views/projects/notes/discussions/_outdated.html.haml14
-rw-r--r--config/gitlab.yml.example9
-rw-r--r--config/initializers/1_settings.rb23
-rw-r--r--config/routes.rb3
-rw-r--r--db/migrate/20160407120251_add_images_enabled_for_project.rb5
-rw-r--r--db/migrate/20160508215820_add_type_to_notes.rb5
-rw-r--r--db/migrate/20160508221410_set_type_on_legacy_diff_notes.rb5
-rw-r--r--db/migrate/20160516174813_add_send_user_confirmation_email_to_application_settings.rb12
-rw-r--r--db/schema.rb6
-rw-r--r--doc/administration/environment_variables.md2
-rw-r--r--doc/administration/high_availability/load_balancer.md2
-rw-r--r--doc/administration/high_availability/nfs.md2
-rw-r--r--doc/api/projects.md3
-rw-r--r--doc/api/services.md2
-rw-r--r--doc/development/doc_styleguide.md4
-rw-r--r--doc/gitlab-basics/create-issue.md2
-rw-r--r--doc/gitlab-basics/create-project.md2
-rw-r--r--doc/hooks/custom_hooks.md2
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/install/relative_url.md2
-rw-r--r--doc/integration/README.md2
-rw-r--r--doc/intro/README.md2
-rw-r--r--doc/logs/logs.md2
-rw-r--r--doc/permissions/permissions.md2
-rw-r--r--doc/raketasks/README.md2
-rw-r--r--doc/security/README.md1
-rw-r--r--doc/security/user_email_confirmation.md7
-rw-r--r--doc/update/README.md6
-rw-r--r--doc/workflow/gitlab_flow.md4
-rw-r--r--doc/workflow/groups.md2
-rw-r--r--doc/workflow/importing/import_projects_from_github.md2
-rw-r--r--doc/workflow/importing/import_projects_from_gitlab_com.md2
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md2
-rw-r--r--features/steps/shared/diff_note.rb12
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/entities.rb9
-rw-r--r--lib/api/projects.rb5
-rw-r--r--lib/gitlab/github_import/branch_formatter.rb29
-rw-r--r--lib/gitlab/github_import/importer.rb76
-rw-r--r--lib/gitlab/github_import/pull_request_formatter.rb49
-rw-r--r--lib/gitlab/metrics/subscribers/rails_cache.rb12
-rw-r--r--lib/gitlab/url_builder.rb2
-rw-r--r--lib/json_web_token/rsa_token.rb42
-rw-r--r--lib/json_web_token/token.rb46
-rw-r--r--public/robots.txt1
-rw-r--r--spec/controllers/admin/users_controller_spec.rb76
-rw-r--r--spec/controllers/registrations_controller_spec.rb33
-rw-r--r--spec/factories/notes.rb4
-rw-r--r--spec/features/admin/admin_users_spec.rb3
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb15
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb2
-rw-r--r--spec/features/signup_spec.rb45
-rw-r--r--spec/finders/issues_finder_spec.rb180
-rw-r--r--spec/lib/gitlab/github_import/branch_formatter_spec.rb71
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb46
-rw-r--r--spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb16
-rw-r--r--spec/lib/json_web_token/rsa_token_spec.rb43
-rw-r--r--spec/lib/json_web_token/token_spec.rb18
-rw-r--r--spec/models/legacy_diff_note_spec.rb74
-rw-r--r--spec/models/merge_request_spec.rb15
-rw-r--r--spec/models/milestone_spec.rb33
-rw-r--r--spec/models/note_spec.rb80
-rw-r--r--spec/models/project_wiki_spec.rb3
-rw-r--r--spec/models/user_spec.rb1
-rw-r--r--spec/requests/jwt_controller_spec.rb72
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb216
118 files changed, 1814 insertions, 749 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 30d4955c9f3..e1252d4b947 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,7 @@ v 8.8.0 (unreleased)
- Fix error when using link to uploads in global snippets
- Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen)
- Use a case-insensitive comparison in sanitizing URI schemes
+ - Toggle sign-up confirmation emails in application settings
- Project#open_branches has been cleaned up and no longer loads entire records into memory.
- Escape HTML in commit titles in system note messages
- Improve multiple branch push performance by memoizing permission checking
@@ -15,6 +16,7 @@ v 8.8.0 (unreleased)
- Make build status canceled if any of the jobs was canceled and none failed
- Upgrade Sidekiq to 4.1.2
- Added /health_check endpoint for checking service status
+ - Make 'upcoming' filter for milestones work better across projects
- Sanitize repo paths in new project error message
- Bump mail_room to 0.7.0 to fix stuck IDLE connections
- Remove future dates from contribution calendar graph.
@@ -25,9 +27,11 @@ v 8.8.0 (unreleased)
- Update SVG sanitizer to conform to SVG 1.1
- Speed up push emails with multiple recipients by only generating the email once
- Updated search UI
+ - Added authentication service for Container Registry
- Display informative message when new milestone is created
- Sanitize milestones and labels titles
- Support multi-line tag messages. !3833 (Calin Seciu)
+ - Force users to reset their password after an admin changes it
- Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea)
- Added button to toggle whitespaces changes on diff view
- Backport GitHub Enterprise import support from EE
@@ -43,7 +47,7 @@ v 8.8.0 (unreleased)
- API support for the 'since' and 'until' operators on commit requests (Paco Guzman)
- Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko)
- Expire repository exists? and has_visible_content? caches after a push if necessary
- - Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi)
+ - Fix unintentional filtering bug in Issue/MR sorted by milestone due (Takuya Noguchi)
- Fix adding a todo for private group members (Ahmad Sherif)
- Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3
- Total method execution timings are no longer tracked
@@ -51,6 +55,11 @@ v 8.8.0 (unreleased)
- Add API endpoints for un/subscribing from/to a label. !4051 (Ahmad Sherif)
- Hide left sidebar on phone screens to give more space for content
- Redesign navigation for profile and group pages
+ - Add counter metrics for rails cache
+ - Import pull requests from GitHub where the source or target branches were removed
+
+v 8.7.6
+ - Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko)
v 8.7.5
- Fix relative links in wiki pages. !4050
diff --git a/Gemfile b/Gemfile
index 6949c16e966..91ad1706a07 100644
--- a/Gemfile
+++ b/Gemfile
@@ -36,6 +36,7 @@ gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'rack-oauth2', '~> 1.2.1'
+gem 'jwt'
# Spam and anti-bot protection
gem 'recaptcha', require: 'recaptcha/rails'
@@ -224,6 +225,7 @@ gem 'request_store', '~> 1.3.0'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
+gem 'base32', '~> 0.3.0'
# Sentry integration
gem 'sentry-raven', '~> 0.15'
diff --git a/Gemfile.lock b/Gemfile.lock
index 4b51bf58bba..b55764504c6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -70,6 +70,7 @@ GEM
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
babosa (1.0.2)
+ base32 (0.3.2)
bcrypt (3.1.10)
benchmark-ips (2.3.0)
better_errors (1.0.1)
@@ -893,6 +894,7 @@ DEPENDENCIES
attr_encrypted (~> 1.3.4)
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
+ base32 (~> 0.3.0)
benchmark-ips
better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2)
@@ -954,6 +956,7 @@ DEPENDENCIES
jquery-rails (~> 4.1.0)
jquery-turbolinks (~> 2.1.0)
jquery-ui-rails (~> 5.0.0)
+ jwt
kaminari (~> 0.16.3)
letter_opener_web (~> 1.3.0)
licensee (~> 8.0.0)
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index efb3e8e2198..6d9d6528f45 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -285,6 +285,7 @@ class @Notes
form.addClass "js-main-target-form"
form.find("#note_line_code").remove()
+ form.find("#note_type").remove()
###
General note form setup.
@@ -472,6 +473,7 @@ class @Notes
setupDiscussionNoteForm: (dataHolder, form) =>
# setup note target
form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}"
+ form.find("#note_type").val dataHolder.data("noteType")
form.find("#line_type").val dataHolder.data("lineType")
form.find("#note_commit_id").val dataHolder.data("commitId")
form.find("#note_line_code").val dataHolder.data("lineCode")
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index 51a17d1469e..16cf394c426 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -9,6 +9,8 @@
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
.page-with-sidebar {
.header-logo {
+ background: $color-darker;
+
a {
color: $color-light;
@@ -88,8 +90,8 @@
}
$theme-blue: #2980b9;
-$theme-charcoal: #333c47;
-$theme-graphite: #888;
+$theme-charcoal: #3d454d;
+$theme-graphite: #666;
$theme-gray: #373737;
$theme-green: #019875;
$theme-violet: #548;
@@ -100,11 +102,11 @@ body {
}
&.ui_charcoal {
- @include gitlab-theme(#c5d0de, $theme-charcoal, #2b333d, #24272d);
+ @include gitlab-theme(#d6d7d9, #485157, $theme-charcoal, #353b41);
}
&.ui_graphite {
- @include gitlab-theme(#ccc, $theme-graphite, #777, #666);
+ @include gitlab-theme(#ccc, #777, $theme-graphite, #555);
}
&.ui_gray {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 624c8249f7e..a3e1ac13a43 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -226,8 +226,7 @@ ul.notes {
}
}
-.note-action-button,
-.discussion-action-button {
+.note-action-button {
display: inline-block;
margin-left: 10px;
line-height: 24px;
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 8c973f0e4a8..ff7a5cad2fb 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -106,6 +106,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:email_author_in_body,
:repository_checks_enabled,
:metrics_packet_size,
+ :send_user_confirmation_email,
restricted_visibility_levels: [],
import_sources: [],
disabled_oauth_sign_in_sources: []
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index f2f654c7bcd..6908a3bf946 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -119,6 +119,7 @@ class Admin::UsersController < Admin::ApplicationController
user_params_with_pass.merge!(
password: params[:user][:password],
password_confirmation: params[:user][:password_confirmation],
+ password_expires_at: Time.now
)
end
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
new file mode 100644
index 00000000000..f5aa5397ff1
--- /dev/null
+++ b/app/controllers/jwt_controller.rb
@@ -0,0 +1,87 @@
+class JwtController < ApplicationController
+ skip_before_action :authenticate_user!
+ skip_before_action :verify_authenticity_token
+ before_action :authenticate_project_or_user
+
+ SERVICES = {
+ Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService,
+ }
+
+ def auth
+ service = SERVICES[params[:service]]
+ return head :not_found unless service
+
+ result = service.new(@project, @user, auth_params).execute
+
+ render json: result, status: result[:http_status]
+ end
+
+ private
+
+ def authenticate_project_or_user
+ authenticate_with_http_basic do |login, password|
+ # if it's possible we first try to authenticate project with login and password
+ @project = authenticate_project(login, password)
+ return if @project
+
+ @user = authenticate_user(login, password)
+ return if @user
+
+ render_403
+ end
+ end
+
+ def auth_params
+ params.permit(:service, :scope, :offline_token, :account, :client_id)
+ end
+
+ def authenticate_project(login, password)
+ if login == 'gitlab_ci_token'
+ Project.find_by(builds_enabled: true, runners_token: password)
+ end
+ end
+
+ def authenticate_user(login, password)
+ # TODO: this is a copy and paste from grack_auth,
+ # it should be refactored in the future
+
+ user = Gitlab::Auth.new.find(login, password)
+
+ # If the user authenticated successfully, we reset the auth failure count
+ # from Rack::Attack for that IP. A client may attempt to authenticate
+ # with a username and blank password first, and only after it receives
+ # a 401 error does it present a password. Resetting the count prevents
+ # false positives from occurring.
+ #
+ # Otherwise, we let Rack::Attack know there was a failed authentication
+ # attempt from this IP. This information is stored in the Rails cache
+ # (Redis) and will be used by the Rack::Attack middleware to decide
+ # whether to block requests from this IP.
+ config = Gitlab.config.rack_attack.git_basic_auth
+
+ if config.enabled
+ if user
+ # A successful login will reset the auth failure count from this IP
+ Rack::Attack::Allow2Ban.reset(request.ip, config)
+ else
+ banned = Rack::Attack::Allow2Ban.filter(request.ip, config) do
+ # Unless the IP is whitelisted, return true so that Allow2Ban
+ # increments the counter (stored in Rails.cache) for the IP
+ if config.ip_whitelist.include?(request.ip)
+ false
+ else
+ true
+ end
+ end
+
+ if banned
+ Rails.logger.info "IP #{request.ip} failed to login " \
+ "as #{login} but has been temporarily banned from Git auth"
+ return
+ end
+ end
+ end
+
+ user
+ end
+end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index a202cb38692..10b5932affa 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -17,12 +17,12 @@ class Projects::CommitController < Projects::ApplicationController
def show
apply_diff_view_cookie!
- @line_notes = commit.notes.inline
+ @grouped_diff_notes = commit.notes.grouped_diff_notes
+
@note = @project.build_commit_note(commit)
- @notes = commit.notes.not_inline.fresh
+ @notes = commit.notes.non_diff_notes.fresh
@noteable = @commit
- @comments_allowed = @reply_allowed = true
- @comments_target = {
+ @comments_target = {
noteable_type: 'Commit',
commit_id: @commit.id
}
@@ -67,10 +67,10 @@ class Projects::CommitController < Projects::ApplicationController
create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.",
success_path: successful_change_path, failure_path: failed_change_path)
end
-
+
def cherry_pick
assign_change_commit_vars(@commit.cherry_pick_branch_name)
-
+
return render_404 if @target_branch.blank?
create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.",
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 671d5c23024..af0b69a2442 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -22,7 +22,8 @@ class Projects::CompareController < Projects::ApplicationController
@base_commit = @project.merge_base_commit(@base_ref, @head_ref)
@diffs = compare.diffs(diff_options)
@diff_refs = [@base_commit, @commit]
- @line_notes = []
+ @diff_notes_disabled = true
+ @grouped_diff_notes = {}
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 9c147b3689e..c5757a24624 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -73,12 +73,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# but we need it for the "View file @ ..." link by deleted files
@base_commit ||= @merge_request.first_commit.parent || @merge_request.first_commit
- @comments_allowed = @reply_allowed = true
@comments_target = {
noteable_type: 'MergeRequest',
noteable_id: @merge_request.id
}
- @line_notes = @merge_request.notes.where("line_code is not null")
+
+ @grouped_diff_notes = @merge_request.notes.grouped_diff_notes
respond_to do |format|
format.html
@@ -117,6 +117,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commit = @merge_request.last_commit
@base_commit = @merge_request.diff_base_commit
@diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
+ @diff_notes_disabled = true
@ci_commit = @merge_request.ci_commit
@statuses = @ci_commit.statuses if @ci_commit
@@ -300,7 +301,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
- @discussions = Note.discussions_from_notes(@notes)
+ @discussions = @notes.discussions
@noteable = @merge_request
# Get commits from repository
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 707a0d0e5c6..4a57cd29a20 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -96,7 +96,7 @@ class Projects::NotesController < Projects::ApplicationController
end
def note_to_discussion_html(note)
- return unless note.for_diff_line?
+ return unless note.diff_note?
if params[:view] == 'parallel'
template = "projects/notes/_diff_notes_with_reply_parallel"
@@ -120,7 +120,7 @@ class Projects::NotesController < Projects::ApplicationController
end
def note_to_discussion_with_diff_html(note)
- return unless note.for_diff_line?
+ return unless note.diff_note?
render_to_string(
"projects/notes/_discussion",
@@ -158,7 +158,7 @@ class Projects::NotesController < Projects::ApplicationController
def note_params
params.require(:note).permit(
:note, :noteable, :noteable_id, :noteable_type, :project_id,
- :attachment, :line_code, :commit_id
+ :attachment, :line_code, :commit_id, :type
)
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 3768efe142a..f4ec60ad2c7 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -235,7 +235,8 @@ class ProjectsController < Projects::ApplicationController
def project_params
params.require(:project).permit(
:name, :path, :description, :issues_tracker, :tag_list, :runners_token,
- :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
+ :issues_enabled, :merge_requests_enabled, :snippets_enabled, :container_registry_enabled,
+ :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds,
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 352bff19383..26eb15f49e4 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -37,8 +37,8 @@ class RegistrationsController < Devise::RegistrationsController
super
end
- def after_sign_up_path_for(_resource)
- users_almost_there_path
+ def after_sign_up_path_for(user)
+ user.confirmed_at.present? ? dashboard_projects_path : users_almost_there_path
end
def after_inactive_sign_up_path_for(_resource)
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index f00f3f709e9..5849e00662b 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -252,8 +252,8 @@ class IssuableFinder
if filter_by_no_milestone?
items = items.where(milestone_id: [-1, nil])
elsif filter_by_upcoming_milestone?
- upcoming = Milestone.where(project_id: projects).upcoming
- items = items.joins(:milestone).where(milestones: { title: upcoming.try(:title) })
+ upcoming_ids = Milestone.upcoming_ids_by_projects(projects)
+ items = items.joins(:milestone).where(milestone_id: upcoming_ids)
else
items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index fa4c635f55c..c41be333537 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -10,7 +10,7 @@ class NotesFinder
notes =
case target_type
when "commit"
- project.notes.for_commit_id(target_id).not_inline
+ project.notes.for_commit_id(target_id).non_diff_notes
when "issue"
project.issues.find(target_id).notes.nonawards.inc_author
when "merge_request"
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 9f73edb4553..5f311f3780a 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -55,22 +55,18 @@ module DiffHelper
end
end
- def line_comments
- @line_comments ||= @line_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
- end
-
- def organize_comments(type_left, type_right, line_code_left, line_code_right)
- comments_left = comments_right = nil
+ def organize_comments(left, right)
+ notes_left = notes_right = nil
- unless type_left.nil? && type_right == 'new'
- comments_left = line_comments[line_code_left]
+ unless left[:type].nil? && right[:type] == 'new'
+ notes_left = @grouped_diff_notes[left[:line_code]]
end
- unless type_left.nil? && type_right.nil?
- comments_right = line_comments[line_code_right]
+ unless left[:type].nil? && right[:type].nil?
+ notes_right = @grouped_diff_notes[right[:line_code]]
end
- [comments_left, comments_right]
+ [notes_left, notes_right]
end
def inline_diff_btn
@@ -96,8 +92,8 @@ module DiffHelper
].join(' ').html_safe
end
- def commit_for_diff(diff)
- if diff.deleted_file
+ def commit_for_diff(diff_file)
+ if diff_file.deleted_file
@base_commit || @commit.parent || @commit
else
@commit
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 0bf328e7d19..e1489381706 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -3,7 +3,7 @@ module EventsHelper
author = event.author
if author
- link_to author.name, user_path(author.username), title: h(author.name)
+ link_to author.name, user_path(author.username), title: author.name
else
event.author_name
end
@@ -57,11 +57,7 @@ module EventsHelper
words << event.ref_name
words << "at"
elsif event.commented?
- if event.note_commit?
- words << event.note_short_commit_id
- else
- words << "##{truncate event.note_target_iid}"
- end
+ words << event.note_target_reference
words << "at"
elsif event.milestone?
words << "##{event.target_iid}" if event.target_iid
@@ -84,21 +80,12 @@ module EventsHelper
elsif event.merge_request?
namespace_project_merge_request_url(event.project.namespace,
event.project, event.merge_request)
- elsif event.note? && event.note_commit?
+ elsif event.note? && event.commit_note?
namespace_project_commit_url(event.project.namespace, event.project,
event.note_target)
elsif event.note?
if event.note_target
- if event.note_commit?
- namespace_project_commit_path(event.project.namespace, event.project,
- event.note_commit_id,
- anchor: dom_id(event.target))
- elsif event.note_project_snippet?
- namespace_project_snippet_path(event.project.namespace,
- event.project, event.note_target)
- else
- event_note_target_path(event)
- end
+ event_note_target_path(event)
end
elsif event.push?
push_event_feed_url(event)
@@ -134,42 +121,30 @@ module EventsHelper
end
def event_note_target_path(event)
- if event.note? && event.note_commit?
- namespace_project_commit_path(event.project.namespace, event.project,
- event.note_target)
+ if event.note? && event.commit_note?
+ namespace_project_commit_path(event.project.namespace,
+ event.project,
+ event.note_target,
+ anchor: dom_id(event.target))
+ elsif event.project_snippet_note?
+ namespace_project_snippet_path(event.project.namespace,
+ event.project,
+ event.note_target,
+ anchor: dom_id(event.target))
else
polymorphic_path([event.project.namespace.becomes(Namespace),
event.project, event.note_target],
- anchor: dom_id(event.target))
+ anchor: dom_id(event.target))
end
end
def event_note_title_html(event)
if event.note_target
- if event.note_commit?
- link_to(
- namespace_project_commit_path(event.project.namespace, event.project,
- event.note_commit_id,
- anchor: dom_id(event.target), title: h(event.target_title)),
- class: "commit_short_id"
- ) do
- "#{event.note_target_type} #{event.note_short_commit_id}"
- end
- elsif event.note_project_snippet?
- link_to(namespace_project_snippet_path(event.project.namespace,
- event.project,
- event.note_target), title: h(event.project.name)) do
- "#{event.note_target_type} #{truncate event.note_target.to_reference}"
- end
- else
- link_to event_note_target_path(event) do
- "#{event.note_target_type} #{truncate event.note_target.to_reference}"
- end
+ link_to(event_note_target_path(event), title: event.target_title, class: 'has-tooltip') do
+ "#{event.note_target_type} #{event.note_target_reference}"
end
else
- content_tag :strong do
- "(deleted)"
- end
+ content_tag(:strong, '(deleted)')
end
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 95072b5373f..b401c8385be 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -1,7 +1,7 @@
module NotesHelper
# Helps to distinguish e.g. commit notes in mr notes list
def note_for_main_target?(note)
- (@noteable.class.name == note.noteable_type && !note.for_diff_line?)
+ @noteable.class.name == note.noteable_type && !note.diff_note?
end
def note_target_fields(note)
@@ -15,16 +15,6 @@ module NotesHelper
note.editable? && can?(current_user, :admin_note, note)
end
- def link_to_commit_diff_line_note(note)
- if note.for_commit_diff_line?
- link_to(
- "#{note.diff_file_name}:L#{note.diff_new_line}",
- namespace_project_commit_path(@project.namespace, @project,
- note.noteable, anchor: note.line_code)
- )
- end
- end
-
def noteable_json(noteable)
{
id: noteable.id,
@@ -35,7 +25,7 @@ module NotesHelper
end
def link_to_new_diff_note(line_code, line_type = nil)
- discussion_id = Note.build_discussion_id(
+ discussion_id = LegacyDiffNote.build_discussion_id(
@comments_target[:noteable_type],
@comments_target[:noteable_id] || @comments_target[:commit_id],
line_code
@@ -45,9 +35,10 @@ module NotesHelper
noteable_type: @comments_target[:noteable_type],
noteable_id: @comments_target[:noteable_id],
commit_id: @comments_target[:commit_id],
+ line_type: line_type,
line_code: line_code,
- discussion_id: discussion_id,
- line_type: line_type
+ note_type: LegacyDiffNote.name,
+ discussion_id: discussion_id
}
button_tag(class: 'btn add-diff-note js-add-diff-note-button',
@@ -57,18 +48,24 @@ module NotesHelper
end
end
- def link_to_reply_diff(note, line_type = nil)
+ def link_to_reply_discussion(note, line_type = nil)
return unless current_user
data = {
noteable_type: note.noteable_type,
noteable_id: note.noteable_id,
commit_id: note.commit_id,
- line_code: note.line_code,
discussion_id: note.discussion_id,
line_type: line_type
}
+ if note.diff_note?
+ data.merge!(
+ line_code: note.line_code,
+ note_type: LegacyDiffNote.name
+ )
+ end
+
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
data: data, title: 'Add a reply'
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index d3e8a3fa79a..f7ea2fd2b1f 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -61,6 +61,7 @@ class Ability
:read_merge_request,
:read_note,
:read_commit_status,
+ :read_container_image,
:download_code
]
@@ -203,6 +204,7 @@ class Ability
:admin_label,
:read_commit_status,
:read_build,
+ :read_container_image,
:read_pipeline,
]
end
@@ -219,7 +221,9 @@ class Ability
:update_pipeline,
:create_merge_request,
:create_wiki,
- :push_code
+ :push_code,
+ :create_container_image,
+ :update_container_image,
]
end
@@ -246,6 +250,7 @@ class Ability
:admin_project,
:admin_commit_status,
:admin_build,
+ :admin_container_image,
:admin_pipeline
]
end
@@ -292,6 +297,10 @@ class Ability
rules += named_abilities('pipeline')
end
+ unless project.container_registry_enabled
+ rules += named_abilities('container_image')
+ end
+
rules
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 1a10768655f..f5079f92444 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -120,7 +120,8 @@ class ApplicationSetting < ActiveRecord::Base
recaptcha_enabled: false,
akismet_enabled: false,
repository_checks_enabled: true,
- disabled_oauth_sign_in_sources: []
+ disabled_oauth_sign_in_sources: [],
+ send_user_confirmation_email: false
)
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 17ee48b91a8..716039fb54b 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -80,7 +80,7 @@ class Event < ActiveRecord::Base
end
def target_title
- target.title if target && target.respond_to?(:title)
+ target.try(:title)
end
def created?
@@ -266,28 +266,20 @@ class Event < ActiveRecord::Base
branch? && project.default_branch != branch_name
end
- def note_commit_id
- target.commit_id
- end
-
def target_iid
target.respond_to?(:iid) ? target.iid : target_id
end
- def note_short_commit_id
- Commit.truncate_sha(note_commit_id)
- end
-
- def note_commit?
- target.noteable_type == "Commit"
+ def commit_note?
+ target.for_commit?
end
def issue_note?
- note? && target && target.noteable_type == "Issue"
+ note? && target && target.for_issue?
end
- def note_project_snippet?
- target.noteable_type == "Snippet"
+ def project_snippet_note?
+ target.for_snippet?
end
def note_target
@@ -295,19 +287,22 @@ class Event < ActiveRecord::Base
end
def note_target_id
- if note_commit?
+ if commit_note?
target.commit_id
else
target.noteable_id.to_s
end
end
- def note_target_iid
- if note_target.respond_to?(:iid)
- note_target.iid
+ def note_target_reference
+ return unless note_target
+
+ # Commit#to_reference returns the full SHA, but we want the short one here
+ if commit_note?
+ note_target.short_id
else
- note_target_id
- end.to_s
+ note_target.to_reference
+ end
end
def note_target_type
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
new file mode 100644
index 00000000000..bbefc911b29
--- /dev/null
+++ b/app/models/legacy_diff_note.rb
@@ -0,0 +1,157 @@
+class LegacyDiffNote < Note
+ serialize :st_diff
+
+ validates :line_code, presence: true, line_code: true
+
+ before_create :set_diff
+
+ class << self
+ def build_discussion_id(noteable_type, noteable_id, line_code, active = true)
+ [super(noteable_type, noteable_id), line_code, active].join("-")
+ end
+ end
+
+ def diff_note?
+ true
+ end
+
+ def legacy_diff_note?
+ true
+ end
+
+ def discussion_id
+ @discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code, active?)
+ end
+
+ def diff_file_hash
+ line_code.split('_')[0] if line_code
+ end
+
+ def diff_old_line
+ line_code.split('_')[1].to_i if line_code
+ end
+
+ def diff_new_line
+ line_code.split('_')[2].to_i if line_code
+ end
+
+ def diff
+ @diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
+ end
+
+ def diff_file_path
+ diff.new_path.presence || diff.old_path
+ end
+
+ def diff_lines
+ @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
+ end
+
+ def diff_line
+ @diff_line ||= diff_lines.find { |line| generate_line_code(line) == self.line_code }
+ end
+
+ def diff_line_text
+ diff_line.try(:text)
+ end
+
+ def diff_line_type
+ diff_line.try(:type)
+ end
+
+ def highlighted_diff_lines
+ Gitlab::Diff::Highlight.new(diff_lines).highlight
+ end
+
+ def truncated_diff_lines
+ max_number_of_lines = 16
+ prev_match_line = nil
+ prev_lines = []
+
+ highlighted_diff_lines.each do |line|
+ if line.type == "match"
+ prev_lines.clear
+ prev_match_line = line
+ else
+ prev_lines << line
+
+ break if generate_line_code(line) == self.line_code
+
+ prev_lines.shift if prev_lines.length >= max_number_of_lines
+ end
+ end
+
+ prev_lines
+ end
+
+ # Check if this note is part of an "active" discussion
+ #
+ # This will always return true for anything except MergeRequest noteables,
+ # which have special logic.
+ #
+ # If the note's current diff cannot be matched in the MergeRequest's current
+ # diff, it's considered inactive.
+ def active?
+ return @active if defined?(@active)
+ return true if for_commit?
+ return true unless self.diff
+ return false unless noteable
+
+ noteable_diff = find_noteable_diff
+
+ if noteable_diff
+ parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line)
+
+ @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line_text }
+ else
+ @active = false
+ end
+
+ @active
+ end
+
+ private
+
+ def find_diff
+ return nil unless noteable
+ return @diff if defined?(@diff)
+
+ @diff = noteable.diffs(Commit.max_diff_options).find do |d|
+ d.new_path && Digest::SHA1.hexdigest(d.new_path) == diff_file_hash
+ end
+ end
+
+ def set_diff
+ # First lets find notes with same diff
+ # before iterating over all mr diffs
+ diff = diff_for_line_code unless for_merge_request?
+ diff ||= find_diff
+
+ self.st_diff = diff.to_hash if diff
+ end
+
+ def diff_for_line_code
+ attributes = {
+ noteable_type: noteable_type,
+ line_code: line_code
+ }
+
+ if for_commit?
+ attributes[:commit_id] = commit_id
+ else
+ attributes[:noteable_id] = noteable_id
+ end
+
+ self.class.where(attributes).last.try(:diff)
+ end
+
+ def generate_line_code(line)
+ Gitlab::Diff::LineCode.generate(diff_file_path, line.new_pos, line.old_pos)
+ end
+
+ # Find the diff on noteable that matches our own
+ def find_noteable_diff
+ diffs = noteable.diffs(Commit.max_diff_options)
+ diffs.find { |d| d.new_path == self.diff.new_path }
+ end
+end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 5c5e6007aa0..45ddcf6812a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -26,6 +26,10 @@ class MergeRequest < ActiveRecord::Base
# when creating new merge request
attr_accessor :can_be_created, :compare_commits, :compare
+ # Temporary fields to store target_sha, and base_sha to
+ # compare when importing pull requests from GitHub
+ attr_accessor :base_target_sha, :head_source_sha
+
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
@@ -490,10 +494,14 @@ class MergeRequest < ActiveRecord::Base
end
def target_sha
- @target_sha ||= target_project.repository.commit(target_branch).try(:sha)
+ return @base_target_sha if defined?(@base_target_sha)
+
+ target_project.repository.commit(target_branch).try(:sha)
end
def source_sha
+ return @head_source_sha if defined?(@head_source_sha)
+
last_commit.try(:sha) || source_tip.try(:sha)
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index eb42c07b9b9..6ad8fc3f034 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -6,7 +6,7 @@ class MergeRequestDiff < ActiveRecord::Base
belongs_to :merge_request
- delegate :target_branch, :source_branch, to: :merge_request, prefix: nil
+ delegate :head_source_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil
state_machine :state, initial: :empty do
state :collected
@@ -38,8 +38,8 @@ class MergeRequestDiff < ActiveRecord::Base
@diffs_no_whitespace ||= begin
compare = Gitlab::Git::Compare.new(
self.repository.raw_repository,
- self.target_branch,
- self.source_sha,
+ self.base,
+ self.head,
)
compare.diffs(options)
end
@@ -144,7 +144,7 @@ class MergeRequestDiff < ActiveRecord::Base
self.st_diffs = new_diffs
- self.base_commit_sha = self.repository.merge_base(self.source_sha, self.target_branch)
+ self.base_commit_sha = self.repository.merge_base(self.head, self.base)
self.save
end
@@ -160,10 +160,24 @@ class MergeRequestDiff < ActiveRecord::Base
end
def source_sha
+ return head_source_sha if head_source_sha.present?
+
source_commit = merge_request.source_project.commit(source_branch)
source_commit.try(:sha)
end
+ def target_sha
+ merge_request.target_sha
+ end
+
+ def base
+ self.target_sha || self.target_branch
+ end
+
+ def head
+ self.source_sha
+ end
+
def compare
@compare ||=
begin
@@ -172,8 +186,8 @@ class MergeRequestDiff < ActiveRecord::Base
Gitlab::Git::Compare.new(
self.repository.raw_repository,
- self.target_branch,
- self.source_sha
+ self.base,
+ self.head
)
end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index e4fdd23badb..fe9a281f366 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -67,8 +67,18 @@ class Milestone < ActiveRecord::Base
@link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
end
- def self.upcoming
- self.where('due_date > ?', Time.now).reorder(due_date: :asc).first
+ def self.upcoming_ids_by_projects(projects)
+ rel = unscoped.of_projects(projects).active.where('due_date > ?', Time.now)
+
+ if Gitlab::Database.postgresql?
+ rel.order(:project_id, :due_date).select('DISTINCT ON (project_id) id')
+ else
+ rel.
+ group(:project_id).
+ having('due_date = MIN(due_date)').
+ pluck(:id, :project_id, :due_date).
+ map(&:first)
+ end
end
def to_reference(from_project = nil)
diff --git a/app/models/note.rb b/app/models/note.rb
index f26aa1bf63f..55b98557244 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -1,6 +1,5 @@
-require 'carrierwave/orm/activerecord'
-
class Note < ActiveRecord::Base
+ extend ActiveModel::Naming
include Gitlab::CurrentSettings
include Participable
include Mentionable
@@ -20,14 +19,13 @@ class Note < ActiveRecord::Base
delegate :gfm_reference, :local_reference, to: :noteable
delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true
+ delegate :title, to: :noteable, allow_nil: true
before_validation :set_award!
- before_validation :clear_blank_line_code!
validates :note, :project, presence: true
validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
- validates :line_code, line_code: true, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
@@ -41,8 +39,6 @@ class Note < ActiveRecord::Base
scope :awards, ->{ where(is_award: true) }
scope :nonawards, ->{ where(is_award: false) }
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
- scope :inline, ->{ where("line_code IS NOT NULL") }
- scope :not_inline, ->{ where(line_code: nil) }
scope :system, ->{ where(system: true) }
scope :user, ->{ where(system: false) }
scope :common, ->{ where(noteable_type: ["", nil]) }
@@ -50,38 +46,31 @@ class Note < ActiveRecord::Base
scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) }
+ scope :legacy_diff_notes, ->{ where(type: 'LegacyDiffNote') }
+ scope :non_diff_notes, ->{ where(type: ['Note', nil]) }
+
scope :with_associations, -> do
includes(:author, :noteable, :updated_by,
project: [:project_members, { group: [:group_members] }])
end
- serialize :st_diff
- before_create :set_diff, if: ->(n) { n.line_code.present? }
+ before_validation :clear_blank_line_code!
class << self
- def discussions_from_notes(notes)
- discussion_ids = []
- discussions = []
-
- notes.each do |note|
- next if discussion_ids.include?(note.discussion_id)
-
- # don't group notes for the main target
- if !note.for_diff_line? && note.for_merge_request?
- discussions << [note]
- else
- discussions << notes.select do |other_note|
- note.discussion_id == other_note.discussion_id
- end
- discussion_ids << note.discussion_id
- end
- end
+ def model_name
+ ActiveModel::Name.new(self, nil, 'note')
+ end
+
+ def build_discussion_id(noteable_type, noteable_id)
+ [:discussion, noteable_type.try(:underscore), noteable_id].join("-")
+ end
- discussions
+ def discussions
+ all.group_by(&:discussion_id).values
end
- def build_discussion_id(type, id, line_code)
- [:discussion, type.try(:underscore), id, line_code].join("-").to_sym
+ def grouped_diff_notes
+ legacy_diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
end
# Searches for notes matching the given query.
@@ -116,167 +105,39 @@ class Note < ActiveRecord::Base
system && SystemNoteService.cross_reference?(note)
end
- def max_attachment_size
- current_application_settings.max_attachment_size.megabytes.to_i
- end
-
- def find_diff
- return nil unless noteable
- return @diff if defined?(@diff)
-
- # Don't use ||= because nil is a valid value for @diff
- @diff = noteable.diffs(Commit.max_diff_options).find do |d|
- Digest::SHA1.hexdigest(d.new_path) == diff_file_index if d.new_path
- end
- end
-
- def hook_attrs
- attributes
+ def diff_note?
+ false
end
- def set_diff
- # First lets find notes with same diff
- # before iterating over all mr diffs
- diff = diff_for_line_code unless for_merge_request?
- diff ||= find_diff
-
- self.st_diff = diff.to_hash if diff
+ def legacy_diff_note?
+ false
end
- def diff
- @diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
- end
-
- def diff_for_line_code
- Note.where(noteable_id: noteable_id, noteable_type: noteable_type, line_code: line_code).last.try(:diff)
- end
-
- # Check if this note is part of an "active" discussion
- #
- # This will always return true for anything except MergeRequest noteables,
- # which have special logic.
- #
- # If the note's current diff cannot be matched in the MergeRequest's current
- # diff, it's considered inactive.
def active?
- return true unless self.diff
- return false unless noteable
- return @active if defined?(@active)
-
- noteable_diff = find_noteable_diff
-
- if noteable_diff
- parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line)
-
- @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line }
- else
- @active = false
- end
-
- @active
- end
-
- def diff_file_index
- line_code.split('_')[0] if line_code
- end
-
- def diff_file_name
- diff.new_path if diff
+ true
end
- def file_path
- if diff.new_path.present?
- diff.new_path
- elsif diff.old_path.present?
- diff.old_path
- end
- end
-
- def diff_old_line
- line_code.split('_')[1].to_i if line_code
- end
-
- def diff_new_line
- line_code.split('_')[2].to_i if line_code
- end
-
- def generate_line_code(line)
- Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
- end
-
- def diff_line
- return @diff_line if @diff_line
-
- if diff
- diff_lines.each do |line|
- if generate_line_code(line) == self.line_code
- @diff_line = line.text
- end
- end
- end
-
- @diff_line
- end
-
- def diff_line_type
- return @diff_line_type if @diff_line_type
-
- if diff
- diff_lines.each do |line|
- if generate_line_code(line) == self.line_code
- @diff_line_type = line.type
- end
- end
- end
-
- @diff_line_type
- end
-
- def truncated_diff_lines
- max_number_of_lines = 16
- prev_match_line = nil
- prev_lines = []
-
- highlighted_diff_lines.each do |line|
- if line.type == "match"
- prev_lines.clear
- prev_match_line = line
+ def discussion_id
+ @discussion_id ||=
+ if for_merge_request?
+ [:discussion, :note, id].join("-")
else
- prev_lines << line
-
- break if generate_line_code(line) == self.line_code
-
- prev_lines.shift if prev_lines.length >= max_number_of_lines
+ self.class.build_discussion_id(noteable_type, noteable_id || commit_id)
end
- end
-
- prev_lines
- end
-
- def diff_lines
- @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
end
- def highlighted_diff_lines
- Gitlab::Diff::Highlight.new(diff_lines).highlight
+ def max_attachment_size
+ current_application_settings.max_attachment_size.megabytes.to_i
end
- def discussion_id
- @discussion_id ||= Note.build_discussion_id(noteable_type, noteable_id || commit_id, line_code)
+ def hook_attrs
+ attributes
end
def for_commit?
noteable_type == "Commit"
end
- def for_commit_diff_line?
- for_commit? && for_diff_line?
- end
-
- def for_diff_line?
- line_code.present?
- end
-
def for_issue?
noteable_type == "Issue"
end
@@ -285,10 +146,6 @@ class Note < ActiveRecord::Base
noteable_type == "MergeRequest"
end
- def for_merge_request_diff_line?
- for_merge_request? && for_diff_line?
- end
-
def for_snippet?
noteable_type == "Snippet"
end
@@ -361,14 +218,8 @@ class Note < ActiveRecord::Base
self.line_code = nil if self.line_code.blank?
end
- # Find the diff on noteable that matches our own
- def find_noteable_diff
- diffs = noteable.diffs(Commit.max_diff_options)
- diffs.find { |d| d.new_path == self.diff.new_path }
- end
-
def awards_supported?
- (for_issue? || for_merge_request?) && !for_diff_line?
+ (for_issue? || for_merge_request?) && !diff_note?
end
def contains_emoji_only?
diff --git a/app/models/project.rb b/app/models/project.rb
index 418b85e028a..a3c4f1d8e9b 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -22,6 +22,7 @@ class Project < ActiveRecord::Base
default_value_for :builds_enabled, gitlab_config_features.builds
default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :snippets_enabled, gitlab_config_features.snippets
+ default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
# set last_activity_at to the same as created_at
@@ -49,6 +50,8 @@ class Project < ActiveRecord::Base
attr_accessor :new_default_branch
attr_accessor :old_path_with_namespace
+ alias_attribute :title, :name
+
# Relations
belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
@@ -327,6 +330,12 @@ class Project < ActiveRecord::Base
@repository ||= Repository.new(path_with_namespace, self)
end
+ def container_registry_url
+ if container_registry_enabled? && Gitlab.config.registry.enabled
+ "#{Gitlab.config.registry.host_with_port}/#{path_with_namespace}"
+ end
+ end
+
def commit(id = 'HEAD')
repository.commit(id)
end
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 060ed9b44ec..339fb0b9f9d 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -40,7 +40,7 @@ class ProjectWiki
end
def wiki_base_path
- ["/", @project.path_with_namespace, "/wikis"].join('')
+ [Gitlab.config.gitlab.relative_url_root, "/", @project.path_with_namespace, "/wikis"].join('')
end
# Returns the Gollum::Wiki object.
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 0eff74320f3..3716ea6ad6c 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -195,6 +195,10 @@ class Repository
cache.fetch(:branch_names) { branches.map(&:name) }
end
+ def branch_exists?(branch_name)
+ branch_names.include?(branch_name)
+ end
+
def tag_names
cache.fetch(:tag_names) { raw_repository.tag_names }
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 489bff3fa4a..368a3f3cfba 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -112,6 +112,7 @@ class User < ActiveRecord::Base
before_save :ensure_external_user_rights
after_save :ensure_namespace_correct
after_initialize :set_projects_limit
+ before_create :check_confirmation_email
after_create :post_create_hook
after_destroy :post_destroy_hook
@@ -307,6 +308,10 @@ class User < ActiveRecord::Base
@reset_token
end
+ def check_confirmation_email
+ skip_confirmation! unless current_application_settings.send_user_confirmation_email
+ end
+
def recently_sent_password_reset?
reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
end
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
new file mode 100644
index 00000000000..b636f55d031
--- /dev/null
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -0,0 +1,70 @@
+module Auth
+ class ContainerRegistryAuthenticationService < BaseService
+ AUDIENCE = 'container_registry'
+
+ def execute
+ return error('not found', 404) unless registry.enabled
+
+ if params[:offline_token]
+ return error('forbidden', 403) unless current_user
+ else
+ return error('forbidden', 403) unless scope
+ end
+
+ { token: authorized_token(scope).encoded }
+ end
+
+ private
+
+ def authorized_token(*accesses)
+ token = JSONWebToken::RSAToken.new(registry.key)
+ token.issuer = registry.issuer
+ token.audience = params[:service]
+ token.subject = current_user.try(:username)
+ token[:access] = accesses.compact
+ token
+ end
+
+ def scope
+ return unless params[:scope]
+
+ @scope ||= process_scope(params[:scope])
+ end
+
+ def process_scope(scope)
+ type, name, actions = scope.split(':', 3)
+ actions = actions.split(',')
+ return unless type == 'repository'
+
+ process_repository_access(type, name, actions)
+ end
+
+ def process_repository_access(type, name, actions)
+ requested_project = Project.find_with_namespace(name)
+ return unless requested_project
+
+ actions = actions.select do |action|
+ can_access?(requested_project, action)
+ end
+
+ { type: type, name: name, actions: actions } if actions.present?
+ end
+
+ def can_access?(requested_project, requested_action)
+ return false unless requested_project.container_registry_enabled?
+
+ case requested_action
+ when 'pull'
+ requested_project == project || can?(current_user, :read_container_image, requested_project)
+ when 'push'
+ requested_project == project || can?(current_user, :create_container_image, requested_project)
+ else
+ false
+ end
+ end
+
+ def registry
+ Gitlab.config.registry
+ end
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index f7c799c968f..df286852b97 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -106,6 +106,12 @@
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
+ = f.label :send_user_confirmation_email do
+ = f.check_box :send_user_confirmation_email
+ Send confirmation email on sign-up
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
= f.label :signin_enabled do
= f.check_box :signin_enabled
Sign-in enabled
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index c994e3b997d..f9f623cc031 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -4,7 +4,7 @@
= event_action_name(event)
- if event.target
- %strong= link_to event.target.reference_link_text, [event.project.namespace.becomes(Namespace), event.project, event.target]
+ %strong= link_to event.target.reference_link_text, [event.project.namespace.becomes(Namespace), event.project, event.target], title: event.target_title
= event_preposition(event)
diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml
index 65f0e4c4068..a3643a00cfe 100644
--- a/app/views/notify/note_merge_request_email.html.haml
+++ b/app/views/notify/note_merge_request_email.html.haml
@@ -1,7 +1,7 @@
-- if @note.diff_file_name
+- if @note.legacy_diff_note?
%p.details
New comment on diff for
- = link_to @note.diff_file_name, @target_url
+ = link_to @note.diff_file_path, @target_url
\:
= render 'note_message'
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index 107097ad963..f1577e8a47b 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -15,7 +15,7 @@
= link_text
- else
= link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text }
- - if @comments_allowed && can?(current_user, :create_note, @project)
+ - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
= link_to_new_diff_note(line_code)
%td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
- link_text = type == "old" ? "&nbsp;".html_safe : line.new_pos
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 81948513e43..4ecc9528bd2 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -16,7 +16,7 @@
- else
%td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]} #{'empty-cell' if !left[:number]}"}
= link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code]
- - if @comments_allowed && can?(current_user, :create_note, @project)
+ - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
= link_to_new_diff_note(left[:line_code], 'old')
%td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]} #{'empty-cell' if left[:text].empty?}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text])
@@ -29,14 +29,14 @@
%td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class} #{'empty-cell' if !right[:number]}", data: { linenumber: right[:number] }}
= link_to raw(right[:number]), "##{new_line_code}", id: new_line_code
- - if @comments_allowed && can?(current_user, :create_note, @project)
- = link_to_new_diff_note(right[:line_code], 'new')
+ - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
+ = link_to_new_diff_note(new_line_code, 'new')
%td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code} #{'empty-cell' if right[:text].empty?}", data: { line_code: new_line_code }}= diff_line_content(right[:text])
- - if @reply_allowed
- - comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code])
- - if comments_left.present? || comments_right.present?
- = render "projects/notes/diff_notes_with_reply_parallel", notes_left: comments_left, notes_right: comments_right
+ - unless @diff_notes_disabled
+ - notes_left, notes_right = organize_comments(left, right)
+ - if notes_left.present? || notes_right.present?
+ = render "projects/notes/diff_notes_with_reply_parallel", notes_left: notes_left, notes_right: notes_right
- if diff_file.diff.diff.blank? && diff_file.mode_changed?
.file-mode-changed
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index e7169d7b599..068593a7dd1 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -6,16 +6,15 @@
%table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' }
- last_line = 0
- - raw_diff_lines = diff_file.diff_lines.to_a
- diff_file.highlighted_diff_lines.each_with_index do |line, index|
- line_code = generate_line_code(diff_file.file_path, line)
- last_line = line.new_pos
= render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: line_code}
- - if @reply_allowed
- - comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at)
- - unless comments.empty?
- = render "projects/notes/diff_notes_with_reply", notes: comments, line: raw_diff_lines[index].text
+ - unless @diff_notes_disabled
+ - diff_notes = @grouped_diff_notes[line_code]
+ - if diff_notes
+ = render "projects/notes/diff_notes_with_reply", notes: diff_notes
- if last_line > 0
= render "projects/diffs/match_line", { line: "",
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 76a4f41193c..f6a53fddf17 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -84,6 +84,16 @@
%br
%span.descr Share code pastes with others out of git repository
+ - if Gitlab.config.registry.enabled
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :container_registry_enabled do
+ = f.check_box :container_registry_enabled
+ %strong Container Registry
+ %br
+ %span.descr Enable Container Registry for this repository
+
= render 'builds_settings', f: f
%fieldset.features
diff --git a/app/views/projects/notes/_commit_discussion.html.haml b/app/views/projects/notes/_commit_discussion.html.haml
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/app/views/projects/notes/_commit_discussion.html.haml
+++ /dev/null
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
index 39be072855a..8144c1ba49e 100644
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml
@@ -1,10 +1,8 @@
-- note = notes.first # example note
--# Check if line want not changed since comment was left
-- if !defined?(line) || line == note.diff_line
- %tr.notes_holder
- %td.notes_line{ colspan: 2 }
- %td.notes_content
- %ul.notes{ data: { discussion_id: note.discussion_id } }
- = render notes
- .discussion-reply-holder
- = link_to_reply_diff(note)
+- note = notes.first
+%tr.notes_holder
+ %td.notes_line{ colspan: 2 }
+ %td.notes_content
+ %ul.notes{ data: { discussion_id: note.discussion_id } }
+ = render partial: "projects/notes/note", collection: notes, as: :note
+ .discussion-reply-holder
+ = link_to_reply_discussion(note)
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
index f8aa5e2fa7d..45986b0d1e8 100644
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
@@ -1,27 +1,27 @@
-- note1 = notes_left.present? ? notes_left.first : nil
-- note2 = notes_right.present? ? notes_right.first : nil
+- note_left = notes_left.present? ? notes_left.first : nil
+- note_right = notes_right.present? ? notes_right.first : nil
%tr.notes_holder
- - if note1
+ - if note_left
%td.notes_line.old
%td.notes_content.parallel.old
- %ul.notes{ data: { discussion_id: note1.discussion_id } }
- = render notes_left
+ %ul.notes{ data: { discussion_id: note_left.discussion_id } }
+ = render partial: "projects/notes/note", collection: notes_left, as: :note
.discussion-reply-holder
- = link_to_reply_diff(note1, 'old')
+ = link_to_reply_discussion(note_left, 'old')
- else
%td.notes_line.old= ""
%td.notes_content.parallel.old= ""
- - if note2
+ - if note_right
%td.notes_line.new
%td.notes_content.parallel.new
- %ul.notes{ data: { discussion_id: note2.discussion_id } }
- = render notes_right
+ %ul.notes{ data: { discussion_id: note_right.discussion_id } }
+ = render partial: "projects/notes/note", collection: notes_right, as: :note
.discussion-reply-holder
- = link_to_reply_diff(note2, 'new')
+ = link_to_reply_discussion(note_right, 'new')
- else
%td.notes_line.new= ""
%td.notes_content.parallel.new= ""
diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml
index 572b00a38c7..7869d6413d8 100644
--- a/app/views/projects/notes/_discussion.html.haml
+++ b/app/views/projects/notes/_discussion.html.haml
@@ -1,13 +1,46 @@
- note = discussion_notes.first
+- expanded = !note.diff_note? || note.active?
%li.note.note-discussion.timeline-entry
.timeline-entry-inner
.timeline-icon
= link_to user_path(note.author) do
- = image_tag avatar_icon(note.author_email), class: "avatar s40"
+ = image_tag avatar_icon(note.author), class: "avatar s40"
.timeline-content
- - if note.for_merge_request?
- - (active_notes, outdated_notes) = discussion_notes.partition(&:active?)
- = render "projects/notes/discussions/active", discussion_notes: active_notes if active_notes.length > 0
- = render "projects/notes/discussions/outdated", discussion_notes: outdated_notes if outdated_notes.length > 0
- - else
- = render "projects/notes/discussions/commit", discussion_notes: discussion_notes
+ .discussion.js-toggle-container{ class: note.discussion_id }
+ .discussion-header
+ = link_to_member(@project, note.author, avatar: false)
+
+ .inline.discussion-headline-light
+ = note.author.to_reference
+ started a discussion on
+
+ - if note.for_commit?
+ - commit = note.noteable
+ - if commit
+ commit
+ = link_to commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code), class: 'monospace'
+ - else
+ a deleted commit
+ - else
+ - if note.active?
+ = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
+ the diff
+ - else
+ an outdated diff
+
+ = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "note-created-ago")
+
+ .discussion-actions
+ = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
+ - if expanded
+ = icon("chevron-up")
+ - else
+ = icon("chevron-down")
+
+ Toggle discussion
+
+ .discussion-body.js-toggle-content{ class: ("hide" unless expanded) }
+ - if note.diff_note?
+ = render "projects/notes/discussions/diff_with_notes", discussion_notes: discussion_notes
+ - else
+ = render "projects/notes/discussions/notes", discussion_notes: discussion_notes
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index d0ac380f216..67ed38a7b22 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -6,6 +6,7 @@
= f.hidden_field :line_code
= f.hidden_field :noteable_id
= f.hidden_field :noteable_type
+ = f.hidden_field :type
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here..."
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index aeb7c1d5ee4..9fbc9a45549 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,5 +1,8 @@
+- return unless note.author
+- return if note.cross_reference_not_visible_for?(current_user)
+
- note_editable = note_editable?(note)
-%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
+%li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
.timeline-entry-inner
.timeline-icon
%a{href: user_path(note.author)}
@@ -8,8 +11,8 @@
.note-header
= link_to_member(note.project, note.author, avatar: false)
.inline.note-headline-light
- = "#{note.author.to_reference}"
- - if !note.system
+ = note.author.to_reference
+ - unless note.system
commented
%a{ href: "##{dom_id(note)}" }
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
diff --git a/app/views/projects/notes/_notes.html.haml b/app/views/projects/notes/_notes.html.haml
index 62db86fb181..ebf7e8a9cb3 100644
--- a/app/views/projects/notes/_notes.html.haml
+++ b/app/views/projects/notes/_notes.html.haml
@@ -2,14 +2,9 @@
- @discussions.each do |discussion_notes|
- note = discussion_notes.first
- if note_for_main_target?(note)
- - next if note.cross_reference_not_visible_for?(current_user)
-
- = render discussion_notes
+ = render partial: "projects/notes/note", object: note, as: :note
- else
= render 'projects/notes/discussion', discussion_notes: discussion_notes
- else
- @notes.each do |note|
- - next unless note.author
- - next if note.cross_reference_not_visible_for?(current_user)
-
- = render note
+ = render partial: "projects/notes/note", object: note, as: :note
diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml
deleted file mode 100644
index 0ea8862a684..00000000000
--- a/app/views/projects/notes/discussions/_active.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-- note = discussion_notes.first
-.discussion.js-toggle-container{ class: note.discussion_id }
- .discussion-header
- = link_to_member(@project, note.author, avatar: false)
- .inline.discussion-headline-light
- = "#{note.author.to_reference} started a discussion"
- = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
- on the diff
- = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
- .discussion-actions
- = link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do
- %i.fa.fa-chevron-up
- Show/hide discussion
-
- .discussion-body.js-toggle-content
- = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
deleted file mode 100644
index 2a2ead58eeb..00000000000
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ /dev/null
@@ -1,25 +0,0 @@
-- note = discussion_notes.first
-- commit = note.noteable
-- commit_description = commit ? 'commit' : 'a deleted commit'
-.discussion.js-toggle-container{ class: note.discussion_id }
- .discussion-header
- = link_to_member(@project, note.author, avatar: false)
- .inline.discussion-headline-light
- = "#{note.author.to_reference} started a discussion on #{commit_description}"
- - if commit
- = link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
- = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
- .discussion-actions
- = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
- %i.fa.fa-chevron-up
- Show/hide discussion
- .discussion-body.js-toggle-content
- - if note.for_diff_line?
- = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
- - else
- .panel.panel-default
- .notes{ data: { discussion_id: discussion_notes.first.discussion_id } }
- %ul.notes.timeline
- = render discussion_notes
- .discussion-reply-holder
- = link_to_reply_diff(discussion_notes.first)
diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml
deleted file mode 100644
index d46aab000c3..00000000000
--- a/app/views/projects/notes/discussions/_diff.html.haml
+++ /dev/null
@@ -1,28 +0,0 @@
-- diff = note.diff
-- if diff
- .diff-file
- .diff-header
- %span
- - if diff.deleted_file
- = diff.old_path
- - else
- = diff.new_path
- - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
- %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
- .diff-content.code.js-syntax-highlight
- %table
- - note.truncated_diff_lines.each do |line|
- - type = line.type
- - line_code = generate_line_code(note.file_path, line)
- %tr.line_holder{ id: line_code, class: "#{type}" }
- - if type == "match"
- %td.old_line.diff-line-num= "..."
- %td.new_line.diff-line-num= "..."
- %td.line_content.match= line.text
- - else
- %td.old_line.diff-line-num{ data: { linenumber: type == "new" ? "&nbsp;".html_safe : line.old_pos } }
- %td.new_line.diff-line-num{ data: { linenumber: type == "old" ? "&nbsp;".html_safe : line.new_pos } }
- %td.line_content{ class: ['noteable_line', type, line_code], line_code: line_code }= diff_line_content(line.text, type)
-
- - if line_code == note.line_code
- = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/notes/discussions/_diff_with_notes.html.haml b/app/views/projects/notes/discussions/_diff_with_notes.html.haml
new file mode 100644
index 00000000000..6401245bf73
--- /dev/null
+++ b/app/views/projects/notes/discussions/_diff_with_notes.html.haml
@@ -0,0 +1,30 @@
+- note = discussion_notes.first
+- diff = note.diff
+- return unless diff
+
+.diff-file
+ .diff-header
+ %span
+ - if diff.deleted_file
+ = diff.old_path
+ - else
+ = diff.new_path
+ - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
+ %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
+ .diff-content.code.js-syntax-highlight
+ %table
+ - note.truncated_diff_lines.each do |line|
+ - type = line.type
+ - line_code = generate_line_code(note.diff_file_path, line)
+ %tr.line_holder{ id: line_code, class: "#{type}" }
+ - if type == "match"
+ %td.old_line.diff-line-num= "..."
+ %td.new_line.diff-line-num= "..."
+ %td.line_content.match= line.text
+ - else
+ %td.old_line.diff-line-num{ data: { linenumber: type == "new" ? "&nbsp;".html_safe : line.old_pos } }
+ %td.new_line.diff-line-num{ data: { linenumber: type == "old" ? "&nbsp;".html_safe : line.new_pos } }
+ %td.line_content{ class: ['noteable_line', type, line_code], line_code: line_code }= diff_line_content(line.text, type)
+
+ - if line_code == note.line_code
+ = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/notes/discussions/_notes.html.haml b/app/views/projects/notes/discussions/_notes.html.haml
new file mode 100644
index 00000000000..e598e3c7c63
--- /dev/null
+++ b/app/views/projects/notes/discussions/_notes.html.haml
@@ -0,0 +1,7 @@
+- note = discussion_notes.first
+.panel.panel-default
+ .notes{ data: { discussion_id: note.discussion_id } }
+ %ul.notes.timeline
+ = render partial: "projects/notes/note", collection: discussion_notes, as: :note
+ .discussion-reply-holder
+ = link_to_reply_discussion(note)
diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml
deleted file mode 100644
index 45141bcd1df..00000000000
--- a/app/views/projects/notes/discussions/_outdated.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-- note = discussion_notes.first
-.discussion.js-toggle-container{ class: note.discussion_id }
- .discussion-header
- = link_to_member(@project, note.author, avatar: false)
- .inline.discussion-headline-light
- = "#{note.author.to_reference} started a discussion"
- on the outdated diff
- = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
- .discussion-actions
- = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
- %i.fa.fa-chevron-down
- Show/hide discussion
- .discussion-body.js-toggle-content.hide
- = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index e682bcb976d..d935121d88b 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -98,6 +98,7 @@ production: &base
wiki: true
snippets: false
builds: true
+ container_registry: true
## Webhook settings
# Number of seconds to wait for HTTP response after sending webhook HTTP POST request (default: 10)
@@ -175,6 +176,14 @@ production: &base
repository_archive_cache_worker:
cron: "0 * * * *"
+ registry:
+ # enabled: true
+ # host: registry.example.com
+ # port: 5000
+ # api_url: http://localhost:5000/
+ # key: config/registry.key
+ # issuer: omnibus-certificate
+
#
# 2. GitLab CI settings
# ==========================
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index a69b933d811..d1fcb053bee 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -206,12 +206,13 @@ Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10
Settings.gitlab['session_expire_delay'] ||= 10080
-Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
-Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil?
-Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil?
-Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil?
-Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil?
-Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
+Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
+Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil?
+Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil?
+Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil?
+Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil?
+Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil?
+Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil?
Settings.gitlab['restricted_signup_domains'] ||= []
Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git']
@@ -243,6 +244,16 @@ Settings.artifacts['path'] = File.expand_path(Settings.artifacts['path']
Settings.artifacts['max_size'] ||= 100 # in megabytes
#
+# Registry
+#
+Settings['registry'] ||= Settingslogic.new({})
+Settings.registry['enabled'] ||= false
+Settings.registry['host'] ||= "example.com"
+Settings.registry['api_url'] ||= "http://localhost:5000/"
+Settings.registry['key'] ||= nil
+Settings.registry['issuer'] ||= nil
+
+#
# Git LFS
#
Settings['lfs'] ||= Settingslogic.new({})
diff --git a/config/routes.rb b/config/routes.rb
index ef2a7b778c7..43a7cc0c82f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -64,6 +64,9 @@ Rails.application.routes.draw do
get 'search' => 'search#show'
get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete
+ # JSON Web Token
+ get 'jwt/auth' => 'jwt#auth'
+
# API
API::API.logger Rails.logger
mount API::API => '/api'
diff --git a/db/migrate/20160407120251_add_images_enabled_for_project.rb b/db/migrate/20160407120251_add_images_enabled_for_project.rb
new file mode 100644
index 00000000000..47f0ca8e8de
--- /dev/null
+++ b/db/migrate/20160407120251_add_images_enabled_for_project.rb
@@ -0,0 +1,5 @@
+class AddImagesEnabledForProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :container_registry_enabled, :boolean
+ end
+end
diff --git a/db/migrate/20160508215820_add_type_to_notes.rb b/db/migrate/20160508215820_add_type_to_notes.rb
new file mode 100644
index 00000000000..58944d4e651
--- /dev/null
+++ b/db/migrate/20160508215820_add_type_to_notes.rb
@@ -0,0 +1,5 @@
+class AddTypeToNotes < ActiveRecord::Migration
+ def change
+ add_column :notes, :type, :string
+ end
+end
diff --git a/db/migrate/20160508221410_set_type_on_legacy_diff_notes.rb b/db/migrate/20160508221410_set_type_on_legacy_diff_notes.rb
new file mode 100644
index 00000000000..c3f23d89d5a
--- /dev/null
+++ b/db/migrate/20160508221410_set_type_on_legacy_diff_notes.rb
@@ -0,0 +1,5 @@
+class SetTypeOnLegacyDiffNotes < ActiveRecord::Migration
+ def change
+ execute "UPDATE notes SET type = 'LegacyDiffNote' WHERE line_code IS NOT NULL"
+ end
+end
diff --git a/db/migrate/20160516174813_add_send_user_confirmation_email_to_application_settings.rb b/db/migrate/20160516174813_add_send_user_confirmation_email_to_application_settings.rb
new file mode 100644
index 00000000000..c34e7ba5409
--- /dev/null
+++ b/db/migrate/20160516174813_add_send_user_confirmation_email_to_application_settings.rb
@@ -0,0 +1,12 @@
+class AddSendUserConfirmationEmailToApplicationSettings < ActiveRecord::Migration
+ def up
+ add_column :application_settings, :send_user_confirmation_email, :boolean, default: false
+
+ #Sets confirmation email to true by default on existing installations.
+ execute "UPDATE application_settings SET send_user_confirmation_email=true"
+ end
+
+ def down
+ remove_column :application_settings, :send_user_confirmation_email
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9b5aa640cb0..af4f4c609e7 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -632,10 +632,11 @@ ActiveRecord::Schema.define(version: 20160509201028) do
t.string "line_code"
t.string "commit_id"
t.integer "noteable_id"
- t.boolean "system", default: false, null: false
+ t.boolean "system", default: false, null: false
t.text "st_diff"
t.integer "updated_by_id"
- t.boolean "is_award", default: false, null: false
+ t.boolean "is_award", default: false, null: false
+ t.string "type"
end
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
@@ -761,6 +762,7 @@ ActiveRecord::Schema.define(version: 20160509201028) do
t.integer "pushes_since_gc", default: 0
t.boolean "last_repository_check_failed"
t.datetime "last_repository_check_at"
+ t.boolean "container_registry_enabled"
end
add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md
index 43ab153d76d..7f53915a4d7 100644
--- a/doc/administration/environment_variables.md
+++ b/doc/administration/environment_variables.md
@@ -58,4 +58,4 @@ to the naming scheme `GITLAB_#{name in 1_settings.rb in upper case}`.
It's possible to preconfigure the GitLab docker image by adding the environment
variable `GITLAB_OMNIBUS_CONFIG` to the `docker run` command.
-For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://doc.gitlab.com/omnibus/docker/#preconfigure-docker-container).
+For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://docs.gitlab.com/omnibus/docker/#preconfigure-docker-container).
diff --git a/doc/administration/high_availability/load_balancer.md b/doc/administration/high_availability/load_balancer.md
index b1fe34ed9a1..136f570ac27 100644
--- a/doc/administration/high_availability/load_balancer.md
+++ b/doc/administration/high_availability/load_balancer.md
@@ -60,4 +60,4 @@ Read more on high-availability configuration:
configure custom domains with custom SSL, which would not be possible
if SSL was terminated at the load balancer.
-[gitlab-pages]: http://doc.gitlab.com/ee/pages/administration.html
+[gitlab-pages]: http://docs.gitlab.com/ee/pages/administration.html
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index e4e124e200a..49ff5d536a1 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -113,4 +113,4 @@ Read more on high-availability configuration:
1. [Configure the GitLab application servers](gitlab.md)
1. [Configure the load balancers](load_balancer.md)
-[udp-log-shipping]: http://doc.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only "UDP log shipping"
+[udp-log-shipping]: http://docs.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only "UDP log shipping"
diff --git a/doc/api/projects.md b/doc/api/projects.md
index de1faadebf5..f5f195b97df 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -424,6 +424,7 @@ Parameters:
- `builds_enabled` (optional)
- `wiki_enabled` (optional)
- `snippets_enabled` (optional)
+- `container_registry_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `import_url` (optional)
@@ -447,6 +448,7 @@ Parameters:
- `builds_enabled` (optional)
- `wiki_enabled` (optional)
- `snippets_enabled` (optional)
+- `container_registry_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `import_url` (optional)
@@ -472,6 +474,7 @@ Parameters:
- `builds_enabled` (optional)
- `wiki_enabled` (optional)
- `snippets_enabled` (optional)
+- `container_registry_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `public_builds` (optional)
diff --git a/doc/api/services.md b/doc/api/services.md
index 7d45b2cf463..83ac7845156 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -491,7 +491,7 @@ Jira issue tracker
Set JIRA service for a project.
-> Setting `project_url`, `issues_url` and `new_issue_url` will allow a user to easily navigate to the Jira issue tracker. See the [integration doc](http://doc.gitlab.com/ce/integration/external-issue-tracker.html) for details. Support for referencing commits and automatic closing of Jira issues directly from GitLab is [available in GitLab EE.](http://doc.gitlab.com/ee/integration/jira.html)
+> Setting `project_url`, `issues_url` and `new_issue_url` will allow a user to easily navigate to the Jira issue tracker. See the [integration doc](http://docs.gitlab.com/ce/integration/external-issue-tracker.html) for details. Support for referencing commits and automatic closing of Jira issues directly from GitLab is [available in GitLab EE.](http://docs.gitlab.com/ee/integration/jira.html)
```
PUT /projects/:id/services/jira
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 187ec9e7b75..8292b393757 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -127,7 +127,7 @@ Inside the document:
```
If the document you are editing resides in a place other than the GitLab CE/EE
`doc/` directory, instead of the relative link, use the full path:
- `http://doc.gitlab.com/ce/administration/restart_gitlab.html`.
+ `http://docs.gitlab.com/ce/administration/restart_gitlab.html`.
Replace `reconfigure` with `restart` where appropriate.
## Installation guide
@@ -266,5 +266,5 @@ curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "restricted_signup_domai
[cURL]: http://curl.haxx.se/ "cURL website"
[single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html
-[gfm]: http://doc.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation"
+[gfm]: http://docs.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation"
[doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation"
diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md
index 87f078def04..5221d85b661 100644
--- a/doc/gitlab-basics/create-issue.md
+++ b/doc/gitlab-basics/create-issue.md
@@ -24,4 +24,4 @@ You may assign the Issue to a user, add a milestone and add labels (they are all
![Submit new issue](basicsimages/submit_new_issue.png)
-Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](http://doc.gitlab.com/ce/customization/issue_closing.html).
+Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](http://docs.gitlab.com/ce/customization/issue_closing.html).
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index b545d62549d..f737dffc024 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -14,7 +14,7 @@ Fill out the required information:
1. Select a [visibility level](https://gitlab.com/help/public_access/public_access)
-1. You can also [import your existing projects](http://doc.gitlab.com/ce/workflow/importing/README.html)
+1. You can also [import your existing projects](http://docs.gitlab.com/ce/workflow/importing/README.html)
1. Click on "create project"
diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md
index dcdf49d3379..820934f97f1 100644
--- a/doc/hooks/custom_hooks.md
+++ b/doc/hooks/custom_hooks.md
@@ -2,7 +2,7 @@
**Note: Custom git hooks must be configured on the filesystem of the GitLab
server. Only GitLab server administrators will be able to complete these tasks.
-Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://doc.gitlab.com/ee/git_hooks/git_hooks.html).**
+Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://docs.gitlab.com/ee/git_hooks/git_hooks.html).**
Git natively supports hooks that are executed on different actions.
Examples of server-side git hooks include pre-receive, post-receive, and update.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 44ae0be406c..fa11eb9ba6a 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -6,7 +6,7 @@ Since an installation from source is a lot of work and error prone we strongly r
One reason the Omnibus package is more reliable is its use of Runit to restart any of the GitLab processes in case one crashes.
On heavily used GitLab instances the memory usage of the Sidekiq background worker will grow over time.
-Omnibus packages solve this by [letting the Sidekiq terminate gracefully](http://doc.gitlab.com/ce/operations/sidekiq_memory_killer.html) if it uses too much memory.
+Omnibus packages solve this by [letting the Sidekiq terminate gracefully](http://docs.gitlab.com/ce/operations/sidekiq_memory_killer.html) if it uses too much memory.
After this termination Runit will detect Sidekiq is not running and will start it.
Since installations from source don't have Runit, Sidekiq can't be terminated and its memory usage will grow over time.
diff --git a/doc/install/relative_url.md b/doc/install/relative_url.md
index 0245febfcd8..44d2a14f366 100644
--- a/doc/install/relative_url.md
+++ b/doc/install/relative_url.md
@@ -132,5 +132,5 @@ To disable the relative URL:
1. Follow the same as above starting from 2. and set up the
GitLab URL to one that doesn't contain a relative path.
-[omnibus-rel]: http://doc.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab "How to setup relative URL in Omnibus GitLab"
+[omnibus-rel]: http://docs.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab "How to setup relative URL in Omnibus GitLab"
[restart gitlab]: ../administration/restart_gitlab.md#installations-from-source "How to restart GitLab"
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 6fe04aa2a06..fd330dd7a7d 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -19,7 +19,7 @@ See the documentation below for details on how to configure these services.
GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
-[jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html
+[jenkins]: http://docs.gitlab.com/ee/integration/jenkins.html
## Project services
diff --git a/doc/intro/README.md b/doc/intro/README.md
index ab298d3808e..382d10aaf40 100644
--- a/doc/intro/README.md
+++ b/doc/intro/README.md
@@ -39,4 +39,4 @@ Install and update your GitLab installation.
- [Install GitLab](https://about.gitlab.com/installation/)
- [Update GitLab](https://about.gitlab.com/update/)
-- [Explore Omnibus GitLab configuration options](http://doc.gitlab.com/omnibus/settings/configuration.html)
+- [Explore Omnibus GitLab configuration options](http://docs.gitlab.com/omnibus/settings/configuration.html)
diff --git a/doc/logs/logs.md b/doc/logs/logs.md
index 27937e51764..ef5affa2ebd 100644
--- a/doc/logs/logs.md
+++ b/doc/logs/logs.md
@@ -1,6 +1,6 @@
## Log system
GitLab has advanced log system so everything is logging and you can analize your instance using various system log files.
-In addition to system log files, GitLab Enterprise Edition comes with Audit Events. Find more about them [in Audit Events documentation](http://doc.gitlab.com/ee/administration/audit_events.html)
+In addition to system log files, GitLab Enterprise Edition comes with Audit Events. Find more about them [in Audit Events documentation](http://docs.gitlab.com/ee/administration/audit_events.html)
System log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files.
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 6219693b8a8..6be5ea0b486 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -27,6 +27,7 @@ documentation](../workflow/add-user/add-user.md).
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
| Manage labels | | ✓ | ✓ | ✓ | ✓ |
| See a commit status | | ✓ | ✓ | ✓ | ✓ |
+| See a container registry | | ✓ | ✓ | ✓ | ✓ |
| Manage merge requests | | | ✓ | ✓ | ✓ |
| Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
@@ -37,6 +38,7 @@ documentation](../workflow/add-user/add-user.md).
| Write a wiki | | | ✓ | ✓ | ✓ |
| Cancel and retry builds | | | ✓ | ✓ | ✓ |
| Create or update commit status | | | ✓ | ✓ | ✓ |
+| Update a container registry | | | ✓ | ✓ | ✓ |
| Create new milestones | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ |
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index 6be954ad68b..a49c43b8ef2 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -8,4 +8,4 @@
- [User management](user_management.md)
- [Webhooks](web_hooks.md)
- [Import](import.md) of git repositories in bulk
-- [Rebuild authorized_keys file](http://doc.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators
+- [Rebuild authorized_keys file](http://docs.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators
diff --git a/doc/security/README.md b/doc/security/README.md
index 4cd0fdd4094..38706e48ec5 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -8,3 +8,4 @@
- [User File Uploads](user_file_uploads.md)
- [How we manage the CRIME vulnerability](crime_vulnerability.md)
- [Enforce Two-factor authentication](two_factor_authentication.md)
+- [Send email confirmation on sign-up](user_email_confirmation.md)
diff --git a/doc/security/user_email_confirmation.md b/doc/security/user_email_confirmation.md
new file mode 100644
index 00000000000..4293944ae8b
--- /dev/null
+++ b/doc/security/user_email_confirmation.md
@@ -0,0 +1,7 @@
+# User email confirmation at sign-up
+
+Gitlab admin can enable email confirmation on sign-up, if you want to confirm all
+user emails before they are able to sign-in.
+
+In the Admin area under **Settings** (`/admin/application_settings`), go to section
+**Sign-in Restrictions** and look for **Send confirmation email on sign-up** option.
diff --git a/doc/update/README.md b/doc/update/README.md
index a770633c9b8..975d72164b4 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -29,7 +29,7 @@ Based on your installation, choose a section below that fits your needs.
## Omnibus Packages
-- The [Omnibus update guide](http://doc.gitlab.com/omnibus/update/README.html)
+- The [Omnibus update guide](http://docs.gitlab.com/omnibus/update/README.html)
contains the steps needed to update an Omnibus GitLab package.
## Installation from source
@@ -86,10 +86,10 @@ possible.
information about configuring GitLab to work with a MySQL database.
- [Restoring from backup after a failed upgrade](restore_after_failure.md)
-[omnidocker]: http://doc.gitlab.com/omnibus/docker/README.html
+[omnidocker]: http://docs.gitlab.com/omnibus/docker/README.html
[source-ee]: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update
[source-ce]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update
[ee-ce]: ../downgrade_ee_to_ce/README.md
[ce]: https://about.gitlab.com/features/#community
[ee]: https://about.gitlab.com/features/#enterprise
-[omni-ce-ee]: http://doc.gitlab.com/omnibus/update/README.html#from-community-edition-to-enterprise-edition
+[omni-ce-ee]: http://docs.gitlab.com/omnibus/update/README.html#from-community-edition-to-enterprise-edition
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index 1b354bcc0f1..2b2f140f8bf 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -131,7 +131,7 @@ When you feel comfortable with it to be merged you assign it to the person that
There is room for more feedback and after the assigned person feels comfortable with the result the branch is merged.
If the assigned person does not feel comfortable they can close the merge request without merging.
-In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](http://doc.gitlab.com/ce/permissions/permissions.html).
+In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](http://docs.gitlab.com/ce/permissions/permissions.html).
So if you want to merge it into a protected branch you assign it to someone with master authorizations.
## Issues with GitLab flow
@@ -187,7 +187,7 @@ If you have an issue that spans across multiple repositories, the best thing is
![Vim screen showing the rebase view](rebase.png)
With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them.
-In GitLab EE and .com you can also [rebase before merge](http://doc.gitlab.com/ee/workflow/rebase_before_merge.html) from the web interface.
+In GitLab EE and .com you can also [rebase before merge](http://docs.gitlab.com/ee/workflow/rebase_before_merge.html) from the web interface.
This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
However you should never rebase commits you have pushed to a remote server.
Somebody can have referred to the commits or cherry-picked them.
diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md
index 52bf611dc5e..34ada1774d8 100644
--- a/doc/workflow/groups.md
+++ b/doc/workflow/groups.md
@@ -54,7 +54,7 @@ If necessary, you can increase the access level of an individual user for a spec
## Managing group memberships via LDAP
In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
-See [the GitLab Enterprise Edition documentation](http://doc.gitlab.com/ee/integration/ldap.html) for more information.
+See [the GitLab Enterprise Edition documentation](http://docs.gitlab.com/ee/integration/ldap.html) for more information.
## Allowing only admins to create groups
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index e670e415c71..a7dfac2c120 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -44,5 +44,5 @@ case the namespace is taken, the project will be imported on the user's
namespace.
[gh-import]: ../../integration/github.md "GitHub integration"
-[ee-gh]: http://doc.gitlab.com/ee/integration/github.html "GitHub integration for GitLab EE"
+[ee-gh]: http://docs.gitlab.com/ee/integration/github.html "GitHub integration for GitLab EE"
[new-project]: ../../gitlab-basics/create-project.md "How to create a new project in GitLab"
diff --git a/doc/workflow/importing/import_projects_from_gitlab_com.md b/doc/workflow/importing/import_projects_from_gitlab_com.md
index 1117db98e7e..dcc00074b75 100644
--- a/doc/workflow/importing/import_projects_from_gitlab_com.md
+++ b/doc/workflow/importing/import_projects_from_gitlab_com.md
@@ -2,7 +2,7 @@
You can import your existing GitLab.com projects to your GitLab instance. But keep in mind that it is possible only if
GitLab support is enabled on your GitLab instance.
-You can read more about GitLab support [here](http://doc.gitlab.com/ce/integration/gitlab.html)
+You can read more about GitLab support [here](http://docs.gitlab.com/ce/integration/gitlab.html)
To get to the importer page you need to go to "New project" page.
![New project page](gitlab_importer/new_project_page.png)
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index 83db44c10b1..1295dfbd770 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -4,7 +4,7 @@ Managing large files such as audio, video and graphics files has always been one
of the shortcomings of Git. The general recommendation is to not have Git repositories
larger than 1GB to preserve performance.
-GitLab already supports [managing large files with git annex](http://doc.gitlab.com/ee/workflow/git_annex.html)
+GitLab already supports [managing large files with git annex](http://docs.gitlab.com/ee/workflow/git_annex.html)
(EE only), however in certain environments it is not always convenient to use
different commands to differentiate between the large files and regular ones.
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index e846c52d474..e8b1e4b4879 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -23,7 +23,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[id$='#{sample_commit.line_code}']") do
+ page.within("form[id$='#{sample_commit.line_code}-true']") do
fill_in "note[note]", with: "Typo, please fix"
find(".js-comment-button").trigger("click")
sleep 0.05
@@ -33,7 +33,7 @@ module SharedDiffNote
step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do
click_parallel_diff_line(sample_commit.line_code, 'old')
- page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do
+ page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}-true']") do
fill_in "note[note]", with: "Old comment"
find(".js-comment-button").trigger("click")
end
@@ -41,7 +41,7 @@ module SharedDiffNote
step 'I leave a diff comment in a parallel view on the right side like "New comment"' do
click_parallel_diff_line(sample_commit.line_code, 'new')
- page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do
+ page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}-true']") do
fill_in "note[note]", with: "New comment"
find(".js-comment-button").trigger("click")
end
@@ -51,7 +51,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[id$='#{sample_commit.line_code}']") do
+ page.within("form[id$='#{sample_commit.line_code}-true']") do
fill_in "note[note]", with: "Should fix it :smile:"
find('.js-md-preview-button').click
end
@@ -62,7 +62,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.del_line_code)
- page.within("form[id$='#{sample_commit.del_line_code}']") do
+ page.within("form[id$='#{sample_commit.del_line_code}-true']") do
fill_in "note[note]", with: "DRY this up"
find('.js-md-preview-button').click
end
@@ -91,7 +91,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[id$='#{sample_commit.line_code}']") do
+ page.within("form[id$='#{sample_commit.line_code}-true']") do
fill_in 'note[note]', with: ':smile:'
click_button('Comment')
end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 93a3a5ce089..4a11c8e3620 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -107,6 +107,8 @@ module API
break if opts[:line_code]
end
+
+ opts[:type] = LegacyDiffNote.name if opts[:line_code]
end
note = ::Notes::CreateService.new(user_project, current_user, opts).execute
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 406f5ea9139..dbd03ea74fa 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -66,7 +66,8 @@ module API
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
expose :name, :name_with_namespace
expose :path, :path_with_namespace
- expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :created_at, :last_activity_at
+ expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :container_registry_enabled
+ expose :created_at, :last_activity_at
expose :shared_runners_enabled
expose :creator_id
expose :namespace
@@ -227,9 +228,9 @@ module API
class CommitNote < Grape::Entity
expose :note
- expose(:path) { |note| note.diff_file_name }
- expose(:line) { |note| note.diff_new_line }
- expose(:line_type) { |note| note.diff_line_type }
+ expose(:path) { |note| note.diff_file_path if note.legacy_diff_note? }
+ expose(:line) { |note| note.diff_new_line if note.legacy_diff_note? }
+ expose(:line_type) { |note| note.diff_line_type if note.legacy_diff_note? }
expose :author, using: Entities::UserBasic
expose :created_at
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 9b595772675..5a22d14988f 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -94,6 +94,7 @@ module API
# builds_enabled (optional)
# wiki_enabled (optional)
# snippets_enabled (optional)
+ # container_registry_enabled (optional)
# shared_runners_enabled (optional)
# namespace_id (optional) - defaults to user namespace
# public (optional) - if true same as setting visibility_level = 20
@@ -112,6 +113,7 @@ module API
:builds_enabled,
:wiki_enabled,
:snippets_enabled,
+ :container_registry_enabled,
:shared_runners_enabled,
:namespace_id,
:public,
@@ -143,6 +145,7 @@ module API
# builds_enabled (optional)
# wiki_enabled (optional)
# snippets_enabled (optional)
+ # container_registry_enabled (optional)
# shared_runners_enabled (optional)
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional)
@@ -206,6 +209,7 @@ module API
# builds_enabled (optional)
# wiki_enabled (optional)
# snippets_enabled (optional)
+ # container_registry_enabled (optional)
# shared_runners_enabled (optional)
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) - visibility level of a project
@@ -222,6 +226,7 @@ module API
:builds_enabled,
:wiki_enabled,
:snippets_enabled,
+ :container_registry_enabled,
:shared_runners_enabled,
:public,
:visibility_level,
diff --git a/lib/gitlab/github_import/branch_formatter.rb b/lib/gitlab/github_import/branch_formatter.rb
new file mode 100644
index 00000000000..a15fc84b418
--- /dev/null
+++ b/lib/gitlab/github_import/branch_formatter.rb
@@ -0,0 +1,29 @@
+module Gitlab
+ module GithubImport
+ class BranchFormatter < BaseFormatter
+ delegate :repo, :sha, :ref, to: :raw_data
+
+ def exists?
+ project.repository.branch_exists?(ref)
+ end
+
+ def name
+ @name ||= exists? ? ref : "#{ref}-#{short_id}"
+ end
+
+ def valid?
+ repo.present?
+ end
+
+ def valid?
+ repo.present?
+ end
+
+ private
+
+ def short_id
+ sha.to_s[0..7]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 0f9e3ee14ee..408d9b79632 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -3,12 +3,15 @@ module Gitlab
class Importer
include Gitlab::ShellAdapter
- attr_reader :project, :client
+ attr_reader :client, :project, :repo, :repo_url
def initialize(project)
- @project = project
- if import_data_credentials
- @client = Client.new(import_data_credentials[:user])
+ @project = project
+ @repo = project.import_source
+ @repo_url = project.import_url
+
+ if credentials
+ @client = Client.new(credentials[:user])
@formatter = Gitlab::ImportFormatter.new
else
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
@@ -22,12 +25,12 @@ module Gitlab
private
- def import_data_credentials
- @import_data_credentials ||= project.import_data.credentials if project.import_data
+ def credentials
+ @credentials ||= project.import_data.credentials if project.import_data
end
def import_labels
- client.labels(project.import_source).each do |raw_data|
+ client.labels(repo).each do |raw_data|
Label.create!(LabelFormatter.new(project, raw_data).attributes)
end
@@ -37,7 +40,7 @@ module Gitlab
end
def import_milestones
- client.list_milestones(project.import_source, state: :all).each do |raw_data|
+ client.list_milestones(repo, state: :all).each do |raw_data|
Milestone.create!(MilestoneFormatter.new(project, raw_data).attributes)
end
@@ -47,9 +50,7 @@ module Gitlab
end
def import_issues
- client.list_issues(project.import_source, state: :all,
- sort: :created,
- direction: :asc).each do |raw_data|
+ client.list_issues(repo, state: :all, sort: :created, direction: :asc).each do |raw_data|
gh_issue = IssueFormatter.new(project, raw_data)
if gh_issue.valid?
@@ -68,29 +69,50 @@ module Gitlab
end
def import_pull_requests
- client.pull_requests(project.import_source, state: :all,
- sort: :created,
- direction: :asc).each do |raw_data|
- pull_request = PullRequestFormatter.new(project, raw_data)
-
- if pull_request.valid?
- merge_request = MergeRequest.new(pull_request.attributes)
-
- if merge_request.save
- apply_labels(pull_request.number, merge_request)
- import_comments(pull_request.number, merge_request)
- import_comments_on_diff(pull_request.number, merge_request)
- end
+ pull_requests = client.pull_requests(repo, state: :all, sort: :created, direction: :asc)
+ .map { |raw| PullRequestFormatter.new(project, raw) }
+ .select(&:valid?)
+
+ source_branches_removed = pull_requests.reject(&:source_branch_exists?).map { |pr| [pr.source_branch_name, pr.source_branch_sha] }
+ target_branches_removed = pull_requests.reject(&:target_branch_exists?).map { |pr| [pr.target_branch_name, pr.target_branch_sha] }
+ branches_removed = source_branches_removed | target_branches_removed
+
+ create_refs(branches_removed)
+
+ pull_requests.each do |pull_request|
+ merge_request = MergeRequest.new(pull_request.attributes)
+
+ if merge_request.save
+ apply_labels(pull_request.number, merge_request)
+ import_comments(pull_request.number, merge_request)
+ import_comments_on_diff(pull_request.number, merge_request)
end
end
+ delete_refs(branches_removed)
+
true
rescue ActiveRecord::RecordInvalid => e
raise Projects::ImportService::Error, e.message
end
+ def create_refs(branches)
+ branches.each do |name, sha|
+ client.create_ref(repo, "refs/heads/#{name}", sha)
+ end
+
+ project.repository.fetch_ref(repo_url, '+refs/heads/*', 'refs/heads/*')
+ end
+
+ def delete_refs(branches)
+ branches.each do |name, _|
+ client.delete_ref(repo, "heads/#{name}")
+ project.repository.rm_branch(project.creator, name)
+ end
+ end
+
def apply_labels(number, issuable)
- issue = client.issue(project.import_source, number)
+ issue = client.issue(repo, number)
if issue.labels.count > 0
label_ids = issue.labels.map do |raw|
@@ -102,12 +124,12 @@ module Gitlab
end
def import_comments(issue_number, noteable)
- comments = client.issue_comments(project.import_source, issue_number)
+ comments = client.issue_comments(repo, issue_number)
create_comments(comments, noteable)
end
def import_comments_on_diff(pull_request_number, merge_request)
- comments = client.pull_request_comments(project.import_source, pull_request_number)
+ comments = client.pull_request_comments(repo, pull_request_number)
create_comments(comments, merge_request)
end
diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb
index d21b942ad4b..574737b31c1 100644
--- a/lib/gitlab/github_import/pull_request_formatter.rb
+++ b/lib/gitlab/github_import/pull_request_formatter.rb
@@ -1,15 +1,20 @@
module Gitlab
module GithubImport
class PullRequestFormatter < BaseFormatter
+ delegate :exists?, :name, :project, :repo, :sha, to: :source_branch, prefix: true
+ delegate :exists?, :name, :project, :repo, :sha, to: :target_branch, prefix: true
+
def attributes
{
iid: number,
title: raw_data.title,
description: description,
- source_project: source_project,
- source_branch: source_branch.name,
- target_project: target_project,
- target_branch: target_branch.name,
+ source_project: source_branch_project,
+ source_branch: source_branch_name,
+ head_source_sha: source_branch_sha,
+ target_project: target_branch_project,
+ target_branch: target_branch_name,
+ base_target_sha: target_branch_sha,
state: state,
milestone: milestone,
author_id: author_id,
@@ -24,7 +29,15 @@ module Gitlab
end
def valid?
- !cross_project? && source_branch.present? && target_branch.present?
+ source_branch.valid? && target_branch.valid? && !cross_project?
+ end
+
+ def source_branch
+ @source_branch ||= BranchFormatter.new(project, raw_data.head)
+ end
+
+ def target_branch
+ @target_branch ||= BranchFormatter.new(project, raw_data.base)
end
private
@@ -52,7 +65,7 @@ module Gitlab
end
def cross_project?
- source_repo.present? && target_repo.present? && source_repo.id != target_repo.id
+ source_branch_repo.id != target_branch_repo.id
end
def description
@@ -65,30 +78,6 @@ module Gitlab
end
end
- def source_project
- project
- end
-
- def source_repo
- raw_data.head.repo
- end
-
- def source_branch
- source_project.repository.find_branch(raw_data.head.ref)
- end
-
- def target_project
- project
- end
-
- def target_repo
- raw_data.base.repo
- end
-
- def target_branch
- target_project.repository.find_branch(raw_data.base.ref)
- end
-
def state
@state ||= case true
when raw_data.state == 'closed' && raw_data.merged_at.present?
diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb
index 49e5f86e6e6..8e345e8ae4a 100644
--- a/lib/gitlab/metrics/subscribers/rails_cache.rb
+++ b/lib/gitlab/metrics/subscribers/rails_cache.rb
@@ -6,26 +6,28 @@ module Gitlab
attach_to :active_support
def cache_read(event)
- increment(:cache_read_duration, event.duration)
+ increment(:cache_read, event.duration)
end
def cache_write(event)
- increment(:cache_write_duration, event.duration)
+ increment(:cache_write, event.duration)
end
def cache_delete(event)
- increment(:cache_delete_duration, event.duration)
+ increment(:cache_delete, event.duration)
end
def cache_exist?(event)
- increment(:cache_exists_duration, event.duration)
+ increment(:cache_exists, event.duration)
end
def increment(key, duration)
return unless current_transaction
current_transaction.increment(:cache_duration, duration)
- current_transaction.increment(key, duration)
+ current_transaction.increment(:cache_count, 1)
+ current_transaction.increment("#{key}_duration".to_sym, duration)
+ current_transaction.increment("#{key}_count".to_sym, 1)
end
private
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index 2bbbd3074e8..fe65c246101 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -62,7 +62,7 @@ module Gitlab
end
def wiki_page_url
- "#{Gitlab.config.gitlab.url}#{object.wiki.wiki_base_path}/#{object.slug}"
+ namespace_project_wiki_url(object.wiki.project.namespace, object.wiki.project, object.slug)
end
end
end
diff --git a/lib/json_web_token/rsa_token.rb b/lib/json_web_token/rsa_token.rb
new file mode 100644
index 00000000000..d6d6af7089c
--- /dev/null
+++ b/lib/json_web_token/rsa_token.rb
@@ -0,0 +1,42 @@
+module JSONWebToken
+ class RSAToken < Token
+ attr_reader :key_file
+
+ def initialize(key_file)
+ super()
+ @key_file = key_file
+ end
+
+ def encoded
+ headers = {
+ kid: kid
+ }
+ JWT.encode(payload, key, 'RS256', headers)
+ end
+
+ private
+
+ def key_data
+ @key_data ||= File.read(key_file)
+ end
+
+ def key
+ @key ||= OpenSSL::PKey::RSA.new(key_data)
+ end
+
+ def public_key
+ key.public_key
+ end
+
+ def kid
+ # calculate sha256 from DER encoded ASN1
+ kid = Digest::SHA256.digest(public_key.to_der)
+
+ # we encode only 30 bytes with base32
+ kid = Base32.encode(kid[0..29])
+
+ # insert colon every 4 characters
+ kid.scan(/.{4}/).join(':')
+ end
+ end
+end
diff --git a/lib/json_web_token/token.rb b/lib/json_web_token/token.rb
new file mode 100644
index 00000000000..5b67715b0b2
--- /dev/null
+++ b/lib/json_web_token/token.rb
@@ -0,0 +1,46 @@
+module JSONWebToken
+ class Token
+ attr_accessor :issuer, :subject, :audience, :id
+ attr_accessor :issued_at, :not_before, :expire_time
+
+ def initialize
+ @id = SecureRandom.uuid
+ @issued_at = Time.now
+ # we give a few seconds for time shift
+ @not_before = issued_at - 5.seconds
+ # default 60 seconds should be more than enough for this authentication token
+ @expire_time = issued_at + 1.minute
+ @custom_payload = {}
+ end
+
+ def [](key)
+ @custom_payload[key]
+ end
+
+ def []=(key, value)
+ @custom_payload[key] = value
+ end
+
+ def encoded
+ raise NotImplementedError
+ end
+
+ def payload
+ @custom_payload.merge(default_payload)
+ end
+
+ private
+
+ def default_payload
+ {
+ jti: id,
+ aud: audience,
+ sub: subject,
+ iss: issuer,
+ iat: issued_at.to_i,
+ nbf: not_before.to_i,
+ exp: expire_time.to_i
+ }.compact
+ end
+ end
+end
diff --git a/public/robots.txt b/public/robots.txt
index 4f616c7f4c1..334f4c03533 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -65,3 +65,4 @@ Disallow: /*/*/deploy_keys
Disallow: /*/*/hooks
Disallow: /*/*/services
Disallow: /*/*/protected_branches
+Disallow: /*/*/uploads/
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index ce2a62ae1fd..6caf37ddc2c 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -114,6 +114,82 @@ describe Admin::UsersController do
end
end
+ describe 'POST update' do
+ context 'when the password has changed' do
+ def update_password(user, password, password_confirmation = nil)
+ params = {
+ id: user.to_param,
+ user: {
+ password: password,
+ password_confirmation: password_confirmation || password
+ }
+ }
+
+ post :update, params
+ end
+
+ context 'when the new password is valid' do
+ it 'redirects to the user' do
+ update_password(user, 'AValidPassword1')
+
+ expect(response).to redirect_to(admin_user_path(user))
+ end
+
+ it 'updates the password' do
+ update_password(user, 'AValidPassword1')
+
+ expect { user.reload }.to change { user.encrypted_password }
+ end
+
+ it 'sets the new password to expire immediately' do
+ update_password(user, 'AValidPassword1')
+
+ expect { user.reload }.to change { user.password_expires_at }.to(a_value <= Time.now)
+ end
+ end
+
+ context 'when the new password is invalid' do
+ it 'shows the edit page again' do
+ update_password(user, 'invalid')
+
+ expect(response).to render_template(:edit)
+ end
+
+ it 'returns the error message' do
+ update_password(user, 'invalid')
+
+ expect(assigns[:user].errors).to contain_exactly(a_string_matching(/too short/))
+ end
+
+ it 'does not update the password' do
+ update_password(user, 'invalid')
+
+ expect { user.reload }.not_to change { user.encrypted_password }
+ end
+ end
+
+ context 'when the new password does not match the password confirmation' do
+ it 'shows the edit page again' do
+ update_password(user, 'AValidPassword1', 'AValidPassword2')
+
+ expect(response).to render_template(:edit)
+ end
+
+ it 'returns the error message' do
+ update_password(user, 'AValidPassword1', 'AValidPassword2')
+
+ expect(assigns[:user].errors).to contain_exactly(a_string_matching(/doesn't match/))
+ end
+
+ it 'does not update the password' do
+ update_password(user, 'AValidPassword1', 'AValidPassword2')
+
+ expect { user.reload }.not_to change { user.encrypted_password }
+ end
+ end
+ end
+ end
+
describe "POST impersonate" do
context "when the user is blocked" do
before do
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
new file mode 100644
index 00000000000..df70a589a89
--- /dev/null
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe RegistrationsController do
+ describe '#create' do
+ around(:each) do |example|
+ perform_enqueued_jobs do
+ example.run
+ end
+ end
+
+ let(:user_params) { { user: { name: "new_user", username: "new_username", email: "new@user.com", password: "Any_password" } } }
+
+ context 'when sending email confirmation' do
+ before { allow(current_application_settings).to receive(:send_user_confirmation_email).and_return(false) }
+
+ it 'logs user in directly' do
+ post(:create, user_params)
+ expect(ActionMailer::Base.deliveries.last).to be_nil
+ expect(subject.current_user).to_not be_nil
+ end
+ end
+
+ context 'when not sending email confirmation' do
+ before { allow(current_application_settings).to receive(:send_user_confirmation_email).and_return(true) }
+
+ it 'does not authenticate user and sends confirmation email' do
+ post(:create, user_params)
+ expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
+ expect(subject.current_user).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 840b13196a6..26719f2652c 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -9,10 +9,10 @@ FactoryGirl.define do
author
factory :note_on_commit, traits: [:on_commit]
- factory :note_on_commit_diff, traits: [:on_commit, :on_diff]
+ factory :note_on_commit_diff, traits: [:on_commit, :on_diff], class: LegacyDiffNote
factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
factory :note_on_merge_request, traits: [:on_merge_request]
- factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff]
+ factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff], class: LegacyDiffNote
factory :note_on_project_snippet, traits: [:on_project_snippet]
factory :system_note, traits: [:system]
factory :downvote_note, traits: [:award, :downvote]
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 4570e409128..6dee0cd8d47 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -210,6 +210,8 @@ describe "Admin::Users", feature: true do
before do
fill_in "user_name", with: "Big Bang"
fill_in "user_email", with: "bigbang@mail.com"
+ fill_in "user_password", with: "AValidPassword1"
+ fill_in "user_password_confirmation", with: "AValidPassword1"
check "user_admin"
click_button "Save changes"
end
@@ -223,6 +225,7 @@ describe "Admin::Users", feature: true do
@simple_user.reload
expect(@simple_user.name).to eq('Big Bang')
expect(@simple_user.is_admin?).to be_truthy
+ expect(@simple_user.password_expires_at).to be <= Time.now
end
end
end
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index cc7f78e7325..2c7e1c748ad 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -38,6 +38,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
expect(page).to have_content 'lfs'
expect(page).not_to have_content 'fix'
expect(page).not_to have_content 'markdown'
+ expect(count_merge_requests).to eq(1)
end
it 'filters on a specific assignee' do
@@ -46,6 +47,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
expect(page).not_to have_content 'lfs'
expect(page).to have_content 'fix'
expect(page).to have_content 'markdown'
+ expect(count_merge_requests).to eq(2)
end
it 'sorts by newest' do
@@ -53,6 +55,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
expect(first_merge_request).to include('lfs')
expect(last_merge_request).to include('fix')
+ expect(count_merge_requests).to eq(3)
end
it 'sorts by oldest' do
@@ -60,30 +63,35 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
expect(first_merge_request).to include('fix')
expect(last_merge_request).to include('lfs')
+ expect(count_merge_requests).to eq(3)
end
it 'sorts by last updated' do
visit_merge_requests(project, sort: sort_value_recently_updated)
expect(first_merge_request).to include('lfs')
+ expect(count_merge_requests).to eq(3)
end
it 'sorts by oldest updated' do
visit_merge_requests(project, sort: sort_value_oldest_updated)
expect(first_merge_request).to include('markdown')
+ expect(count_merge_requests).to eq(3)
end
it 'sorts by milestone due soon' do
visit_merge_requests(project, sort: sort_value_milestone_soon)
expect(first_merge_request).to include('fix')
+ expect(count_merge_requests).to eq(3)
end
it 'sorts by milestone due later' do
visit_merge_requests(project, sort: sort_value_milestone_later)
expect(first_merge_request).to include('markdown')
+ expect(count_merge_requests).to eq(3)
end
it 'filters on one label and sorts by due soon' do
@@ -94,6 +102,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
sort: sort_value_due_date_soon)
expect(first_merge_request).to include('fix')
+ expect(count_merge_requests).to eq(1)
end
context 'while filtering on two labels' do
@@ -110,6 +119,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
sort: sort_value_due_date_soon)
expect(first_merge_request).to include('fix')
+ expect(count_merge_requests).to eq(1)
end
context 'filter on assignee and' do
@@ -119,6 +129,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
sort: sort_value_due_date_soon)
expect(first_merge_request).to include('fix')
+ expect(count_merge_requests).to eq(1)
end
end
end
@@ -134,4 +145,8 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
def last_merge_request
page.all('ul.mr-list > li').last.text
end
+
+ def count_merge_requests
+ page.all('ul.mr-list > li').count
+ end
end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 389812ff7e1..9e9fec01943 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -192,7 +192,7 @@ describe 'Comments', feature: true do
end
it 'should be removed when canceled' do
- page.within(".diff-file form[id$='#{line_code}']") do
+ page.within(".diff-file form[id$='#{line_code}-true']") do
find('.js-close-discussion-note-form').trigger('click')
end
diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb
index 58aabd913eb..4229e82b443 100644
--- a/spec/features/signup_spec.rb
+++ b/spec/features/signup_spec.rb
@@ -2,20 +2,45 @@ require 'spec_helper'
feature 'Signup', feature: true do
describe 'signup with no errors' do
- it 'creates the user account and sends a confirmation email' do
- user = build(:user)
- visit root_path
+ context "when sending confirmation email" do
+ before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true) }
- 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"
+ it 'creates the user account and sends a confirmation email' do
+ user = build(:user)
+
+ visit root_path
+
+ 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
- expect(page).to have_content("Please check your email to confirm your account")
+ expect(current_path).to eq users_almost_there_path
+ expect(page).to have_content("Please check your email to confirm your account")
+ end
end
+
+ context "when not sending confirmation email" do
+ before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(false) }
+
+ it 'creates the user account and goes to dashboard' do
+ user = build(:user)
+
+ visit root_path
+
+ 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 dashboard_projects_path
+ expect(page).to have_content("Welcome! You have signed up successfully.")
+ end
+ end
+
end
describe 'signup with errors' do
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index bc607a29751..ec8809e6926 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
describe IssuesFinder do
- let(:user) { create :user }
- let(:user2) { create :user }
- let(:project1) { create(:project) }
- let(:project2) { create(:project) }
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:project1) { create(:empty_project) }
+ let(:project2) { create(:empty_project) }
let(:milestone) { create(:milestone, project: project1) }
let(:label) { create(:label, project: project2) }
let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone) }
@@ -16,101 +16,147 @@ describe IssuesFinder do
project1.team << [user, :master]
project2.team << [user, :developer]
project2.team << [user2, :developer]
+
+ issue1
+ issue2
+ issue3
end
- describe :execute do
- before :each do
- issue1
- issue2
- issue3
- end
+ describe '#execute' do
+ let(:search_user) { user }
+ let(:params) { {} }
+ let(:issues) { IssuesFinder.new(search_user, params.merge(scope: scope, state: 'opened')).execute }
context 'scope: all' do
- it 'should filter by all' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues.size).to eq(3)
+ let(:scope) { 'all' }
+
+ it 'returns all issues' do
+ expect(issues).to contain_exactly(issue1, issue2, issue3)
end
- it 'should filter by assignee id' do
- params = { scope: "all", assignee_id: user.id, state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues.size).to eq(2)
+ context 'filtering by assignee ID' do
+ let(:params) { { assignee_id: user.id } }
+
+ it 'returns issues assigned to that user' do
+ expect(issues).to contain_exactly(issue1, issue2)
+ end
end
- it 'should filter by author id' do
- params = { scope: "all", author_id: user2.id, state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues).to eq([issue3])
+ context 'filtering by author ID' do
+ let(:params) { { author_id: user2.id } }
+
+ it 'returns issues created by that user' do
+ expect(issues).to contain_exactly(issue3)
+ end
end
- it 'should filter by milestone id' do
- params = { scope: "all", milestone_title: milestone.title, state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues).to eq([issue1])
+ context 'filtering by milestone' do
+ let(:params) { { milestone_title: milestone.title } }
+
+ it 'returns issues assigned to that milestone' do
+ expect(issues).to contain_exactly(issue1)
+ end
end
- it 'should filter by no milestone id' do
- params = { scope: "all", milestone_title: Milestone::None.title, state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues).to match_array([issue2, issue3])
+ context 'filtering by no milestone' do
+ let(:params) { { milestone_title: Milestone::None.title } }
+
+ it 'returns issues with no milestone' do
+ expect(issues).to contain_exactly(issue2, issue3)
+ end
end
- it 'should filter by label name' do
- params = { scope: "all", label_name: label.title, state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues).to eq([issue2])
+ context 'filtering by upcoming milestone' do
+ let(:params) { { milestone_title: Milestone::Upcoming.name } }
+
+ let(:project_no_upcoming_milestones) { create(:empty_project, :public) }
+ let(:project_next_1_1) { create(:empty_project, :public) }
+ let(:project_next_8_8) { create(:empty_project, :public) }
+
+ let(:yesterday) { Date.today - 1.day }
+ let(:tomorrow) { Date.today + 1.day }
+ let(:two_days_from_now) { Date.today + 2.days }
+ let(:ten_days_from_now) { Date.today + 10.days }
+
+ let(:milestones) do
+ [
+ create(:milestone, :closed, project: project_no_upcoming_milestones),
+ create(:milestone, project: project_next_1_1, title: '1.1', due_date: two_days_from_now),
+ create(:milestone, project: project_next_1_1, title: '8.8', due_date: ten_days_from_now),
+ create(:milestone, project: project_next_8_8, title: '1.1', due_date: yesterday),
+ create(:milestone, project: project_next_8_8, title: '8.8', due_date: tomorrow)
+ ]
+ end
+
+ before do
+ milestones.each do |milestone|
+ create(:issue, project: milestone.project, milestone: milestone, author: user, assignee: user)
+ end
+ end
+
+ it 'returns issues in the upcoming milestone for each project' do
+ expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.1', '8.8')
+ expect(issues.map { |issue| issue.milestone.due_date }).to contain_exactly(tomorrow, two_days_from_now)
+ end
end
- it 'returns unique issues when filtering by multiple labels' do
- label2 = create(:label, project: project2)
+ context 'filtering by label' do
+ let(:params) { { label_name: label.title } }
- create(:label_link, label: label2, target: issue2)
+ it 'returns issues with that label' do
+ expect(issues).to contain_exactly(issue2)
+ end
+ end
- params = {
- scope: 'all',
- label_name: [label.title, label2.title].join(','),
- state: 'opened'
- }
+ context 'filtering by multiple labels' do
+ let(:params) { { label_name: [label.title, label2.title].join(',') } }
+ let(:label2) { create(:label, project: project2) }
- issues = IssuesFinder.new(user, params).execute
+ before { create(:label_link, label: label2, target: issue2) }
- expect(issues).to eq([issue2])
+ it 'returns the unique issues with any of those labels' do
+ expect(issues).to contain_exactly(issue2)
+ end
end
- it 'should filter by no label name' do
- params = { scope: "all", label_name: Label::None.title, state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues).to match_array([issue1, issue3])
+ context 'filtering by no label' do
+ let(:params) { { label_name: Label::None.title } }
+
+ it 'returns issues with no labels' do
+ expect(issues).to contain_exactly(issue1, issue3)
+ end
end
- it 'should be empty for unauthorized user' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new(nil, params).execute
- expect(issues.size).to be_zero
+ context 'when the user is unauthorized' do
+ let(:search_user) { nil }
+
+ it 'returns no results' do
+ expect(issues).to be_empty
+ end
end
- it 'should not include unauthorized issues' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new(user2, params).execute
- expect(issues.size).to eq(2)
- expect(issues).not_to include(issue1)
- expect(issues).to include(issue2)
- expect(issues).to include(issue3)
+ context 'when the user can see some, but not all, issues' do
+ let(:search_user) { user2 }
+
+ it 'returns only issues they can see' do
+ expect(issues).to contain_exactly(issue2, issue3)
+ end
end
end
context 'personal scope' do
- it 'should filter by assignee' do
- params = { scope: "assigned-to-me", state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues.size).to eq(2)
+ let(:scope) { 'assigned-to-me' }
+
+ it 'returns issue assigned to the user' do
+ expect(issues).to contain_exactly(issue1, issue2)
end
- it 'should filter by project' do
- params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id }
- issues = IssuesFinder.new(user, params).execute
- expect(issues.size).to eq(1)
+ context 'filtering by project' do
+ let(:params) { { project_id: project1.id } }
+
+ it 'returns issues assigned to the user in that project' do
+ expect(issues).to contain_exactly(issue1)
+ end
end
end
end
diff --git a/spec/lib/gitlab/github_import/branch_formatter_spec.rb b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
new file mode 100644
index 00000000000..3cb634ba010
--- /dev/null
+++ b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::BranchFormatter, lib: true do
+ let(:project) { create(:project) }
+ let(:repo) { double }
+ let(:raw) do
+ {
+ ref: 'feature',
+ repo: repo,
+ sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'
+ }
+ end
+
+ describe '#exists?' do
+ it 'returns true when branch exists' do
+ branch = described_class.new(project, double(raw))
+
+ expect(branch.exists?).to eq true
+ end
+
+ it 'returns false when branch does not exist' do
+ branch = described_class.new(project, double(raw.merge(ref: 'removed-branch')))
+
+ expect(branch.exists?).to eq false
+ end
+ end
+
+ describe '#name' do
+ it 'returns raw ref when branch exists' do
+ branch = described_class.new(project, double(raw))
+
+ expect(branch.name).to eq 'feature'
+ end
+
+ it 'returns formatted ref when branch does not exist' do
+ branch = described_class.new(project, double(raw.merge(ref: 'removed-branch')))
+
+ expect(branch.name).to eq 'removed-branch-2e5d3239'
+ end
+ end
+
+ describe '#repo' do
+ it 'returns raw repo' do
+ branch = described_class.new(project, double(raw))
+
+ expect(branch.repo).to eq repo
+ end
+ end
+
+ describe '#sha' do
+ it 'returns raw sha' do
+ branch = described_class.new(project, double(raw))
+
+ expect(branch.sha).to eq '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'
+ end
+ end
+
+ describe '#valid?' do
+ it 'returns true when repository exists' do
+ branch = described_class.new(project, double(raw))
+
+ expect(branch.valid?).to eq true
+ end
+
+ it 'returns false when repository does not exist' do
+ branch = described_class.new(project, double(raw.merge(repo: nil)))
+
+ expect(branch.valid?).to eq false
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index e59c0ca110e..120f59e6e71 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -4,9 +4,9 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:project) { create(:project) }
let(:repository) { double(id: 1, fork: false) }
let(:source_repo) { repository }
- let(:source_branch) { double(ref: 'feature', repo: source_repo) }
+ let(:source_branch) { double(ref: 'feature', repo: source_repo, sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b') }
let(:target_repo) { repository }
- let(:target_branch) { double(ref: 'master', repo: target_repo) }
+ let(:target_branch) { double(ref: 'master', repo: target_repo, sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7') }
let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
@@ -41,8 +41,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project,
source_branch: 'feature',
+ head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b',
target_project: project,
target_branch: 'master',
+ base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7',
state: 'opened',
milestone: nil,
author_id: project.creator_id,
@@ -66,8 +68,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project,
source_branch: 'feature',
+ head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b',
target_project: project,
target_branch: 'master',
+ base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7',
state: 'closed',
milestone: nil,
author_id: project.creator_id,
@@ -91,8 +95,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project,
source_branch: 'feature',
+ head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b',
target_project: project,
target_branch: 'master',
+ base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7',
state: 'merged',
milestone: nil,
author_id: project.creator_id,
@@ -137,11 +143,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:milestone) { double(number: 45) }
let(:raw_data) { double(base_data.merge(milestone: milestone)) }
- it 'returns nil when milestone does not exists' do
+ it 'returns nil when milestone does not exist' do
expect(pull_request.attributes.fetch(:milestone)).to be_nil
end
- it 'returns milestone when is exists' do
+ it 'returns milestone when it exists' do
milestone = create(:milestone, project: project, iid: 45)
expect(pull_request.attributes.fetch(:milestone)).to eq milestone
@@ -158,36 +164,16 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
describe '#valid?' do
- let(:invalid_branch) { double(ref: 'invalid-branch').as_null_object }
-
- context 'when source, and target repositories are the same' do
- context 'and source and target branches exists' do
- let(:raw_data) { double(base_data.merge(head: source_branch, base: target_branch)) }
-
- it 'returns true' do
- expect(pull_request.valid?).to eq true
- end
- end
-
- context 'and source branch doesn not exists' do
- let(:raw_data) { double(base_data.merge(head: invalid_branch, base: target_branch)) }
-
- it 'returns false' do
- expect(pull_request.valid?).to eq false
- end
- end
-
- context 'and target branch doesn not exists' do
- let(:raw_data) { double(base_data.merge(head: source_branch, base: invalid_branch)) }
+ context 'when source, and target repos are not a fork' do
+ let(:raw_data) { double(base_data) }
- it 'returns false' do
- expect(pull_request.valid?).to eq false
- end
+ it 'returns true' do
+ expect(pull_request.valid?).to eq true
end
end
context 'when source repo is a fork' do
- let(:source_repo) { double(id: 2, fork: true) }
+ let(:source_repo) { double(id: 2) }
let(:raw_data) { double(base_data) }
it 'returns false' do
@@ -196,7 +182,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
context 'when target repo is a fork' do
- let(:target_repo) { double(id: 2, fork: true) }
+ let(:target_repo) { double(id: 2) }
let(:raw_data) { double(base_data) }
it 'returns false' do
diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
index e01b0b4bd21..d824dc54438 100644
--- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
describe '#cache_read' do
it 'increments the cache_read duration' do
expect(subscriber).to receive(:increment).
- with(:cache_read_duration, event.duration)
+ with(:cache_read, event.duration)
subscriber.cache_read(event)
end
@@ -18,7 +18,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
describe '#cache_write' do
it 'increments the cache_write duration' do
expect(subscriber).to receive(:increment).
- with(:cache_write_duration, event.duration)
+ with(:cache_write, event.duration)
subscriber.cache_write(event)
end
@@ -27,7 +27,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
describe '#cache_delete' do
it 'increments the cache_delete duration' do
expect(subscriber).to receive(:increment).
- with(:cache_delete_duration, event.duration)
+ with(:cache_delete, event.duration)
subscriber.cache_delete(event)
end
@@ -36,7 +36,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
describe '#cache_exist?' do
it 'increments the cache_exists duration' do
expect(subscriber).to receive(:increment).
- with(:cache_exists_duration, event.duration)
+ with(:cache_exists, event.duration)
subscriber.cache_exist?(event)
end
@@ -62,9 +62,15 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
with(:cache_duration, event.duration)
expect(transaction).to receive(:increment).
+ with(:cache_count, 1)
+
+ expect(transaction).to receive(:increment).
with(:cache_delete_duration, event.duration)
- subscriber.increment(:cache_delete_duration, event.duration)
+ expect(transaction).to receive(:increment).
+ with(:cache_delete_count, 1)
+
+ subscriber.increment(:cache_delete, event.duration)
end
end
end
diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb
new file mode 100644
index 00000000000..0c3d3ea7019
--- /dev/null
+++ b/spec/lib/json_web_token/rsa_token_spec.rb
@@ -0,0 +1,43 @@
+describe JSONWebToken::RSAToken do
+ let(:rsa_key) do
+ OpenSSL::PKey::RSA.new <<-eos.strip_heredoc
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIBOgIBAAJBAMA5sXIBE0HwgIB40iNidN4PGWzOyLQK0bsdOBNgpEXkDlZBvnak
+ OUgAPF+rME4PB0Yl415DabUI40T5UNmlwxcCAwEAAQJAZtY2pSwIFm3JAXIh0cZZ
+ iXcAfiJ+YzuqinUOS+eW2sBCAEzjcARlU/o6sFQgtsOi4FOMczAd1Yx8UDMXMmrw
+ 2QIhAPBgVhJiTF09pdmeFWutCvTJDlFFAQNbrbo2X2x/9WF9AiEAzLgqMKeStSRu
+ H9N16TuDrUoO8R+DPqriCwkKrSHaWyMCIFzMhE4inuKcSywBaLmiG4m3GQzs++Al
+ A6PRG/PSTpQtAiBxtBg6zdf+JC3GH3zt/dA0/10tL4OF2wORfYQghRzyYQIhAL2l
+ 0ZQW+yLIZAGrdBFWYEAa52GZosncmzBNlsoTgwE4
+ -----END RSA PRIVATE KEY-----
+ eos
+ end
+ let(:rsa_token) { described_class.new(nil) }
+ let(:rsa_encoded) { rsa_token.encoded }
+
+ before { allow_any_instance_of(described_class).to receive(:key).and_return(rsa_key) }
+
+ context 'token' do
+ context 'for valid key to be validated' do
+ before { rsa_token['key'] = 'value' }
+
+ subject { JWT.decode(rsa_encoded, rsa_key) }
+
+ it { expect{subject}.to_not raise_error }
+ it { expect(subject.first).to include('key' => 'value') }
+ it do
+ expect(subject.second).to eq(
+ "typ" => "JWT",
+ "alg" => "RS256",
+ "kid" => "OGXY:4TR7:FAVO:WEM2:XXEW:E4FP:TKL7:7ACK:TZAF:D54P:SUIA:P3B2")
+ end
+ end
+
+ context 'for invalid key to raise an exception' do
+ let(:new_key) { OpenSSL::PKey::RSA.generate(512) }
+ subject { JWT.decode(rsa_encoded, new_key) }
+
+ it { expect{subject}.to raise_error(JWT::DecodeError) }
+ end
+ end
+end
diff --git a/spec/lib/json_web_token/token_spec.rb b/spec/lib/json_web_token/token_spec.rb
new file mode 100644
index 00000000000..3d955e4d774
--- /dev/null
+++ b/spec/lib/json_web_token/token_spec.rb
@@ -0,0 +1,18 @@
+describe JSONWebToken::Token do
+ let(:token) { described_class.new }
+
+ context 'custom parameters' do
+ let(:value) { 'value' }
+ before { token[:key] = value }
+
+ it { expect(token[:key]).to eq(value) }
+ it { expect(token.payload).to include(key: value) }
+ end
+
+ context 'embeds default payload' do
+ subject { token.payload }
+ let(:default) { token.send(:default_payload) }
+
+ it { is_expected.to include(default) }
+ end
+end
diff --git a/spec/models/legacy_diff_note_spec.rb b/spec/models/legacy_diff_note_spec.rb
new file mode 100644
index 00000000000..7c29bef54e4
--- /dev/null
+++ b/spec/models/legacy_diff_note_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+
+describe LegacyDiffNote, models: true do
+ describe "Commit diff line notes" do
+ let!(:note) { create(:note_on_commit_diff, note: "+1 from me") }
+ let!(:commit) { note.noteable }
+
+ it "should save a valid note" do
+ expect(note.commit_id).to eq(commit.id)
+ expect(note.noteable.id).to eq(commit.id)
+ end
+
+ it "should be recognized by #legacy_diff_note?" do
+ expect(note).to be_legacy_diff_note
+ end
+ end
+
+ describe '#active?' do
+ it 'is always true when the note has no associated diff' do
+ note = build(:note_on_merge_request_diff)
+
+ expect(note).to receive(:diff).and_return(nil)
+
+ expect(note).to be_active
+ end
+
+ it 'is never true when the note has no noteable associated' do
+ note = build(:note_on_merge_request_diff)
+
+ expect(note).to receive(:diff).and_return(double)
+ expect(note).to receive(:noteable).and_return(nil)
+
+ expect(note).not_to be_active
+ end
+
+ it 'returns the memoized value if defined' do
+ note = build(:note_on_merge_request_diff)
+
+ note.instance_variable_set(:@active, 'foo')
+ expect(note).not_to receive(:find_noteable_diff)
+
+ expect(note.active?).to eq 'foo'
+ end
+
+ context 'for a merge request noteable' do
+ it 'is false when noteable has no matching diff' do
+ merge = build_stubbed(:merge_request, :simple)
+ note = build(:note_on_merge_request_diff, noteable: merge)
+
+ allow(note).to receive(:diff).and_return(double)
+ expect(note).to receive(:find_noteable_diff).and_return(nil)
+
+ expect(note).not_to be_active
+ end
+
+ it 'is true when noteable has a matching diff' do
+ merge = create(:merge_request, :simple)
+
+ # Generate a real line_code value so we know it will match. We use a
+ # random line from a random diff just for funsies.
+ diff = merge.diffs.to_a.sample
+ line = Gitlab::Diff::Parser.new.parse(diff.diff.each_line).to_a.sample
+ code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
+
+ # We're persisting in order to trigger the set_diff callback
+ note = create(:note_on_merge_request_diff, noteable: merge, line_code: code)
+
+ # Make sure we don't get a false positive from a guard clause
+ expect(note).to receive(:find_noteable_diff).and_call_original
+ expect(note).to be_active
+ end
+ end
+ end
+end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index c8578749b21..9eef08c6d00 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -64,7 +64,13 @@ describe MergeRequest, models: true do
describe '#target_sha' do
context 'when the target branch does not exist anymore' do
- subject { create(:merge_request).tap { |mr| mr.update_attribute(:target_branch, 'deleted') } }
+ let(:project) { create(:project) }
+
+ subject { create(:merge_request, source_project: project, target_project: project) }
+
+ before do
+ project.repository.raw_repository.delete_branch(subject.target_branch)
+ end
it 'returns nil' do
expect(subject.target_sha).to be_nil
@@ -289,7 +295,12 @@ describe MergeRequest, models: true do
let(:fork_project) { create(:project, forked_from_project: project) }
context 'when the target branch does not exist anymore' do
- subject { create(:merge_request).tap { |mr| mr.update_attribute(:target_branch, 'deleted') } }
+ subject { create(:merge_request, source_project: project, target_project: project) }
+
+ before do
+ project.repository.raw_repository.delete_branch(subject.target_branch)
+ subject.reload
+ end
it 'does not crash' do
expect{ subject.diverged_commits_count }.not_to raise_error
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 247a9fa9910..1e18c788b50 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -204,4 +204,37 @@ describe Milestone, models: true do
to eq([milestone])
end
end
+
+ describe '.upcoming_ids_by_projects' do
+ let(:project_1) { create(:empty_project) }
+ let(:project_2) { create(:empty_project) }
+ let(:project_3) { create(:empty_project) }
+ let(:projects) { [project_1, project_2, project_3] }
+
+ let!(:past_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.now - 1.day) }
+ let!(:current_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.now + 1.day) }
+ let!(:future_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.now + 2.days) }
+
+ let!(:past_milestone_project_2) { create(:milestone, project: project_2, due_date: Time.now - 1.day) }
+ let!(:closed_milestone_project_2) { create(:milestone, :closed, project: project_2, due_date: Time.now + 1.day) }
+ let!(:current_milestone_project_2) { create(:milestone, project: project_2, due_date: Time.now + 2.days) }
+
+ let!(:past_milestone_project_3) { create(:milestone, project: project_3, due_date: Time.now - 1.day) }
+
+ # The call to `#try` is because this returns a relation with a Postgres DB,
+ # and an array of IDs with a MySQL DB.
+ let(:milestone_ids) { Milestone.upcoming_ids_by_projects(projects).map { |id| id.try(:id) || id } }
+
+ it 'returns the next upcoming open milestone ID for each project' do
+ expect(milestone_ids).to contain_exactly(current_milestone_project_1.id, current_milestone_project_2.id)
+ end
+
+ context 'when the projects have no open upcoming milestones' do
+ let(:projects) { [project_3] }
+
+ it 'returns no results' do
+ expect(milestone_ids).to be_empty
+ end
+ end
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 4b788b57882..5d916f0e6a6 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -34,24 +34,6 @@ describe Note, models: true do
end
end
- describe "Commit diff line notes" do
- let!(:note) { create(:note_on_commit_diff, note: "+1 from me") }
- let!(:commit) { note.noteable }
-
- it "should save a valid note" do
- expect(note.commit_id).to eq(commit.id)
- expect(note.noteable.id).to eq(commit.id)
- end
-
- it "should be recognized by #for_diff_line?" do
- expect(note).to be_for_diff_line
- end
-
- it "should be recognized by #for_commit_diff_line?" do
- expect(note).to be_for_commit_diff_line
- end
- end
-
describe 'authorization' do
before do
@p1 = create(:project)
@@ -148,66 +130,6 @@ describe Note, models: true do
end
end
- describe '#active?' do
- it 'is always true when the note has no associated diff' do
- note = build(:note)
-
- expect(note).to receive(:diff).and_return(nil)
-
- expect(note).to be_active
- end
-
- it 'is never true when the note has no noteable associated' do
- note = build(:note)
-
- expect(note).to receive(:diff).and_return(double)
- expect(note).to receive(:noteable).and_return(nil)
-
- expect(note).not_to be_active
- end
-
- it 'returns the memoized value if defined' do
- note = build(:note)
-
- expect(note).to receive(:diff).and_return(double)
- expect(note).to receive(:noteable).and_return(double)
-
- note.instance_variable_set(:@active, 'foo')
- expect(note).not_to receive(:find_noteable_diff)
-
- expect(note.active?).to eq 'foo'
- end
-
- context 'for a merge request noteable' do
- it 'is false when noteable has no matching diff' do
- merge = build_stubbed(:merge_request, :simple)
- note = build(:note, noteable: merge)
-
- allow(note).to receive(:diff).and_return(double)
- expect(note).to receive(:find_noteable_diff).and_return(nil)
-
- expect(note).not_to be_active
- end
-
- it 'is true when noteable has a matching diff' do
- merge = create(:merge_request, :simple)
-
- # Generate a real line_code value so we know it will match. We use a
- # random line from a random diff just for funsies.
- diff = merge.diffs.to_a.sample
- line = Gitlab::Diff::Parser.new.parse(diff.diff.each_line).to_a.sample
- code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
-
- # We're persisting in order to trigger the set_diff callback
- note = create(:note, noteable: merge, line_code: code)
-
- # Make sure we don't get a false positive from a guard clause
- expect(note).to receive(:find_noteable_diff).and_call_original
- expect(note).to be_active
- end
- end
- end
-
describe "editable?" do
it "returns true" do
note = build(:note)
@@ -258,7 +180,7 @@ describe Note, models: true do
end
it "is not an award emoji when comment is on a diff" do
- note = create(:note, note: ":blowfish:", noteable: merge_request, line_code: "11d5d2e667e9da4f7f610f81d86c974b146b13bd_0_2")
+ note = create(:note_on_merge_request_diff, note: ":blowfish:", noteable: merge_request, line_code: "11d5d2e667e9da4f7f610f81d86c974b146b13bd_0_2")
note = note.reload
expect(note.note).to eq(":blowfish:")
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 532e3f013fd..91ebb612baa 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -38,7 +38,8 @@ describe ProjectWiki, models: true do
describe "#wiki_base_path" do
it "returns the wiki base path" do
- wiki_base_path = "/#{project.path_with_namespace}/wikis"
+ wiki_base_path = "#{Gitlab.config.gitlab.relative_url_root}/#{project.path_with_namespace}/wikis"
+
expect(subject.wiki_base_path).to eq(wiki_base_path)
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 10e7e693571..9581990666b 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -141,6 +141,7 @@ describe User, models: true do
end
describe '#confirm' do
+ before { allow(current_application_settings).to receive(:send_user_confirmation_email).and_return(true) }
let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: 'test@gitlab.com') }
it 'returns unconfirmed' do
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
new file mode 100644
index 00000000000..7bb71365a48
--- /dev/null
+++ b/spec/requests/jwt_controller_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe JwtController do
+ let(:service) { double(execute: {}) }
+ let(:service_class) { double(new: service) }
+ let(:service_name) { 'test' }
+ let(:parameters) { { service: service_name } }
+
+ before { stub_const('JwtController::SERVICES', service_name => service_class) }
+
+ context 'existing service' do
+ subject! { get '/jwt/auth', parameters }
+
+ it { expect(response.status).to eq(200) }
+
+ context 'returning custom http code' do
+ let(:service) { double(execute: { http_status: 505 }) }
+
+ it { expect(response.status).to eq(505) }
+ end
+ end
+
+ context 'when using authorized request' do
+ context 'using CI token' do
+ let(:project) { create(:empty_project, runners_token: 'token', builds_enabled: builds_enabled) }
+ let(:headers) { { authorization: credentials('gitlab_ci_token', project.runners_token) } }
+
+ subject! { get '/jwt/auth', parameters, headers }
+
+ context 'project with enabled CI' do
+ let(:builds_enabled) { true }
+
+ it { expect(service_class).to have_received(:new).with(project, nil, parameters) }
+ end
+
+ context 'project with disabled CI' do
+ let(:builds_enabled) { false }
+
+ it { expect(response.status).to eq(403) }
+ end
+ end
+
+ context 'using User login' do
+ let(:user) { create(:user) }
+ let(:headers) { { authorization: credentials('user', 'password') } }
+
+ before { expect_any_instance_of(Gitlab::Auth).to receive(:find).with('user', 'password').and_return(user) }
+
+ subject! { get '/jwt/auth', parameters, headers }
+
+ it { expect(service_class).to have_received(:new).with(nil, user, parameters) }
+ end
+
+ context 'using invalid login' do
+ let(:headers) { { authorization: credentials('invalid', 'password') } }
+
+ subject! { get '/jwt/auth', parameters, headers }
+
+ it { expect(response.status).to eq(403) }
+ end
+ end
+
+ context 'unknown service' do
+ subject! { get '/jwt/auth', service: 'unknown' }
+
+ it { expect(response.status).to eq(404) }
+ end
+
+ def credentials(login, password)
+ ActionController::HttpAuthentication::Basic.encode_credentials(login, password)
+ end
+end
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
new file mode 100644
index 00000000000..3ea252ed44f
--- /dev/null
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -0,0 +1,216 @@
+require 'spec_helper'
+
+describe Auth::ContainerRegistryAuthenticationService, services: true do
+ let(:current_project) { nil }
+ let(:current_user) { nil }
+ let(:current_params) { {} }
+ let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) }
+ let(:registry_settings) do
+ {
+ enabled: true,
+ issuer: 'rspec',
+ key: nil
+ }
+ end
+ let(:payload) { JWT.decode(subject[:token], rsa_key).first }
+
+ subject { described_class.new(current_project, current_user, current_params).execute }
+
+ before do
+ allow(Gitlab.config.registry).to receive_messages(registry_settings)
+ allow_any_instance_of(JSONWebToken::RSAToken).to receive(:key).and_return(rsa_key)
+ end
+
+ shared_examples 'an authenticated' do
+ it { is_expected.to include(:token) }
+ it { expect(payload).to include('access') }
+ end
+
+ shared_examples 'a accessible' do
+ let(:access) do
+ [{
+ 'type' => 'repository',
+ 'name' => project.path_with_namespace,
+ 'actions' => actions,
+ }]
+ end
+
+ it_behaves_like 'an authenticated'
+ it { expect(payload).to include('access' => access) }
+ end
+
+ shared_examples 'a pullable' do
+ it_behaves_like 'a accessible' do
+ let(:actions) { ['pull'] }
+ end
+ end
+
+ shared_examples 'a pushable' do
+ it_behaves_like 'a accessible' do
+ let(:actions) { ['push'] }
+ end
+ end
+
+ shared_examples 'a pullable and pushable' do
+ it_behaves_like 'a accessible' do
+ let(:actions) { ['pull', 'push'] }
+ end
+ end
+
+ shared_examples 'a forbidden' do
+ it { is_expected.to include(http_status: 403) }
+ it { is_expected.to_not include(:token) }
+ end
+
+ context 'user authorization' do
+ let(:project) { create(:project) }
+ let(:current_user) { create(:user) }
+
+ context 'allow to use offline_token' do
+ let(:current_params) do
+ { offline_token: true }
+ end
+
+ it_behaves_like 'an authenticated'
+ end
+
+ context 'allow developer to push images' do
+ before { project.team << [current_user, :developer] }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:push" }
+ end
+
+ it_behaves_like 'a pushable'
+ end
+
+ context 'allow reporter to pull images' do
+ before { project.team << [current_user, :reporter] }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull" }
+ end
+
+ it_behaves_like 'a pullable'
+ end
+
+ context 'return a least of privileges' do
+ before { project.team << [current_user, :reporter] }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:push,pull" }
+ end
+
+ it_behaves_like 'a pullable'
+ end
+
+ context 'disallow guest to pull or push images' do
+ before { project.team << [current_user, :guest] }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull,push" }
+ end
+
+ it_behaves_like 'a forbidden'
+ end
+ end
+
+ context 'project authorization' do
+ let(:current_project) { create(:empty_project) }
+
+ context 'disallow to use offline_token' do
+ let(:current_params) do
+ { offline_token: true }
+ end
+
+ it_behaves_like 'a forbidden'
+ end
+
+ context 'allow to pull and push images' do
+ let(:current_params) do
+ { scope: "repository:#{current_project.path_with_namespace}:pull,push" }
+ end
+
+ it_behaves_like 'a pullable and pushable' do
+ let(:project) { current_project }
+ end
+ end
+
+ context 'for other projects' do
+ context 'when pulling' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull" }
+ end
+
+ context 'allow for public' do
+ let(:project) { create(:empty_project, :public) }
+ it_behaves_like 'a pullable'
+ end
+
+ context 'disallow for private' do
+ let(:project) { create(:empty_project, :private) }
+ it_behaves_like 'a forbidden'
+ end
+ end
+
+ context 'when pushing' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:push" }
+ end
+
+ context 'disallow for all' do
+ let(:project) { create(:empty_project, :public) }
+ it_behaves_like 'a forbidden'
+ end
+ end
+ end
+ end
+
+ context 'unauthorized' do
+ context 'disallow to use offline_token' do
+ let(:current_params) do
+ { offline_token: true }
+ end
+
+ it_behaves_like 'a forbidden'
+ end
+
+ context 'for invalid scope' do
+ let(:current_params) do
+ { scope: 'invalid:aa:bb' }
+ end
+
+ it_behaves_like 'a forbidden'
+ end
+
+ context 'for private project' do
+ let(:project) { create(:empty_project, :private) }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull" }
+ end
+
+ it_behaves_like 'a forbidden'
+ end
+
+ context 'for public project' do
+ let(:project) { create(:empty_project, :public) }
+
+ context 'when pulling and pushing' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull,push" }
+ end
+
+ it_behaves_like 'a pullable'
+ end
+
+ context 'when pushing' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:push" }
+ end
+
+ it_behaves_like 'a forbidden'
+ end
+ end
+ end
+end