From 70623cd423b0c7e26e56422bf25c413d9921ee88 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 2 Mar 2016 12:18:43 +0100 Subject: fix token issue - timing attack --- app/models/project.rb | 4 ++-- app/models/project_services/ci_service.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 6f5d592755a..6c9377448e2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -889,13 +889,13 @@ class Project < ActiveRecord::Base end def valid_runners_token? token - self.runners_token && self.runners_token == token + self.runners_token && ActiveSupport::SecurityUtils.secure_compare(token, self.runners_token) end # TODO (ayufan): For now we use runners_token (backward compatibility) # In 8.4 every build will have its own individual token valid for time of build def valid_build_token? token - self.builds_enabled? && self.runners_token && self.runners_token == token + self.builds_enabled? && self.runners_token && ActiveSupport::SecurityUtils.secure_compare(token, self.runners_token) end def build_coverage_enabled? diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index e10b5529b42..f328deda354 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -26,7 +26,7 @@ class CiService < Service default_value_for :category, 'ci' def valid_token?(token) - self.respond_to?(:token) && self.token.present? && self.token == token + self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.secure_compare(token, self.token) end def supported_events -- cgit v1.2.1 From 3447ffbe9a11375a0cee40e2d5fa47e632361a3b Mon Sep 17 00:00:00 2001 From: nico de ceulaer Date: Thu, 3 Mar 2016 14:50:21 +0100 Subject: ensure also alternative email addresses are used when looking up user --- app/helpers/application_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f0aa2b57121..40a8b512e4c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -72,7 +72,7 @@ module ApplicationHelper if user_or_email.is_a?(User) user = user_or_email else - user = User.find_by(email: user_or_email.downcase) + user = User.find_by_any_email(user_or_email.downcase) end if user -- cgit v1.2.1 From 077d3791952a7120d4f28ca36c600e2185e6c1c5 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 8 Mar 2016 11:18:07 +0100 Subject: Updated gitlab_git to 9.0.1 This includes gitlab-org/gitlab_git!69 and will hopefully solve gitlab-org/gitlab-ce#13808. --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 22c86e4ae8f..00eeffecb08 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -358,7 +358,7 @@ GEM posix-spawn (~> 0.3) gitlab_emoji (0.3.1) gemojione (~> 2.2, >= 2.2.1) - gitlab_git (9.0.0) + gitlab_git (9.0.1) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) -- cgit v1.2.1 From fc610c182e73cdff2534bef91ce0385b06befacf Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 8 Mar 2016 15:57:45 +0100 Subject: add SHA256 to secure_compare --- app/models/project.rb | 4 ++-- app/models/project_services/ci_service.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index c0f2ab91fa4..3451779e18d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -908,13 +908,13 @@ class Project < ActiveRecord::Base end def valid_runners_token? token - self.runners_token && ActiveSupport::SecurityUtils.secure_compare(token, self.runners_token) + self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) end # TODO (ayufan): For now we use runners_token (backward compatibility) # In 8.4 every build will have its own individual token valid for time of build def valid_build_token? token - self.builds_enabled? && self.runners_token && ActiveSupport::SecurityUtils.secure_compare(token, self.runners_token) + self.builds_enabled? && self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) end def build_coverage_enabled? diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index f328deda354..d9f0849d147 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -26,7 +26,7 @@ class CiService < Service default_value_for :category, 'ci' def valid_token?(token) - self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.secure_compare(token, self.token) + self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) end def supported_events -- cgit v1.2.1 From 5844a21a0acae08a19fa82984dcc0feb1b8777c5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 15 Feb 2016 21:17:20 -0500 Subject: Use a custom Devise failure app to handle unauthenticated .zip requests Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/12944 --- config/initializers/devise.rb | 10 +++--- lib/gitlab/devise_failure.rb | 23 ++++++++++++++ .../projects/repositories_controller_spec.rb | 37 ++++++++++++++-------- 3 files changed, 52 insertions(+), 18 deletions(-) create mode 100644 lib/gitlab/devise_failure.rb diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index d82cfb3ec0c..31dceaebcad 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -203,11 +203,11 @@ Devise.setup do |config| # If you want to use other strategies, that are not supported by Devise, or # change the failure app, you can configure them inside the config.warden block. # - # config.warden do |manager| - # manager.failure_app = AnotherApp - # manager.intercept_401 = false - # manager.default_strategies(scope: :user).unshift :some_external_strategy - # end + config.warden do |manager| + manager.failure_app = Gitlab::DeviseFailure + # manager.intercept_401 = false + # manager.default_strategies(scope: :user).unshift :some_external_strategy + end if Gitlab::LDAP::Config.enabled? Gitlab.config.ldap.servers.values.each do |server| diff --git a/lib/gitlab/devise_failure.rb b/lib/gitlab/devise_failure.rb new file mode 100644 index 00000000000..a78fde9d782 --- /dev/null +++ b/lib/gitlab/devise_failure.rb @@ -0,0 +1,23 @@ +module Gitlab + class DeviseFailure < Devise::FailureApp + protected + + # Override `Devise::FailureApp#request_format` to handle a special case + # + # This tells Devise to handle an unauthenticated `.zip` request as an HTML + # request (i.e., redirect to sign in). + # + # Otherwise, Devise would respond with a 401 Unauthorized with + # `Content-Type: application/zip` and a response body in plaintext, and the + # browser would freak out. + # + # See https://gitlab.com/gitlab-org/gitlab-ce/issues/12944 + def request_format + if request.format == :zip + Mime::Type.lookup_by_extension(:html).ref + else + super + end + end + end +end diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index 09ec4f18f9d..0ddbec9eac2 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -2,30 +2,41 @@ require "spec_helper" describe Projects::RepositoriesController do let(:project) { create(:project) } - let(:user) { create(:user) } describe "GET archive" do - before do - sign_in(user) - project.team << [user, :developer] - end - - it "uses Gitlab::Workhorse" do - expect(Gitlab::Workhorse).to receive(:send_git_archive).with(project, "master", "zip") + context 'as a guest' do + it 'responds with redirect in correct format' do + get :archive, namespace_id: project.namespace.path, project_id: project.path, format: "zip" - get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" + expect(response.content_type).to start_with 'text/html' + expect(response).to be_redirect + end end - context "when the service raises an error" do + context 'as a user' do + let(:user) { create(:user) } before do - allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed") + project.team << [user, :developer] + sign_in(user) end + it "uses Gitlab::Workhorse" do + expect(Gitlab::Workhorse).to receive(:send_git_archive).with(project, "master", "zip") - it "renders Not Found" do get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" + end + + context "when the service raises an error" do + + before do + allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed") + end + + it "renders Not Found" do + get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" - expect(response.status).to eq(404) + expect(response.status).to eq(404) + end end end end -- cgit v1.2.1 From 639cad7e3034c895f7f38653b27f1e37e6ee34c2 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Wed, 9 Mar 2016 20:43:54 +0100 Subject: Ignore .byebug_history --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1eb785451f4..8f861d76a37 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ .sass-cache/ .secret .vagrant +.byebug_history Vagrantfile backups/* config/aws.yml -- cgit v1.2.1 From befa7a9c170327ecc4ce698f416450dc3542942c Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 9 Mar 2016 13:25:39 -0500 Subject: Don't remove `ProjectSnippet#expires_at` from API See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3103 This partially reverts commit 836d5930332797192094ce4a3c8083e96f7e8c53. --- doc/api/notes.md | 1 + doc/api/project_snippets.md | 1 + doc/web_hooks/web_hooks.md | 1 + lib/api/entities.rb | 3 +++ spec/requests/api/project_snippets_spec.rb | 18 ++++++++++++++++++ 5 files changed, 24 insertions(+) create mode 100644 spec/requests/api/project_snippets_spec.rb diff --git a/doc/api/notes.md b/doc/api/notes.md index 85d4f0bafa2..d4d63e825ab 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -145,6 +145,7 @@ Parameters: "state": "active", "created_at": "2013-09-30T13:46:01Z" }, + "expires_at": null, "updated_at": "2013-10-02T07:34:20Z", "created_at": "2013-10-02T07:34:20Z" } diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md index fb802102e3a..a7acf37b5bc 100644 --- a/doc/api/project_snippets.md +++ b/doc/api/project_snippets.md @@ -51,6 +51,7 @@ Parameters: "state": "active", "created_at": "2012-05-23T08:00:58Z" }, + "expires_at": null, "updated_at": "2012-06-28T10:52:04Z", "created_at": "2012-06-28T10:52:04Z" } diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index e2b53c45ab1..b82306bd1da 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -582,6 +582,7 @@ X-Gitlab-Event: Note Hook "created_at": "2015-04-09 02:40:38 UTC", "updated_at": "2015-04-09 02:40:38 UTC", "file_name": "test.rb", + "expires_at": null, "type": "ProjectSnippet", "visibility_level": 0 } diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 5b5b8bd044b..b49af093a14 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -144,6 +144,9 @@ module API expose :id, :title, :file_name expose :author, using: Entities::UserBasic expose :updated_at, :created_at + + # TODO (rspeicher): Deprecated; remove in 9.0 + expose(:expires_at) { |snippet| nil } end class ProjectEntity < Grape::Entity diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb new file mode 100644 index 00000000000..3722ddf5a33 --- /dev/null +++ b/spec/requests/api/project_snippets_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +describe API::API, api: true do + include ApiHelpers + + describe 'GET /projects/:project_id/snippets/:id' do + # TODO (rspeicher): Deprecated; remove in 9.0 + it 'always exposes expires_at as nil' do + admin = create(:admin) + snippet = create(:project_snippet, author: admin) + + get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}", admin) + + expect(json_response).to have_key('expires_at') + expect(json_response['expires_at']).to be_nil + end + end +end -- cgit v1.2.1 From d69dff5b4b1c2dd4944269703ff7af32869adf13 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 9 Mar 2016 11:39:48 +0100 Subject: Removed benchmark suite and its documentation The rationale for this can be found in https://gitlab.com/gitlab-org/gitlab-ce/issues/13718 but in short the benchmark suite no longer serves a good purpose now that we have proper production monitoring in place. Fixes gitlab-org/gitlab-ce#13718 --- .gitlab-ci.yml | 27 +------- doc/development/README.md | 1 - doc/development/benchmarking.md | 69 ------------------- lib/tasks/spec.rake | 13 +--- spec/benchmarks/finders/issues_finder_spec.rb | 55 --------------- .../finders/trending_projects_finder_spec.rb | 14 ---- .../lib/gitlab/markdown/reference_filter_spec.rb | 41 ------------ spec/benchmarks/models/milestone_spec.rb | 17 ----- spec/benchmarks/models/project_spec.rb | 50 -------------- spec/benchmarks/models/project_team_spec.rb | 23 ------- spec/benchmarks/models/user_spec.rb | 78 ---------------------- .../services/projects/create_service_spec.rb | 28 -------- spec/spec_helper.rb | 2 - spec/support/matchers/benchmark_matchers.rb | 61 ----------------- 14 files changed, 3 insertions(+), 476 deletions(-) delete mode 100644 doc/development/benchmarking.md delete mode 100644 spec/benchmarks/finders/issues_finder_spec.rb delete mode 100644 spec/benchmarks/finders/trending_projects_finder_spec.rb delete mode 100644 spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb delete mode 100644 spec/benchmarks/models/milestone_spec.rb delete mode 100644 spec/benchmarks/models/project_spec.rb delete mode 100644 spec/benchmarks/models/project_team_spec.rb delete mode 100644 spec/benchmarks/models/user_spec.rb delete mode 100644 spec/benchmarks/services/projects/create_service_spec.rb delete mode 100644 spec/support/matchers/benchmark_matchers.rb diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d21785f7af2..bd013d50faa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -71,15 +71,6 @@ spec:services: - ruby - mysql -spec:benchmark: - stage: test - script: - - RAILS_ENV=test bundle exec rake spec:benchmark - tags: - - ruby - - mysql - allow_failure: true - spec:other: stage: test script: @@ -243,22 +234,6 @@ spec:services:ruby22: - ruby - mysql -spec:benchmark:ruby22: - stage: test - image: ruby:2.2 - only: - - master - script: - - RAILS_ENV=test bundle exec rake spec:benchmark - cache: - key: "ruby22" - paths: - - vendor - tags: - - ruby - - mysql - allow_failure: true - spec:other:ruby22: stage: test image: ruby:2.2 @@ -332,4 +307,4 @@ notify:slack: - master@gitlab-org/gitlab-ce - tags@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ee - - tags@gitlab-org/gitlab-ee \ No newline at end of file + - tags@gitlab-org/gitlab-ee diff --git a/doc/development/README.md b/doc/development/README.md index f5c3107ff44..1b281809afc 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -1,7 +1,6 @@ # Development - [Architecture](architecture.md) of GitLab -- [Benchmarking](benchmarking.md) - [CI setup](ci_setup.md) for testing GitLab - [Gotchas](gotchas.md) to avoid - [How to dump production data to staging](db_dump.md) diff --git a/doc/development/benchmarking.md b/doc/development/benchmarking.md deleted file mode 100644 index 88e18ee95f9..00000000000 --- a/doc/development/benchmarking.md +++ /dev/null @@ -1,69 +0,0 @@ -# Benchmarking - -GitLab CE comes with a set of benchmarks that are executed for every build. This -makes it easier to measure performance of certain components over time. - -Benchmarks are written as RSpec tests using a few extra helpers. To write a -benchmark, first tag the top-level `describe`: - -```ruby -describe MaruTheCat, benchmark: true do - -end -``` - -This ensures the benchmark is executed separately from other test collections. -It also exposes the various RSpec matchers used for writing benchmarks to the -test group. - -Next, lets write the actual benchmark: - -```ruby -describe MaruTheCat, benchmark: true do - let(:maru) { MaruTheChat.new } - - describe '#jump_in_box' do - benchmark_subject { maru.jump_in_box } - - it { is_expected.to iterate_per_second(9000) } - end -end -``` - -Here `benchmark_subject` is a small wrapper around RSpec's `subject` method that -makes it easier to specify the subject of a benchmark. Using RSpec's regular -`subject` would require us to write the following instead: - -```ruby -subject { -> { maru.jump_in_box } } -``` - -The `iterate_per_second` matcher defines the amount of times per second a -subject should be executed. The higher the amount of iterations the better. - -By default the allowed standard deviation is a maximum of 30%. This can be -adjusted by chaining the `with_maximum_stddev` on the `iterate_per_second` -matcher: - -```ruby -it { is_expected.to iterate_per_second(9000).with_maximum_stddev(50) } -``` - -This can be useful if the code in question depends on external resources of -which the performance can vary a lot (e.g. physical HDDs, network calls, etc). -However, in most cases 30% should be enough so only change this when really -needed. - -## Benchmarks Location - -Benchmarks should be stored in `spec/benchmarks` and should follow the regular -Rails specs structure. That is, model benchmarks go in `spec/benchmark/models`, -benchmarks for code in the `lib` directory go in `spec/benchmarks/lib`, etc. - -## Underlying Technology - -The benchmark setup uses [benchmark-ips][benchmark-ips] which takes care of the -heavy lifting such as warming up code, calculating iterations, standard -deviation, etc. - -[benchmark-ips]: https://github.com/evanphx/benchmark-ips diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake index 0985ef3a669..2cf7a25a0fd 100644 --- a/lib/tasks/spec.rake +++ b/lib/tasks/spec.rake @@ -46,20 +46,11 @@ namespace :spec do run_commands(cmds) end - desc 'GitLab | Rspec | Run benchmark specs' - task :benchmark do - cmds = [ - %W(rake gitlab:setup), - %W(rspec spec --tag @benchmark) - ] - run_commands(cmds) - end - desc 'GitLab | Rspec | Run other specs' task :other do cmds = [ %W(rake gitlab:setup), - %W(rspec spec --tag ~@api --tag ~@feature --tag ~@models --tag ~@lib --tag ~@services --tag ~@benchmark) + %W(rspec spec --tag ~@api --tag ~@feature --tag ~@models --tag ~@lib --tag ~@services) ] run_commands(cmds) end @@ -69,7 +60,7 @@ desc "GitLab | Run specs" task :spec do cmds = [ %W(rake gitlab:setup), - %W(rspec spec --tag ~@benchmark), + %W(rspec spec), ] run_commands(cmds) end diff --git a/spec/benchmarks/finders/issues_finder_spec.rb b/spec/benchmarks/finders/issues_finder_spec.rb deleted file mode 100644 index b57a33004a4..00000000000 --- a/spec/benchmarks/finders/issues_finder_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'spec_helper' - -describe IssuesFinder, benchmark: true do - describe '#execute' do - let(:user) { create(:user) } - let(:project) { create(:project, :public) } - - let(:label1) { create(:label, project: project, title: 'A') } - let(:label2) { create(:label, project: project, title: 'B') } - - before do - 10.times do |n| - issue = create(:issue, author: user, project: project) - - if n > 4 - create(:label_link, label: label1, target: issue) - create(:label_link, label: label2, target: issue) - end - end - end - - describe 'retrieving issues without labels' do - let(:finder) do - IssuesFinder.new(user, scope: 'all', label_name: Label::None.title, - state: 'opened') - end - - benchmark_subject { finder.execute } - - it { is_expected.to iterate_per_second(2000) } - end - - describe 'retrieving issues with labels' do - let(:finder) do - IssuesFinder.new(user, scope: 'all', label_name: label1.title, - state: 'opened') - end - - benchmark_subject { finder.execute } - - it { is_expected.to iterate_per_second(1000) } - end - - describe 'retrieving issues for a single project' do - let(:finder) do - IssuesFinder.new(user, scope: 'all', label_name: Label::None.title, - state: 'opened', project_id: project.id) - end - - benchmark_subject { finder.execute } - - it { is_expected.to iterate_per_second(2000) } - end - end -end diff --git a/spec/benchmarks/finders/trending_projects_finder_spec.rb b/spec/benchmarks/finders/trending_projects_finder_spec.rb deleted file mode 100644 index 551ce21840d..00000000000 --- a/spec/benchmarks/finders/trending_projects_finder_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'spec_helper' - -describe TrendingProjectsFinder, benchmark: true do - describe '#execute' do - let(:finder) { described_class.new } - let(:user) { create(:user) } - - # to_a is used to force actually running the query (instead of just building - # it). - benchmark_subject { finder.execute(user).non_archived.to_a } - - it { is_expected.to iterate_per_second(500) } - end -end diff --git a/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb b/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb deleted file mode 100644 index 3855763b200..00000000000 --- a/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'spec_helper' - -describe Banzai::Filter::ReferenceFilter, benchmark: true do - let(:input) do - html = <<-EOF -

Hello @alice and @bob, how are you doing today?

-

This is simple @dummy text to see how the @ReferenceFilter class performs -when @processing HTML.

- EOF - - Nokogiri::HTML.fragment(html) - end - - let(:project) { create(:empty_project) } - - let(:filter) { described_class.new(input, project: project) } - - describe '#replace_text_nodes_matching' do - let(:iterations) { 6000 } - - describe 'with identical input and output HTML' do - benchmark_subject do - filter.replace_text_nodes_matching(User.reference_pattern) do |content| - content - end - end - - it { is_expected.to iterate_per_second(iterations) } - end - - describe 'with different input and output HTML' do - benchmark_subject do - filter.replace_text_nodes_matching(User.reference_pattern) do |content| - '@eve' - end - end - - it { is_expected.to iterate_per_second(iterations) } - end - end -end diff --git a/spec/benchmarks/models/milestone_spec.rb b/spec/benchmarks/models/milestone_spec.rb deleted file mode 100644 index a94afc4c40d..00000000000 --- a/spec/benchmarks/models/milestone_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'spec_helper' - -describe Milestone, benchmark: true do - describe '#sort_issues' do - let(:milestone) { create(:milestone) } - - let(:issue1) { create(:issue, milestone: milestone) } - let(:issue2) { create(:issue, milestone: milestone) } - let(:issue3) { create(:issue, milestone: milestone) } - - let(:issue_ids) { [issue3.id, issue2.id, issue1.id] } - - benchmark_subject { milestone.sort_issues(issue_ids) } - - it { is_expected.to iterate_per_second(500) } - end -end diff --git a/spec/benchmarks/models/project_spec.rb b/spec/benchmarks/models/project_spec.rb deleted file mode 100644 index cee0949edc5..00000000000 --- a/spec/benchmarks/models/project_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -require 'spec_helper' - -describe Project, benchmark: true do - describe '.trending' do - let(:group) { create(:group) } - let(:project1) { create(:empty_project, :public, group: group) } - let(:project2) { create(:empty_project, :public, group: group) } - - let(:iterations) { 500 } - - before do - 2.times do - create(:note_on_commit, project: project1) - end - - create(:note_on_commit, project: project2) - end - - describe 'without an explicit start date' do - benchmark_subject { described_class.trending.to_a } - - it { is_expected.to iterate_per_second(iterations) } - end - - describe 'with an explicit start date' do - let(:date) { 1.month.ago } - - benchmark_subject { described_class.trending(date).to_a } - - it { is_expected.to iterate_per_second(iterations) } - end - end - - describe '.find_with_namespace' do - let(:group) { create(:group, name: 'sisinmaru') } - let(:project) { create(:project, name: 'maru', namespace: group) } - - describe 'using a capitalized namespace' do - benchmark_subject { described_class.find_with_namespace('sisinmaru/MARU') } - - it { is_expected.to iterate_per_second(600) } - end - - describe 'using a lowercased namespace' do - benchmark_subject { described_class.find_with_namespace('sisinmaru/maru') } - - it { is_expected.to iterate_per_second(600) } - end - end -end diff --git a/spec/benchmarks/models/project_team_spec.rb b/spec/benchmarks/models/project_team_spec.rb deleted file mode 100644 index 8b039ef7317..00000000000 --- a/spec/benchmarks/models/project_team_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'spec_helper' - -describe ProjectTeam, benchmark: true do - describe '#max_member_access' do - let(:group) { create(:group) } - let(:project) { create(:empty_project, group: group) } - let(:user) { create(:user) } - - before do - project.team << [user, :master] - - 5.times do - project.team << [create(:user), :reporter] - - project.group.add_user(create(:user), :reporter) - end - end - - benchmark_subject { project.team.max_member_access(user.id) } - - it { is_expected.to iterate_per_second(35000) } - end -end diff --git a/spec/benchmarks/models/user_spec.rb b/spec/benchmarks/models/user_spec.rb deleted file mode 100644 index 1be7a8d3ed9..00000000000 --- a/spec/benchmarks/models/user_spec.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'spec_helper' - -describe User, benchmark: true do - describe '.all' do - before do - 10.times { create(:user) } - end - - benchmark_subject { User.all.to_a } - - it { is_expected.to iterate_per_second(500) } - end - - describe '.by_login' do - before do - %w{Alice Bob Eve}.each do |name| - create(:user, - email: "#{name}@gitlab.com", - username: name, - name: name) - end - end - - # The iteration count is based on the query taking little over 1 ms when - # using PostgreSQL. - let(:iterations) { 900 } - - describe 'using a capitalized username' do - benchmark_subject { User.by_login('Alice') } - - it { is_expected.to iterate_per_second(iterations) } - end - - describe 'using a lowercase username' do - benchmark_subject { User.by_login('alice') } - - it { is_expected.to iterate_per_second(iterations) } - end - - describe 'using a capitalized Email address' do - benchmark_subject { User.by_login('Alice@gitlab.com') } - - it { is_expected.to iterate_per_second(iterations) } - end - - describe 'using a lowercase Email address' do - benchmark_subject { User.by_login('alice@gitlab.com') } - - it { is_expected.to iterate_per_second(iterations) } - end - end - - describe '.find_by_any_email' do - let(:user) { create(:user) } - - describe 'using a user with only a single Email address' do - let(:email) { user.email } - - benchmark_subject { User.find_by_any_email(email) } - - it { is_expected.to iterate_per_second(1000) } - end - - describe 'using a user with multiple Email addresses' do - let(:email) { user.emails.first.email } - - benchmark_subject { User.find_by_any_email(email) } - - before do - 10.times do - user.emails.create(email: FFaker::Internet.email) - end - end - - it { is_expected.to iterate_per_second(1000) } - end - end -end diff --git a/spec/benchmarks/services/projects/create_service_spec.rb b/spec/benchmarks/services/projects/create_service_spec.rb deleted file mode 100644 index 25ed48c34fd..00000000000 --- a/spec/benchmarks/services/projects/create_service_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'spec_helper' - -describe Projects::CreateService, benchmark: true do - describe '#execute' do - let(:user) { create(:user, :admin) } - - let(:group) do - group = create(:group) - - create(:group_member, group: group, user: user) - - group - end - - benchmark_subject do - name = SecureRandom.hex - service = described_class.new(user, - name: name, - path: name, - namespace_id: group.id, - visibility_level: Gitlab::VisibilityLevel::PUBLIC) - - service.execute - end - - it { is_expected.to iterate_per_second(0.5) } - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 159fb964171..7d939ca7509 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,7 +14,6 @@ require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'shoulda/matchers' require 'sidekiq/testing/inline' -require 'benchmark/ips' require 'rspec/retry' # Requires supporting ruby files with custom matchers and macros, etc, @@ -38,7 +37,6 @@ RSpec.configure do |config| config.include ActiveJob::TestHelper config.include StubGitlabCalls config.include StubGitlabData - config.include BenchmarkMatchers, benchmark: true config.infer_spec_type_from_file_location! config.raise_errors_for_deprecations! diff --git a/spec/support/matchers/benchmark_matchers.rb b/spec/support/matchers/benchmark_matchers.rb deleted file mode 100644 index 84f655c2119..00000000000 --- a/spec/support/matchers/benchmark_matchers.rb +++ /dev/null @@ -1,61 +0,0 @@ -module BenchmarkMatchers - extend RSpec::Matchers::DSL - - def self.included(into) - into.extend(ClassMethods) - end - - matcher :iterate_per_second do |min_iterations| - supports_block_expectations - - match do |block| - @max_stddev ||= 30 - - @entry = benchmark(&block) - - expect(@entry.ips).to be >= min_iterations - expect(@entry.stddev_percentage).to be <= @max_stddev - end - - chain :with_maximum_stddev do |value| - @max_stddev = value - end - - description do - "run at least #{min_iterations} iterations per second" - end - - failure_message do - ips = @entry.ips.round(2) - stddev = @entry.stddev_percentage.round(2) - - "expected at least #{min_iterations} iterations per second " \ - "with a maximum stddev of #{@max_stddev}%, instead of " \ - "#{ips} iterations per second with a stddev of #{stddev}%" - end - end - - # Benchmarks the given block and returns a Benchmark::IPS::Report::Entry. - def benchmark(&block) - report = Benchmark.ips(quiet: true) do |bench| - bench.report do - instance_eval(&block) - end - end - - report.entries[0] - end - - module ClassMethods - # Wraps around rspec's subject method so you can write: - # - # benchmark_subject { SomeClass.some_method } - # - # instead of: - # - # subject { -> { SomeClass.some_method } } - def benchmark_subject(&block) - subject { block } - end - end -end -- cgit v1.2.1 From e3efce9237ea15c7b1c609a364d3dac927ac6286 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 10 Mar 2016 14:29:38 +0100 Subject: Move group activity feed to separate page for consistency with dashboard and project pages Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + app/assets/javascripts/dispatcher.js.coffee | 3 ++- app/controllers/groups_controller.rb | 6 ++++-- app/views/groups/_activities.html.haml | 12 ++++++++++++ app/views/groups/activity.html.haml | 9 +++++++++ app/views/groups/show.html.haml | 15 +-------------- app/views/layouts/nav/_group.html.haml | 7 ++++++- config/routes.rb | 2 +- features/groups.feature | 4 ++++ features/steps/shared/paths.rb | 4 ++++ 10 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 app/views/groups/_activities.html.haml create mode 100644 app/views/groups/activity.html.haml diff --git a/CHANGELOG b/CHANGELOG index 9474ca50b24..e2846ee48df 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.6.0 (unreleased) - Show labels in dashboard and group milestone views - Add main language of a project in the list of projects (Tiago Botelho) - Add ability to show archived projects on dashboard, explore and group pages + - Move group activity to separate page v 8.5.5 - Ensure removing a project removes associated Todo entries. diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 54b28f2dd8d..ee81fee5868 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -74,8 +74,9 @@ class Dispatcher shortcut_handler = new ShortcutsNavigation() new TreeView() if $('#tree-slider').length - when 'groups:show' + when 'groups:activity' new Activities() + when 'groups:show' shortcut_handler = new ShortcutsNavigation() when 'groups:group_members:index' new GroupMembers() diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index f05c29e9974..360930f95a8 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -15,7 +15,7 @@ class GroupsController < Groups::ApplicationController # Load group projects before_action :load_projects, except: [:index, :new, :create, :projects, :edit, :update, :autocomplete] - before_action :event_filter, only: [:show, :events] + before_action :event_filter, only: [:activity] layout :determine_layout @@ -62,8 +62,10 @@ class GroupsController < Groups::ApplicationController end end - def events + def activity respond_to do |format| + format.html + format.json do load_events pager_json("events/_events", @events.count) diff --git a/app/views/groups/_activities.html.haml b/app/views/groups/_activities.html.haml new file mode 100644 index 00000000000..dc76599b776 --- /dev/null +++ b/app/views/groups/_activities.html.haml @@ -0,0 +1,12 @@ +.hidden-xs + = render "events/event_last_push", event: @last_push + +.nav-block + - if current_user + .controls + = link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn' do + %i.fa.fa-rss + = render 'shared/event_filter' + +.content_list += spinner diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml new file mode 100644 index 00000000000..10b432ef03e --- /dev/null +++ b/app/views/groups/activity.html.haml @@ -0,0 +1,9 @@ += content_for :meta_tags do + - if current_user + = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") + +- page_title "Activity" +- header_title "Activity", activity_dashboard_path + +%section.activities + = render 'activities' diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 6148d8cb3d2..3cf0a4baacd 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -30,26 +30,13 @@ %ul.nav-links %li.active - = link_to "#activity", 'data-toggle' => 'tab' do - Activity - %li = link_to "#projects", 'data-toggle' => 'tab' do Projects - if can?(current_user, :read_group, @group) %div{ class: container_class } .tab-content - .tab-pane.active#activity - .activity-filter-block - - if current_user - = render "events/event_last_push", event: @last_push - - = render 'shared/event_filter' - - .content_list{data: {href: events_group_path}} - = spinner - - .tab-pane#projects + .tab-pane.active#projects = render "projects", projects: @projects - else diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index e5e2a59eaed..59411ae1da1 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -9,10 +9,15 @@ = nav_link(path: 'groups#show', html_options: {class: 'home'}) do = link_to group_path(@group), title: 'Home' do - = icon('dashboard fw') + = icon('group fw') %span Group - if can?(current_user, :read_group, @group) + = nav_link(path: 'groups#activity') do + = link_to activity_group_path(@group), title: 'Activity' do + = icon('dashboard fw') + %span + Activity - if current_user = nav_link(controller: [:group, :milestones]) do = link_to group_milestones_path(@group), title: 'Milestones' do diff --git a/config/routes.rb b/config/routes.rb index a918b5bd3f0..869fca03ec4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -382,7 +382,7 @@ Rails.application.routes.draw do get :issues get :merge_requests get :projects - get :events + get :activity end scope module: :groups do diff --git a/features/groups.feature b/features/groups.feature index a60c3860b83..419a5d3963d 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -15,6 +15,10 @@ Feature: Groups Scenario: I should see group "Owned" dashboard list When I visit group "Owned" page Then I should see group "Owned" projects list + + @javascript + Scenario: I should see group "Owned" activity feed + When I visit group "Owned" activity page And I should see projects activity feed Scenario: I should see group "Owned" issues list diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index da9d1503ebc..2bd8ea745e4 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -27,6 +27,10 @@ module SharedPaths visit group_path(Group.find_by(name: "Owned")) end + step 'I visit group "Owned" activity page' do + visit activity_group_path(Group.find_by(name: "Owned")) + end + step 'I visit group "Owned" issues page' do visit issues_group_path(Group.find_by(name: "Owned")) end -- cgit v1.2.1 From 90ffb1c0dbed6f695cedb3b7ded2a1769136f238 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 10 Mar 2016 14:35:23 +0100 Subject: Fix header title Signed-off-by: Dmitriy Zaporozhets --- app/views/groups/activity.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml index 10b432ef03e..f73e1d9e865 100644 --- a/app/views/groups/activity.html.haml +++ b/app/views/groups/activity.html.haml @@ -3,7 +3,7 @@ = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") - page_title "Activity" -- header_title "Activity", activity_dashboard_path +- header_title group_title(@group, "Activity", activity_group_path(@group)) %section.activities = render 'activities' -- cgit v1.2.1 From fd49f1b0d3eca1a226df1552e11848d1d1c92f2f Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Thu, 10 Mar 2016 08:13:11 -0800 Subject: Reduce example documentation. --- doc/ci/README.md | 23 ++--------------------- doc/ci/examples/README.md | 20 +++++++++++++------- doc/ci/quick_start/README.md | 11 ++--------- 3 files changed, 17 insertions(+), 37 deletions(-) diff --git a/doc/ci/README.md b/doc/ci/README.md index 2120b5b2850..4abc45bf9bb 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -3,6 +3,7 @@ ### CI User documentation - [Get started with GitLab CI](quick_start/README.md) +- [CI examples for various languages](examples/README.md) - [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md) - [Learn how `.gitlab-ci.yml` works](yaml/README.md) - [Configure a Runner, the application that runs your builds](runners/README.md) @@ -14,24 +15,4 @@ - [Build artifacts](build_artifacts/README.md) - [User permissions](permissions/README.md) - [API](api/README.md) - -### CI Examples - -- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) -- [Test your PHP applications](examples/php.md) -- [Test and deploy Ruby applications to Heroku](examples/test-and-deploy-ruby-application-to-heroku.md) -- [Test and deploy Python applications to Heroku](examples/test-and-deploy-python-application-to-heroku.md) -- [Test Clojure applications](examples/test-clojure-application.md) -- [Using `dpl` as deployment tool](deployment/README.md) -- Help your favorite programming language and GitLab by sending a merge request - with a guide for that language. - -### CI Services - -GitLab CI uses the `services` keyword to define what docker containers should -be linked with your base image. Below is a list of examples you may use: - -- [Using MySQL](services/mysql.md) -- [Using PostgreSQL](services/postgres.md) -- [Using Redis](services/redis.md) -- [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services) +- [CI services (linked docker containers)](services/README.md) diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 31f29f4a082..0a83c4dd858 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -1,13 +1,19 @@ -## Build script examples +# CI Examples +- [Testing PHP](php.md) - [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md) - [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md) - [Test a Clojure application](test-clojure-application.md) +- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) +- [Test your PHP applications](examples/php.md) +- [Test and deploy Ruby applications to Heroku](examples/test-and-deploy-ruby-application-to-heroku.md) +- [Test and deploy Python applications to Heroku](examples/test-and-deploy-python-application-to-heroku.md) +- [Test Clojure applications](examples/test-clojure-application.md) +- [Using `dpl` as deployment tool](deployment/README.md) +- Help your favorite programming language and GitLab by sending a merge request + with a guide for that language. -## Languages - -This is a list of languages you can test with GitLab CI. Each section has -comprehensive documentation and comes with a test repository hosted on -GitLab.com. +## Outside the documentation -- [Testing PHP](php.md) +- [Blost post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) +- [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples) diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index 624d9899c79..9aba4326e11 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -223,20 +223,13 @@ You can access a builds badge image using following link: http://example.gitlab.com/namespace/project/badges/branch/build.svg ``` +Awesome! You started using CI in GitLab! + ## Examples Visit the [examples README][examples] to see a list of examples using GitLab CI with various languages. -## Next steps - -Awesome! You started using CI in GitLab! - -Next you can look into doing more with the CI. Many people are using GitLab -to package, containerize, test and deploy software. - -Visit our various languages examples at . - [runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation [blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/ [examples]: ../examples/README.md -- cgit v1.2.1 From 32ddf092f3ca5cbb9fdb7b7b3754ba61495d0532 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 10 Mar 2016 16:07:10 -0500 Subject: Upgrade `omniauth-saml` to 1.5.0 and document it's new capabilities. --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 8 ++++---- doc/integration/saml.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index aaffa51898d..047ff0425c8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 8.6.0 (unreleased) - Fix issue when pushing to projects ending in .wiki - Fix avatar stretching by providing a cropping feature (Johann Pardanaud) - Don't load all of GitLab in mail_room + - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set - Memoize @group in Admin::GroupsController (Yatish Mehta) - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie) - Strip leading and trailing spaces in URL validator (evuez) diff --git a/Gemfile b/Gemfile index 7e70761a77a..1550afb1b56 100644 --- a/Gemfile +++ b/Gemfile @@ -30,7 +30,7 @@ gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-google-oauth2', '~> 0.2.0' gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos -gem 'omniauth-saml', '~> 1.4.2' +gem 'omniauth-saml', '~> 1.5.0' gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd', '~> 2.2.0' diff --git a/Gemfile.lock b/Gemfile.lock index 432bdc344ad..ea5058e6414 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -532,8 +532,8 @@ GEM omniauth-oauth2 (1.3.1) oauth2 (~> 1.0) omniauth (~> 1.2) - omniauth-saml (1.4.2) - omniauth (~> 1.1) + omniauth-saml (1.5.0) + omniauth (~> 1.3) ruby-saml (~> 1.1, >= 1.1.1) omniauth-shibboleth (1.2.1) omniauth (>= 1.0.0) @@ -692,7 +692,7 @@ GEM ruby-fogbugz (0.2.1) crack (~> 0.4) ruby-progressbar (1.7.5) - ruby-saml (1.1.1) + ruby-saml (1.1.2) nokogiri (>= 1.5.10) uuid (~> 2.3) ruby2ruby (2.2.0) @@ -975,7 +975,7 @@ DEPENDENCIES omniauth-gitlab (~> 1.0.0) omniauth-google-oauth2 (~> 0.2.0) omniauth-kerberos (~> 0.3.0) - omniauth-saml (~> 1.4.2) + omniauth-saml (~> 1.5.0) omniauth-shibboleth (~> 1.2.0) omniauth-twitter (~> 1.2.0) omniauth_crowd (~> 2.2.0) diff --git a/doc/integration/saml.md b/doc/integration/saml.md index 148c4ac1886..1c7c114dbde 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -131,6 +131,58 @@ On the sign in page there should now be a SAML button below the regular sign in Click the icon to begin the authentication process. If everything goes well the user will be returned to GitLab and will be signed in. +## Customization + +### attribute_statements: + +>**Note:** +This setting is only available on GitLab 8.6 and above. +This setting should only be used to map attributes that are part of the +OmniAuth info hash schema. + +Used to map Attribute Names in a SAMLResponse to entries in the OmniAuth +[info hash](https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema#schema-10-and-later). + +For example, if your SAMLResponse contains an Attribute called 'EmailAddress', +specify `{ email: ['EmailAddress'] }` to map the Attribute to the +corresponding key in the info hash. URI-named Attributes are also supported, e.g. +`{ email: ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] }`. + +This setting allows you tell GitLab where to look for certain attributes required +to create an account. Like mentioned above, if your IdP send the user's email +address as `EmailAddress` instead of `email`, let GitLab know by setting it on +your configuration: + +```yaml +args: { + assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback', + idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8', + idp_sso_target_url: 'https://login.example.com/idp', + issuer: 'https://gitlab.example.com', + name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient', + attribute_statements: { :email => ['EmailAddress'] } +} +``` + +### allowed_clock_drift: + +The clock of the Identity Provider may drift slightly ahead of your system clocks. +To allow for a small amount of clock drift you can use this argument within your +settings. Its value must be given in a number (and/or fraction) of seconds. The +value given is added to the current time at which the response is validated. + +```yaml +args: { + assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback', + idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8', + idp_sso_target_url: 'https://login.example.com/idp', + issuer: 'https://gitlab.example.com', + name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient', + attribute_statements: { :email => ['EmailAddress'] }, + allowed_clock_drift: 1 # for one second clock drift +} +``` + ## Troubleshooting ### 500 error after login -- cgit v1.2.1 From e4237f1fe36e85501b624488cd227a91c6770eb3 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 10 Mar 2016 16:52:52 -0500 Subject: Consistently use the same hash syntax in the new documentation. --- doc/integration/saml.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/integration/saml.md b/doc/integration/saml.md index 1c7c114dbde..b61b7cb9678 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -160,7 +160,7 @@ args: { idp_sso_target_url: 'https://login.example.com/idp', issuer: 'https://gitlab.example.com', name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient', - attribute_statements: { :email => ['EmailAddress'] } + attribute_statements: { email: ['EmailAddress'] } } ``` @@ -178,7 +178,7 @@ args: { idp_sso_target_url: 'https://login.example.com/idp', issuer: 'https://gitlab.example.com', name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient', - attribute_statements: { :email => ['EmailAddress'] }, + attribute_statements: { email: ['EmailAddress'] }, allowed_clock_drift: 1 # for one second clock drift } ``` -- cgit v1.2.1 From 37b00b16a568655343a96976bf2b5252e0894c9c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 10 Mar 2016 18:55:18 -0300 Subject: Fix importing PR's from GitHub when the source repo was removed --- lib/gitlab/github_import/importer.rb | 11 ++-- lib/gitlab/github_import/pull_request_formatter.rb | 10 ++-- .../github_import/pull_request_formatter_spec.rb | 64 ++++++++++------------ 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index e2a85f29825..172c5441e36 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -45,10 +45,13 @@ module Gitlab direction: :asc).each do |raw_data| pull_request = PullRequestFormatter.new(project, raw_data) - if !pull_request.cross_project? && pull_request.valid? - merge_request = MergeRequest.create!(pull_request.attributes) - import_comments(pull_request.number, merge_request) - import_comments_on_diff(pull_request.number, merge_request) + if pull_request.valid? + merge_request = MergeRequest.new(pull_request.attributes) + + if merge_request.save + import_comments(pull_request.number, merge_request) + import_comments_on_diff(pull_request.number, merge_request) + end end end diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb index f96fed0f5cf..4e507b090e8 100644 --- a/lib/gitlab/github_import/pull_request_formatter.rb +++ b/lib/gitlab/github_import/pull_request_formatter.rb @@ -17,16 +17,12 @@ module Gitlab } end - def cross_project? - source_repo.id != target_repo.id - end - def number raw_data.number end def valid? - source_branch.present? && target_branch.present? + !cross_project? && source_branch.present? && target_branch.present? end private @@ -53,6 +49,10 @@ module Gitlab raw_data.body || "" end + def cross_project? + source_repo.present? && target_repo.present? && source_repo.id != target_repo.id + end + def description formatter.author_line(author) + body 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 6cebcb5009a..e49dcb42342 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -127,34 +127,6 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do end end - describe '#cross_project?' do - context 'when source, and target repositories are the same' do - let(:raw_data) { OpenStruct.new(base_data) } - - it 'returns false' do - expect(pull_request.cross_project?).to eq false - end - end - - context 'when source repo is a fork' do - let(:source_repo) { OpenStruct.new(id: 2, fork: true) } - let(:raw_data) { OpenStruct.new(base_data) } - - it 'returns true' do - expect(pull_request.cross_project?).to eq true - end - end - - context 'when target repo is a fork' do - let(:target_repo) { OpenStruct.new(id: 2, fork: true) } - let(:raw_data) { OpenStruct.new(base_data) } - - it 'returns true' do - expect(pull_request.cross_project?).to eq true - end - end - end - describe '#number' do let(:raw_data) { OpenStruct.new(base_data.merge(number: 1347)) } @@ -166,24 +138,44 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do describe '#valid?' do let(:invalid_branch) { OpenStruct.new(ref: 'invalid-branch') } - context 'when source and target branches exists' do - let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: target_branch)) } + context 'when source, and target repositories are the same' do + context 'and source and target branches exists' do + let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: target_branch)) } - it 'returns true' do - expect(pull_request.valid?).to eq true + it 'returns true' do + expect(pull_request.valid?).to eq true + end + end + + context 'and source branch doesn not exists' do + let(:raw_data) { OpenStruct.new(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) { OpenStruct.new(base_data.merge(head: source_branch, base: invalid_branch)) } + + it 'returns false' do + expect(pull_request.valid?).to eq false + end end end - context 'when source branch doesn not exists' do - let(:raw_data) { OpenStruct.new(base_data.merge(head: invalid_branch, base: target_branch)) } + context 'when source repo is a fork' do + let(:source_repo) { OpenStruct.new(id: 2, fork: true) } + let(:raw_data) { OpenStruct.new(base_data) } it 'returns false' do expect(pull_request.valid?).to eq false end end - context 'when target branch doesn not exists' do - let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: invalid_branch)) } + context 'when target repo is a fork' do + let(:target_repo) { OpenStruct.new(id: 2, fork: true) } + let(:raw_data) { OpenStruct.new(base_data) } it 'returns false' do expect(pull_request.valid?).to eq false -- cgit v1.2.1 From 75ba7d5d4f13e4c4e4f53ff03a61a0f447ea0ce2 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 10 Mar 2016 20:32:08 -0500 Subject: Prepare docs for 8.6 RC1 [ci skip] --- doc/install/installation.md | 4 +- doc/update/8.5-to-8.6.md | 145 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 doc/update/8.5-to-8.6.md diff --git a/doc/install/installation.md b/doc/install/installation.md index 0fd54be58b0..4f011397269 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -233,9 +233,9 @@ sudo usermod -aG redis git ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-5-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-6-stable gitlab -**Note:** You can change `8-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `8-6-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It diff --git a/doc/update/8.5-to-8.6.md b/doc/update/8.5-to-8.6.md new file mode 100644 index 00000000000..e42f691cd32 --- /dev/null +++ b/doc/update/8.5-to-8.6.md @@ -0,0 +1,145 @@ +# From 8.5 to 8.6 + +### 1. Stop server + + sudo service gitlab stop + +### 2. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Get latest code + +```bash +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 8-6-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 8-6-stable-ee +``` + +### 4. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch --all +sudo -u git -H git checkout v2.6.10 +``` + +### 5. Update gitlab-workhorse + +Install and compile gitlab-workhorse. This requires +[Go 1.5](https://golang.org/dl) which should already be on your system from +GitLab 8.1. + +```bash +cd /home/git/gitlab-workhorse +sudo -u git -H git fetch --all +sudo -u git -H git checkout 0.6.5 +sudo -u git -H make +``` + +### 6. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +``` + +### 7. Update configuration files + +#### New configuration options for `gitlab.yml` + +There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +git diff origin/8-5-stable:config/gitlab.yml.example origin/8-6-stable:config/gitlab.yml.example +``` + +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +# For HTTPS configurations +git diff origin/8-5-stable:lib/support/nginx/gitlab-ssl origin/8-6-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/8-5-stable:lib/support/nginx/gitlab origin/8-6-stable:lib/support/nginx/gitlab +``` + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-6-stable/lib/support/init.d/gitlab.default.example#L37 + +#### Init script + +Ensure you're still up-to-date with the latest init script changes: + + sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +### 8. Start application + + sudo service gitlab start + sudo service nginx restart + +### 9. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (8.5) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 8.4 to 8.5](8.4-to-8.5.md), except for the +database migration (the backup is already migrated to the previous version). + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. -- cgit v1.2.1 From 36ddca101e05ce885f23e9a797c577e81f70ab16 Mon Sep 17 00:00:00 2001 From: Josh Frye Date: Thu, 10 Mar 2016 21:22:46 -0500 Subject: Filter import_url params because they may contain auth information. Fixes #14199 --- config/application.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/application.rb b/config/application.rb index d8d1e7b4679..2b103c4592d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -34,7 +34,7 @@ module Gitlab config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. - config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt, :variables) + config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt, :variables, :import_url) # Enable escaping HTML in JSON. config.active_support.escape_html_entities_in_json = true -- cgit v1.2.1 From 5e145918b4dc20672ca212d094a73b87476d1db5 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 1 Mar 2016 11:25:59 +0000 Subject: Updated UI of award emoji Closes #13878 --- app/assets/javascripts/awards_handler.coffee | 37 +++-- app/assets/stylesheets/framework/blocks.scss | 4 + app/assets/stylesheets/pages/awards.scss | 189 ++++++++++------------ app/views/emojis/index.html.haml | 10 +- app/views/projects/issues/show.html.haml | 2 +- app/views/projects/merge_requests/_show.html.haml | 2 +- app/views/votes/_votes_block.html.haml | 27 +--- 7 files changed, 131 insertions(+), 140 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 8f89d3e61a2..351f871de94 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -1,6 +1,6 @@ class @AwardsHandler constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) -> - $(".add-award").click (event) => + $(".js-add-award").click (event) => event.stopPropagation() event.preventDefault() @@ -11,18 +11,28 @@ class @AwardsHandler if $(".emoji-menu").is(":visible") $(".emoji-menu").hide() + $(".awards").off "click" + $(".awards").on "click", ".js-emoji-btn", @handleClick + @renderFrequentlyUsedBlock() - @setupSearch() + + handleClick: (e) -> + e.preventDefault() + emoji = $(this).find(".icon").data "emoji" + awards_handler.addAward emoji showEmojiMenu: -> if $(".emoji-menu").length $(".emoji-menu").show() $("#emoji_search").focus() else - $.get "/emojis", (response) -> - $(".add-award").after response + $('.js-add-award').addClass "is-loading" + $.get "/emojis", (response) => + $('.js-add-award').removeClass "is-loading" + $(".js-award-holder").append response $(".emoji-menu").show() $("#emoji_search").focus() + @setupSearch() addAward: (emoji) -> emoji = @normilizeEmojiName(emoji) @@ -39,7 +49,7 @@ class @AwardsHandler if @isActive(emoji) @decrementCounter(emoji) else - counter = @findEmojiIcon(emoji).siblings(".counter") + counter = @findEmojiIcon(emoji).siblings(".js-counter") counter.text(parseInt(counter.text()) + 1) counter.parent().addClass("active") @addMeToAuthorList(emoji) @@ -53,7 +63,7 @@ class @AwardsHandler @findEmojiIcon(emoji).parent().hasClass("active") decrementCounter: (emoji) -> - counter = @findEmojiIcon(emoji).siblings(".counter") + counter = @findEmojiIcon(emoji).siblings(".js-counter") emojiIcon = counter.parent() if parseInt(counter.text()) > 1 counter.text(parseInt(counter.text()) - 1) @@ -98,14 +108,13 @@ class @AwardsHandler emojiCssClass = @resolveNameToCssClass(emoji) nodes = [] - nodes.push("
") - nodes.push("
") - nodes.push("
1
") - nodes.push("
") - - emoji_node = $(nodes.join("\n")).insertBefore(".awards-controls").find(".emoji-icon").data("emoji", emoji) + nodes.push "" - $(".award").tooltip() + emoji_node = $(nodes.join("\n")).insertBefore(".js-award-holder").find(".emoji-icon").data("emoji", emoji) + $('.award-control').tooltip() resolveNameToCssClass: (emoji) -> emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']") @@ -128,7 +137,7 @@ class @AwardsHandler callback.call() findEmojiIcon: (emoji) -> - $(".award [data-emoji='#{emoji}']") + $(".awards > .js-emoji-btn [data-emoji='#{emoji}']") scrollToAwards: -> $('body, html').animate({ diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index e6609ac7108..6edabe20136 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -157,3 +157,7 @@ float: right; } } + +.content-block-small { + padding: 10px 0; +} diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index 87dd30f4111..e692730bddf 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -1,125 +1,112 @@ .awards { - @include clearfix; line-height: 34px; .emoji-icon { width: 20px; height: 20px; - margin: 7px 0 0 5px; } +} - .award { - @include border-radius(5px); +.emoji-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + min-width: 160px; + font-size: 14px; + background-color: #fff; + border: 1px solid #F1F2F4; + border-radius: 2px; + box-shadow: 0 6px 12px rgba(0,0,0,.175); + + .emoji-menu-content { + padding: $gl-padding; + width: 300px; + height: 300px; + overflow-y: scroll; + + input.emoji-search{ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC"); + background-repeat: no-repeat; + background-position: right 5px center; + background-size: 16px; + } + } +} - border: 1px solid; - padding: 0px 10px; - float: left; - margin-right: 5px; - border-color: $border-color; - cursor: pointer; +.emoji-menu-list { + list-style: none; + padding-left: 0; + margin-bottom: 0; +} - &:hover { - background-color: #dce0e5; - } +.emoji-menu-list-item { + padding: 3px; + margin-left: 1px; + margin-right: 1px; +} + +.emoji-menu-btn { + display: block; + cursor: pointer; + width: 30px; + height: 30px; + padding: 0; + background: none; + border: 0; + border-radius: 4px; + + &:hover { + background-color: #ededed; + } + + .emoji-icon { + display: inline-block; + position: relative; + top: 3px; + } +} - &.active { - border-color: $border-gray-light; - background-color: $gray-light; +.award-menu-holder { + display: inline-block; + position: relative; +} - &:hover { - background-color: #dce0e5; - } +.award-control { + margin-right: 5px; + line-height: 20px; + outline: 0; - .counter { - font-weight: bold; - } - } + &.active, + &:active { + background-color: #ededed; + box-shadow: none; + outline: 0; + } - .icon { - float: left; - margin-right: 10px; + &.is-loading { + .award-control-icon { + display: none; } - .counter { - float: left; + .award-control-icon-loading { + display: block; } } - .awards-controls { - position: relative; - margin-left: 10px; + .icon, + .award-control-icon { float: left; + margin-right: 5px; + font-size: 20px; + } - .add-award { - font-size: 24px; - color: $gl-gray; - position: relative; - top: 2px; - - &:hover, - &:link { - text-decoration: none; - } - } + .award-control-icon-loading { + display: none; + } - .emoji-menu{ - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - min-width: 160px; - padding: 5px 0; - margin: 2px 0 0; - font-size: 14px; - text-align: left; - list-style: none; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0,0,0,.15); - border-radius: 4px; - -webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175); - box-shadow: 0 6px 12px rgba(0,0,0,.175); - - .emoji-menu-content { - padding: $gl-padding; - width: 300px; - height: 300px; - overflow-y: scroll; - - h5 { - clear: left; - } - - ul { - list-style-type: none; - margin-left: -20px; - margin-bottom: 20px; - overflow: auto; - } - - input.emoji-search{ - background: image-url("icon-search.png") 240px no-repeat; - } - - li { - cursor: pointer; - width: 30px; - height: 30px; - text-align: center; - float: left; - margin: 3px; - list-decorate: none; - @include border-radius(5px); - - &:hover { - background-color: #ccc; - } - } - } - } + .award-control-icon { + color: #DCDCDC; } } diff --git a/app/views/emojis/index.html.haml b/app/views/emojis/index.html.haml index b66e513e4d2..3443a8e2307 100644 --- a/app/views/emojis/index.html.haml +++ b/app/views/emojis/index.html.haml @@ -2,8 +2,10 @@ .emoji-menu-content = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control" - AwardEmoji.emoji_by_category.each do |category, emojis| - %h5= AwardEmoji::CATEGORIES[category] - %ul + %h5.emoji-menu-title + = AwardEmoji::CATEGORIES[category] + %ul.clearfix.emoji-menu-list - emojis.each do |emoji| - %li - = emoji_icon(emoji["name"], emoji["unicode"], emoji["aliases"]) \ No newline at end of file + %li.pull-left.text-center.emoji-menu-list-item + %button.emoji-menu-btn.text-center.js-emoji-btn{type: "button"} + = emoji_icon(emoji["name"], emoji["unicode"], emoji["aliases"]) diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 617b0437807..eedeff2bed6 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -71,7 +71,7 @@ .merge-requests = render 'merge_requests' - .content-block + .content-block.content-block-small = render 'votes/votes_block', votable: @issue .row diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index b262892ac65..ee5b9fd95a8 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -68,7 +68,7 @@ .tab-content #notes.notes.tab-pane.voting_notes - .content-block.oneline-block + .content-block.content-block-small.oneline-block = render 'votes/votes_block', votable: @merge_request .row diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 176fd29cb57..20d2d5f317b 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,14 +1,17 @@ .awards.votes-block - awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes| - .award{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user)} + %button.btn.award-control.js-emoji-btn.has_tooltip{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user), data: {placement: "top"}} = emoji_icon(emoji) - .counter + %span.award-control-text.js-counter = notes.count - if current_user - .awards-controls - %a.add-award{"href" => "#"} - = icon('smile-o') + %div.award-menu-holder.js-award-holder + %a.btn.award-control.js-add-award{"href" => "#"} + = icon('smile-o', {class: "award-control-icon"}) + = icon('spinner spin', {class: "award-control-icon award-control-icon-loading"}) + %span.award-control-text + Add - if current_user :javascript @@ -23,17 +26,3 @@ noteable_id, aliases ); - - $(".awards").on("click", ".emoji-menu-content li", function(e) { - var emoji = $(this).find(".emoji-icon").data("emoji"); - awards_handler.addAward(emoji); - }); - - $(".awards").on("click", ".award", function(e) { - var emoji = $(this).find(".icon").data("emoji"); - awards_handler.addAward(emoji); - }); - - $(".award").tooltip(); - - $(".emoji-menu-content").niceScroll({cursorwidth: "7px", autohidemode: false}); -- cgit v1.2.1 From a7e76a6119b4f51bdb29ea1420c151081cd12d77 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 1 Mar 2016 13:00:26 +0000 Subject: Updated award emoji tests --- app/assets/javascripts/awards_handler.coffee | 2 +- features/steps/project/issues/award_emoji.rb | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 351f871de94..829340dea6e 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -82,7 +82,7 @@ class @AwardsHandler award_block = @findEmojiIcon(emoji).parent() authors = award_block.attr("data-original-title").split(", ") authors.splice(authors.indexOf("me"),1) - award_block.closest(".award").attr("data-original-title", authors.join(", ")) + award_block.closest(".js-emoji-btn").attr("data-original-title", authors.join(", ")) @resetTooltip(award_block) addMeToAuthorList: (emoji) -> diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index 277c63914d1..9f0f1812b46 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -10,7 +10,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps step 'I click the thumbsup award Emoji' do page.within '.awards' do - thumbsup = page.find('.award .emoji-1F44D') + thumbsup = page.first('.award-control') thumbsup.click thumbsup.hover sleep 0.3 @@ -18,23 +18,23 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end step 'I click to emoji-picker' do - page.within '.awards-controls' do - page.find('.add-award').click + page.within '.awards' do + page.find('.js-add-award').click end end step 'I click to emoji in the picker' do page.within '.emoji-menu-content' do - page.first('.emoji-icon').click + page.first('.js-emoji-btn').click end end step 'I can remove it by clicking to icon' do page.within '.awards' do expect do - page.find('.award.active').click + page.find('.js-emoji-btn.active').click sleep 0.3 - end.to change{ page.all(".award").size }.from(3).to(2) + end.to change{ page.all(".award-control.js-emoji-btn").size }.from(3).to(2) end end @@ -49,23 +49,23 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps sleep 0.2 page.within '.awards' do - expect(page).to have_selector '.award' - expect(page.find('.award.active .counter')).to have_content '1' - expect(page.find('.award.active')['data-original-title']).to eq('me') + expect(page).to have_selector '.js-emoji-btn' + expect(page.find('.js-emoji-btn.active .js-counter')).to have_content '1' + expect(page.find('.js-emoji-btn.active')['data-original-title']).to eq('me') end end step 'I have no awards added' do page.within '.awards' do - expect(page).to have_selector '.award' - expect(page.all('.award').size).to eq(2) + expect(page).to have_selector '.award-control.js-emoji-btn' + expect(page.all('.award-control.js-emoji-btn').size).to eq(2) # Check tooltip data - page.all('.award').each do |element| + page.all('.award-control.js-emoji-btn').each do |element| expect(element['title']).to eq("") end - page.all('.award .counter').each do |element| + page.all('.award-control .js-counter').each do |element| expect(element).to have_content '0' end end -- cgit v1.2.1 From e3d844984b385cebcb92d21695b1d513e6b745c7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 2 Mar 2016 15:40:00 +0000 Subject: Moved SCSS values into variables --- app/assets/javascripts/awards_handler.coffee | 14 ++++++++++---- app/assets/stylesheets/framework/variables.scss | 11 +++++++++++ app/assets/stylesheets/pages/awards.scss | 14 +++++++------- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 829340dea6e..e9bf053aa56 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -1,6 +1,6 @@ class @AwardsHandler constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) -> - $(".js-add-award").click (event) => + $(".js-add-award").on "click", (event) => event.stopPropagation() event.preventDefault() @@ -18,7 +18,9 @@ class @AwardsHandler handleClick: (e) -> e.preventDefault() - emoji = $(this).find(".icon").data "emoji" + emoji = $(this) + .find(".icon") + .data "emoji" awards_handler.addAward emoji showEmojiMenu: -> @@ -80,9 +82,13 @@ class @AwardsHandler removeMeFromAuthorList: (emoji) -> award_block = @findEmojiIcon(emoji).parent() - authors = award_block.attr("data-original-title").split(", ") + authors = award_block + .attr("data-original-title") + .split(", ") authors.splice(authors.indexOf("me"),1) - award_block.closest(".js-emoji-btn").attr("data-original-title", authors.join(", ")) + award_block + .closest(".js-emoji-btn") + .attr("data-original-title", authors.join(", ")) @resetTooltip(award_block) addMeToAuthorList: (emoji) -> diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 6561b3de7c1..fe8ea399d9c 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -150,3 +150,14 @@ $dropdown-toggle-border-color: #EAEAEA; $dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 15%); $dropdown-toggle-icon-color: #C4C4C4; $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color; + +/* + * Award emoji + */ +$award-emoji-menu-bg: #FFF; +$award-emoji-menu-border: #F1F2F4; +$award-emoji-menu-radius: 3px; +$award-emoji-menu-btn-radius: 4px; +$award-emoji-menu-btn-hover-bg: #EDEDED; +$award-emoji-control-active-bg: $award-emoji-menu-btn-hover-bg; +$award-emoji-new-btn-color: #DCDCDC; diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index e692730bddf..3134511cb31 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -15,9 +15,9 @@ display: none; min-width: 160px; font-size: 14px; - background-color: #fff; - border: 1px solid #F1F2F4; - border-radius: 2px; + background-color: $award-emoji-menu-bg; + border: 1px solid $award-emoji-menu-border; + border-radius: $award-emoji-menu-radius; box-shadow: 0 6px 12px rgba(0,0,0,.175); .emoji-menu-content { @@ -55,10 +55,10 @@ padding: 0; background: none; border: 0; - border-radius: 4px; + border-radius: $award-emoji-menu-btn-radius; &:hover { - background-color: #ededed; + background-color: $award-emoji-menu-btn-hover-bg; } .emoji-icon { @@ -80,7 +80,7 @@ &.active, &:active { - background-color: #ededed; + background-color: $award-emoji-control-active-bg; box-shadow: none; outline: 0; } @@ -107,6 +107,6 @@ } .award-control-icon { - color: #DCDCDC; + color: $award-emoji-new-btn-color; } } -- cgit v1.2.1 From b5f3cf82184c1293684337e2be847c2bc3268578 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 2 Mar 2016 15:43:01 +0000 Subject: Moved method calls to separate lines --- app/assets/javascripts/awards_handler.coffee | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index e9bf053aa56..66b26e4e6d7 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -114,12 +114,17 @@ class @AwardsHandler emojiCssClass = @resolveNameToCssClass(emoji) nodes = [] - nodes.push "" - - emoji_node = $(nodes.join("\n")).insertBefore(".js-award-holder").find(".emoji-icon").data("emoji", emoji) + nodes.push( + "" + ) + + emoji_node = $(nodes.join("\n")) + .insertBefore(".js-award-holder") + .find(".emoji-icon") + .data("emoji", emoji) $('.award-control').tooltip() resolveNameToCssClass: (emoji) -> -- cgit v1.2.1 From fc79fe7b535770507e457ec996e87fbd8504c91e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 2 Mar 2016 18:55:05 +0000 Subject: Renamed variables & removed some for award emoji --- app/assets/stylesheets/framework/variables.scss | 6 +----- app/assets/stylesheets/pages/awards.scss | 10 +++++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index fe8ea399d9c..b038c7958d2 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -156,8 +156,4 @@ $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color; */ $award-emoji-menu-bg: #FFF; $award-emoji-menu-border: #F1F2F4; -$award-emoji-menu-radius: 3px; -$award-emoji-menu-btn-radius: 4px; -$award-emoji-menu-btn-hover-bg: #EDEDED; -$award-emoji-control-active-bg: $award-emoji-menu-btn-hover-bg; -$award-emoji-new-btn-color: #DCDCDC; +$award-emoji-new-btn-icon-color: #DCDCDC; diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index 3134511cb31..b0cd187fc00 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -17,7 +17,7 @@ font-size: 14px; background-color: $award-emoji-menu-bg; border: 1px solid $award-emoji-menu-border; - border-radius: $award-emoji-menu-radius; + border-radius: $border-radius-base; box-shadow: 0 6px 12px rgba(0,0,0,.175); .emoji-menu-content { @@ -55,10 +55,10 @@ padding: 0; background: none; border: 0; - border-radius: $award-emoji-menu-btn-radius; + border-radius: $border-radius-base; &:hover { - background-color: $award-emoji-menu-btn-hover-bg; + background-color: $white-dark; } .emoji-icon { @@ -80,7 +80,7 @@ &.active, &:active { - background-color: $award-emoji-control-active-bg; + background-color: $white-dark; box-shadow: none; outline: 0; } @@ -107,6 +107,6 @@ } .award-control-icon { - color: $award-emoji-new-btn-color; + color: $award-emoji-new-btn-icon-color; } } -- cgit v1.2.1 From bfa6064dc9c5eda998db8ff8945c0cc1354f0b7d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 3 Mar 2016 08:57:40 +0000 Subject: Award emoji button padding --- app/assets/stylesheets/pages/awards.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index b0cd187fc00..6a19562e592 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -75,6 +75,8 @@ .award-control { margin-right: 5px; + padding-left: 5px; + padding-right: 5px; line-height: 20px; outline: 0; -- cgit v1.2.1 From cb72f9b30cd524cb238f3462cfbcb2ee90dee146 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 4 Mar 2016 09:13:06 +0000 Subject: CoffeeScript style improv. --- app/assets/javascripts/awards_handler.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 66b26e4e6d7..13b4a7e05e9 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -11,8 +11,9 @@ class @AwardsHandler if $(".emoji-menu").is(":visible") $(".emoji-menu").hide() - $(".awards").off "click" - $(".awards").on "click", ".js-emoji-btn", @handleClick + $(".awards") + .off "click" + .on "click", ".js-emoji-btn", @handleClick @renderFrequentlyUsedBlock() -- cgit v1.2.1 From 57a9d07716b39206c683430ef65d00af32fd9811 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 11 Mar 2016 09:11:36 +0000 Subject: Fixed bug with emoji search styling --- app/assets/javascripts/awards_handler.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 13b4a7e05e9..da102db39c2 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -185,13 +185,13 @@ class @AwardsHandler term = $(ev.target).val() # Clean previous search results - $("ul.emoji-search,h5.emoji-search").remove() + $("ul.emoji-menu-search, h5.emoji-search").remove() if term # Generate a search result block h5 = $("
").text("Search results").addClass("emoji-search") found_emojis = @searchEmojis(term).show() - ul = $("
    ").addClass("emoji-search").append(found_emojis) + ul = $("
      ").addClass("emoji-menu-list emoji-menu-search").append(found_emojis) $(".emoji-menu-content ul, .emoji-menu-content h5").hide() $(".emoji-menu-content").append(h5).append(ul) else -- cgit v1.2.1 From 25f392eab734bd68da377a61c608bca73355ba43 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 11 Mar 2016 09:47:18 +0000 Subject: Added 'surprise' animation --- app/assets/javascripts/awards_handler.coffee | 20 +++++++++++++------- app/assets/stylesheets/pages/awards.scss | 23 +++++++++++++++++++++-- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index da102db39c2..03a44874161 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -9,7 +9,7 @@ class @AwardsHandler $("html").on 'click', (event) -> if !$(event.target).closest(".emoji-menu").length if $(".emoji-menu").is(":visible") - $(".emoji-menu").hide() + $(".emoji-menu").removeClass "is-visible" $(".awards") .off "click" @@ -26,23 +26,29 @@ class @AwardsHandler showEmojiMenu: -> if $(".emoji-menu").length - $(".emoji-menu").show() - $("#emoji_search").focus() + if $(".emoji-menu").is ".is-visible" + $(".emoji-menu").removeClass "is-visible" + $("#emoji_search").blur() + else + $(".emoji-menu").addClass "is-visible" + $("#emoji_search").focus() else $('.js-add-award').addClass "is-loading" $.get "/emojis", (response) => $('.js-add-award').removeClass "is-loading" $(".js-award-holder").append response - $(".emoji-menu").show() - $("#emoji_search").focus() - @setupSearch() + setTimeout => + $(".emoji-menu").addClass "is-visible" + $("#emoji_search").focus() + @setupSearch() + , 200 addAward: (emoji) -> emoji = @normilizeEmojiName(emoji) @postEmoji emoji, => @addAwardToEmojiBar(emoji) - $(".emoji-menu").hide() + $(".emoji-menu").removeClass "is-visible" addAwardToEmojiBar: (emoji) -> @addEmojiToFrequentlyUsedList(emoji) diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index 6a19562e592..28994e60baa 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -11,14 +11,25 @@ position: absolute; top: 100%; left: 0; + margin-top: 3px; z-index: 1000; - display: none; min-width: 160px; font-size: 14px; background-color: $award-emoji-menu-bg; border: 1px solid $award-emoji-menu-border; border-radius: $border-radius-base; box-shadow: 0 6px 12px rgba(0,0,0,.175); + pointer-events: none; + opacity: 0; + transform: scale(.2); + transform-origin: 0 -45px; + transition: all .3s cubic-bezier(.87,-.41,.19,1.44); + + &.is-visible { + pointer-events: all; + opacity: 1; + transform: scale(1); + } .emoji-menu-content { padding: $gl-padding; @@ -56,9 +67,17 @@ background: none; border: 0; border-radius: $border-radius-base; + transition: transform .15s cubic-bezier(.3, 0, .2, 2); &:hover { - background-color: $white-dark; + background-color: transparent; + outline: 0; + transform: scale(1.3); + } + + &:focus, + &:active { + outline: 0; } .emoji-icon { -- cgit v1.2.1 From ad4d3a075fc338280baaf6240861c9de7aa312ad Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 11 Mar 2016 13:39:11 +0100 Subject: Describe special YAML features: the use of anchors and hidden jobs --- CHANGELOG | 2 + doc/ci/yaml/README.md | 71 ++++++++++++++++++++++++++++ lib/ci/gitlab_ci_yaml_processor.rb | 2 + spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 29 ++++++++++++ 4 files changed, 104 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 1847c5193ab..66675e39427 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,8 @@ v 8.6.0 (unreleased) - Return empty array instead of 404 when commit has no statuses in commit status API - Decrease the font size and the padding of the `.anchor` icons used in the README (Roberto Dip) - Rewrite logo to simplify SVG code (Sean Lang) + - Allow to use YAML anchors when parsing the `.gitlab-ci.yml` (Pascal Bach) + - Ignore jobs that start with `.` (hidden jobs) - Add support for cross-project label references - Update documentation to reflect Guest role not being enforced on internal projects - Allow search for logged out users diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 051eaa04152..ec57ac5789e 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -509,6 +509,77 @@ rspec: The cache is provided on best effort basis, so don't expect that cache will be always present. For implementation details please check GitLab Runner. +## Special features + +It's possible special YAML features like anchors and map merging. +Thus allowing to greatly reduce the complexity of `.gitlab-ci.yml`. + +#### Anchors + +You can read more about YAML features [here](https://learnxinyminutes.com/docs/yaml/). + +```yaml +.job_template: &job_definition + image: ruby:2.1 + services: + - postgres + - redis + +test1: + << *job_definition + script: + - test project + +test2: + << *job_definition + script: + - test project +``` + +The above example uses anchors and map merging. +It will create a two jobs: `test1` and `test2` that will have the parameters of `.job_template` and custom `script` defined. + +```yaml +.job_template: &job_definition + script: + - test project + +.postgres_services: + services: &postgres_definition + - postgres + - ruby + +.mysql_services: + services: &mysql_definition + - mysql + - ruby + +test:postgres: + << *job_definition + services: *postgres_definition + +test:mysql: + << *job_definition + services: *mysql_definition +``` + +The above example uses anchors to define two set of services. +It will create a two jobs: `test:postgres` and `test:mysql` that will have the script defined in `.job_template` +and one, the service defined in `.postgres_services` and second the services defined in `.mysql_services`. + +### Hidden jobs + +The jobs that start with `.` will be not processed by GitLab. + +Example of such hidden jobs: +```yaml +.job_name: + script: + - rake spec +``` + +The `.job_name` will be ignored. You can use this feature to ignore jobs, or use them as templates with special YAML features. + ## Validate the .gitlab-ci.yml Each instance of GitLab CI has an embedded debug tool called Lint. diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 1a3f662811a..8ece73eec0e 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -60,6 +60,7 @@ module Ci @jobs = {} @config.each do |key, job| + next if key.to_s.start_with?('.') stage = job[:stage] || job[:type] || DEFAULT_STAGE @jobs[key] = { stage: stage }.merge(job) end @@ -81,6 +82,7 @@ module Ci services: job[:services] || @services, artifacts: job[:artifacts], cache: job[:cache] || @cache, + dependencies: job[:dependencies], }.compact } end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index f3394910c5b..665a65fe352 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -427,6 +427,35 @@ module Ci end end + describe "Hidden jobs" do + let(:config) do + YAML.dump({ + '.hidden_job' => { script: 'test' }, + 'normal_job' => { script: 'test' } + }) + end + + let(:config_processor) { GitlabCiYamlProcessor.new(config) } + + subject { config_processor.builds_for_stage_and_ref("test", "master") } + + it "doesn't create jobs that starts with dot" do + expect(subject.size).to eq(1) + expect(subject.first).to eq({ + except: nil, + stage: "test", + stage_idx: 1, + name: :normal_job, + only: nil, + commands: "\ntest", + tag_list: [], + options: {}, + when: "on_success", + allow_failure: false + }) + end + end + describe "Error handling" do it "fails to parse YAML" do expect{GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError) -- cgit v1.2.1 From d300ecf8d9e886ee7cff9b883bfcdbdb1e49769b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 11 Mar 2016 13:43:57 +0100 Subject: Allow to pass name of created artifacts archive in `.gitlab-ci.yml` --- CHANGELOG | 1 + doc/ci/yaml/README.md | 61 ++++++++++++++++++++++++++++ lib/ci/gitlab_ci_yaml_processor.rb | 4 ++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 10 ++++- 4 files changed, 75 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 66675e39427..c171d662b8d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.6.0 (unreleased) - Rewrite logo to simplify SVG code (Sean Lang) - Allow to use YAML anchors when parsing the `.gitlab-ci.yml` (Pascal Bach) - Ignore jobs that start with `.` (hidden jobs) + - Allow to pass name of created artifacts archive in `.gitlab-ci.yml` - Add support for cross-project label references - Update documentation to reflect Guest role not being enforced on internal projects - Allow search for logged out users diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index ec57ac5789e..9a1f86cec45 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -453,6 +453,67 @@ release-job: The artifacts will be sent to GitLab after a successful build and will be available for download in the GitLab UI. +#### artifacts:name + +_**Note:** Introduced in GitLab 8.6 and GitLab Runner v1.1.0._ + +The `name` directive allows you to define the name of created artifacts archive. + +Currently the `artifacts` is used. +It may be useful when you will want to download the archive from GitLab. +You could possible have the unique name of every archive. + +The `artifacts:name` variable can use any of the [predefined variables](../variables/README.md). + +--- + +**Example configurations** + +To create a archive with a name of current build: + +```yaml +job: + artifacts: + name: "$CI_BUILD_NAME" +``` + +To create a archive with a name of current branch or tag: + +```yaml +job: + artifacts: + name: "$CI_BUILD_REF_NAME" + untracked: true +``` + +To create a archive with a name of current branch or tag: + +```yaml +job: + artifacts: + name: "${CI_BUILD_NAME}_${CI_BUILD_REF_NAME}" + untracked: true +``` + +To create a archive with a name of stage and branch name: + +```yaml +job: + artifacts: + name: "${CI_BUILD_STAGE}_${CI_BUILD_REF_NAME}" + untracked: true +``` + +If you use **Windows Batch** to run your shell scripts you need to replace +`$` with `%`: + +```yaml +job: + artifacts: + name: "%CI_BUILD_STAGE%_%CI_BUILD_REF_NAME%" + untracked: true +``` + ### cache _**Note:** Introduced in GitLab Runner v0.7.0._ diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 8ece73eec0e..ce3d0138268 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -218,6 +218,10 @@ module Ci end def validate_job_artifacts!(name, job) + if job[:artifacts][:name] && !validate_string(job[:artifacts][:name]) + raise ValidationError, "#{name} job: artifacts:name parameter should be a string" + end + if job[:artifacts][:untracked] && !validate_boolean(job[:artifacts][:untracked]) raise ValidationError, "#{name} job: artifacts:untracked parameter should be an boolean" end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 665a65fe352..44d2b9eb1f7 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -397,7 +397,7 @@ module Ci services: ["mysql"], before_script: ["pwd"], rspec: { - artifacts: { paths: ["logs/", "binaries/"], untracked: true }, + artifacts: { paths: ["logs/", "binaries/"], untracked: true, name: "custom_name" }, script: "rspec" } }) @@ -417,6 +417,7 @@ module Ci image: "ruby:2.1", services: ["mysql"], artifacts: { + name: "custom_name", paths: ["logs/", "binaries/"], untracked: true } @@ -619,6 +620,13 @@ module Ci end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always") end + it "returns errors if job artifacts:name is not an a string" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { name: 1 } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:name parameter should be a string") + end + it "returns errors if job artifacts:untracked is not an array of strings" do config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } }) expect do -- cgit v1.2.1 From 9a271d80128451eecc9a301d5e9924c09740be07 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 11 Mar 2016 14:15:13 +0100 Subject: Allow to define on which builds the current one depends on --- CHANGELOG | 1 + doc/ci/yaml/README.md | 55 ++++++++++++++++++++++++++++ lib/ci/gitlab_ci_yaml_processor.rb | 21 ++++++++++- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 45 +++++++++++++++++++++++ 4 files changed, 121 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index c171d662b8d..fa634cc20db 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.6.0 (unreleased) - Add support for cross-project label references - Update documentation to reflect Guest role not being enforced on internal projects - Allow search for logged out users + - Allow to define on which builds the current one depends on - Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio) - Don't show Issues/MRs from archived projects in Groups view - Increase the notes polling timeout over time (Roberto Dip) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 9a1f86cec45..fb62ed25d64 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -241,6 +241,7 @@ job_name: | tags | no | Defines a list of tags which are used to select runner | | allow_failure | no | Allow build to fail. Failed build doesn't contribute to commit status | | when | no | Define when to run build. Can be `on_success`, `on_failure` or `always` | +| dependencies | no | Define a builds that this build depends on | | artifacts | no | Define list build artifacts | | cache | no | Define list of files that should be cached between subsequent runs | @@ -514,6 +515,60 @@ job: untracked: true ``` +### dependencies + +_**Note:** Introduced in GitLab 8.6 and GitLab Runner v1.1.1._ + +This feature should be used with `artifacts` and allows to define artifacts passing between different builds. + +`artifacts` from previous stages are passed by default. + +To use a feature define `dependencies` in context of the build and pass +a list of all previous builds from which the artifacts should be downloaded. +You can only define a builds from stages that are executed before this one. +Error will be shown if you define builds from current stage or next stages. + +How to use artifacts passing between stages: + +``` +build:osx: + stage: build + script: ... + artifacts: + paths: + - binaries/ + +build:linux: + stage: build + script: ... + artifacts: + paths: + - binaries/ + +test:osx: + stage: test + script: ... + dependencies: + - build:osx + +test:linux: + stage: test + script: ... + dependencies: + - build:linux + +deploy: + stage: deploy + script: ... +``` + +The above will create a build artifacts for two jobs: `build:osx` and `build:linux`. +When executing the `test:osx` the artifacts for `build:osx` will be downloaded and extracted in context of the build. +The same happens for `test:linux` and artifacts from `build:linux`. + +The job `deploy` will download artifacts from all previous builds. +However, only the `build:osx` and `build:linux` exports artifacts so only these will be downloaded. + ### cache _**Note:** Introduced in GitLab Runner v0.7.0._ diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index ce3d0138268..04b58cf1cd3 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -5,7 +5,9 @@ module Ci DEFAULT_STAGES = %w(build test deploy) DEFAULT_STAGE = 'test' ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables, :cache] - ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when, :artifacts, :cache] + ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, + :allow_failure, :type, :stage, :when, :artifacts, :cache, + :dependencies] attr_reader :before_script, :image, :services, :variables, :path, :cache @@ -145,6 +147,7 @@ module Ci validate_job_stage!(name, job) if job[:stage] validate_job_cache!(name, job) if job[:cache] validate_job_artifacts!(name, job) if job[:artifacts] + validate_job_dependencies!(name, job) if job[:dependencies] end private @@ -231,6 +234,22 @@ module Ci end end + def validate_job_dependencies!(name, job) + if !validate_array_of_strings(job[:dependencies]) + raise ValidationError, "#{name} job: dependencies parameter should be an array of strings" + end + + stage_index = stages.index(job[:stage]) + + job[:dependencies].each do |dependency| + raise ValidationError, "#{name} job: undefined dependency: #{dependency}" unless @jobs[dependency] + + unless stages.index(@jobs[dependency][:stage]) < stage_index + raise ValidationError, "#{name} job: dependency #{dependency} is not defined in prior stages" + end + end + end + def validate_array_of_strings(values) values.is_a?(Array) && values.all? { |value| validate_string(value) } end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 44d2b9eb1f7..fe5096989b2 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -428,6 +428,44 @@ module Ci end end + describe "Dependencies" do + let(:config) do + { + build1: { stage: 'build', script: 'test' }, + build2: { stage: 'build', script: 'test' }, + test1: { stage: 'test', script: 'test', dependencies: dependencies }, + test2: { stage: 'test', script: 'test' }, + deploy: { stage: 'test', script: 'test' } + } + end + + subject { GitlabCiYamlProcessor.new(YAML.dump(config)) } + + context 'no dependencies' do + let(:dependencies) { } + + it { expect { subject }.to_not raise_error } + end + + context 'dependencies to builds' do + let(:dependencies) { [:build1, :build2] } + + it { expect { subject }.to_not raise_error } + end + + context 'undefined dependency' do + let(:dependencies) { [:undefined] } + + it { expect { subject }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'test1 job: undefined dependency: undefined') } + end + + context 'dependencies to deploy' do + let(:dependencies) { [:deploy] } + + it { expect { subject }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'test1 job: dependency deploy is not defined in prior stages') } + end + end + describe "Hidden jobs" do let(:config) do YAML.dump({ @@ -682,6 +720,13 @@ module Ci GitlabCiYamlProcessor.new(config) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:paths parameter should be an array of strings") end + + it "returns errors if job dependencies is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", dependencies: "string" } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: dependencies parameter should be an array of strings") + end end end end -- cgit v1.2.1 From 6c69868cfc595a4d64ba8ae3a530a1cf49d2e1f3 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 11 Mar 2016 15:29:29 +0000 Subject: Improved search results filter dropdown --- app/views/search/_filter.html.haml | 46 +++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml index ec478a5963d..4ef544136a8 100644 --- a/app/views/search/_filter.html.haml +++ b/app/views/search/_filter.html.haml @@ -6,14 +6,21 @@ - else Any %b.caret - %ul.dropdown-menu - %li - = link_to search_filter_path(group_id: nil) do - Any - - current_user.authorized_groups.sort_by(&:name).each do |group| - %li - = link_to search_filter_path(group_id: group.id, project_id: nil) do - = group.name + .dropdown-menu.dropdown-select.dropdown-menu-selectable + .dropdown-title + %span Filter results by group + %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} + = icon('times') + .dropdown-content + %ul + %li + = link_to search_filter_path(group_id: nil), class: ("is-active" if !params[:group_id].present?) do + Any + %li.divider + - current_user.authorized_groups.sort_by(&:name).each do |group| + %li + = link_to search_filter_path(group_id: group.id, project_id: nil), class: ("is-active" if params[:group_id] == group.id.to_s) do + = group.name .dropdown.inline.prepend-left-10.project-filter %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} @@ -23,11 +30,18 @@ - else Any %b.caret - %ul.dropdown-menu - %li - = link_to search_filter_path(project_id: nil) do - Any - - current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project| - %li - = link_to search_filter_path(project_id: project.id, group_id: nil) do - = project.name_with_namespace + .dropdown-menu.dropdown-select.dropdown-menu-selectable + .dropdown-title + %span Filter results by project + %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} + = icon('times') + .dropdown-content + %ul + %li + = link_to search_filter_path(project_id: nil), class: ("is-active" if !params[:project_id].present?) do + Any + %li.divider + - current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project| + %li + = link_to search_filter_path(project_id: project.id, group_id: nil), class: ("is-active" if params[:project_id] == project.id.to_s) do + = project.name_with_namespace -- cgit v1.2.1 From 9f8661987db44f6c1d2cfd75c889344fc49bd6bc Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 11 Mar 2016 16:30:58 +0000 Subject: Increased dropdown max height --- app/assets/stylesheets/framework/dropdowns.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 009d621fc74..5b647fc6176 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -294,7 +294,7 @@ } .dropdown-content { - max-height: 200px; + max-height: 215px; overflow-y: scroll; } -- cgit v1.2.1 From ea5f4cae53eb571b250fcbaa3649cefe3083a636 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 11 Mar 2016 17:47:05 +0100 Subject: Bring ProjectGroupLink model and migrations from EE Signed-off-by: Dmitriy Zaporozhets --- app/models/group.rb | 2 ++ app/models/project.rb | 2 ++ app/models/project_group_link.rb | 36 ++++++++++++++++++++++ .../20130711063759_create_project_group_links.rb | 10 ++++++ ...30820102832_add_access_to_project_group_link.rb | 5 +++ db/schema.rb | 14 +++++++-- spec/factories/project_group_links.rb | 6 ++++ spec/models/project_group_link_spec.rb | 17 ++++++++++ 8 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 app/models/project_group_link.rb create mode 100644 db/migrate/20130711063759_create_project_group_links.rb create mode 100644 db/migrate/20130820102832_add_access_to_project_group_link.rb create mode 100644 spec/factories/project_group_links.rb create mode 100644 spec/models/project_group_link_spec.rb diff --git a/app/models/group.rb b/app/models/group.rb index 76042b3e3fd..e307624e965 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -23,6 +23,8 @@ class Group < Namespace has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' alias_method :members, :group_members has_many :users, through: :group_members + has_many :project_group_links, dependent: :destroy + has_many :shared_projects, through: :project_group_links, source: :project validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validates :avatar, file_size: { maximum: 200.kilobytes.to_i } diff --git a/app/models/project.rb b/app/models/project.rb index 65829bec77a..2232637f1f1 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -151,6 +151,8 @@ class Project < ActiveRecord::Base has_many :releases, dependent: :destroy has_many :lfs_objects_projects, dependent: :destroy has_many :lfs_objects, through: :lfs_objects_projects + has_many :project_group_links, dependent: :destroy + has_many :invited_groups, through: :project_group_links, source: :group has_many :todos, dependent: :destroy has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb new file mode 100644 index 00000000000..e52a6bd7c84 --- /dev/null +++ b/app/models/project_group_link.rb @@ -0,0 +1,36 @@ +class ProjectGroupLink < ActiveRecord::Base + GUEST = 10 + REPORTER = 20 + DEVELOPER = 30 + MASTER = 40 + + belongs_to :project + belongs_to :group + + validates :project_id, presence: true + validates :group_id, presence: true + validates :group_id, uniqueness: { scope: [:project_id], message: "already shared with this group" } + validates :group_access, presence: true + validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true + validate :different_group + + def self.access_options + Gitlab::Access.options + end + + def self.default_access + DEVELOPER + end + + def human_access + self.class.access_options.key(self.group_access) + end + + private + + def different_group + if self.group && self.project && self.project.group == self.group + errors.add(:base, "Project cannot be shared with the project it is in.") + end + end +end diff --git a/db/migrate/20130711063759_create_project_group_links.rb b/db/migrate/20130711063759_create_project_group_links.rb new file mode 100644 index 00000000000..395083f2a03 --- /dev/null +++ b/db/migrate/20130711063759_create_project_group_links.rb @@ -0,0 +1,10 @@ +class CreateProjectGroupLinks < ActiveRecord::Migration + def change + create_table :project_group_links do |t| + t.integer :project_id, null: false + t.integer :group_id, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20130820102832_add_access_to_project_group_link.rb b/db/migrate/20130820102832_add_access_to_project_group_link.rb new file mode 100644 index 00000000000..00e3947a6bb --- /dev/null +++ b/db/migrate/20130820102832_add_access_to_project_group_link.rb @@ -0,0 +1,5 @@ +class AddAccessToProjectGroupLink < ActiveRecord::Migration + def change + add_column :project_group_links, :group_access, :integer, null: false, default: ProjectGroupLink.default_access + end +end diff --git a/db/schema.rb b/db/schema.rb index a74b86d8e2f..89e8c9e67b0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -656,6 +656,14 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree + create_table "project_group_links", force: :cascade do |t| + t.integer "project_id", null: false + t.integer "group_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.integer "group_access", default: 30, null: false + end + create_table "project_import_data", force: :cascade do |t| t.integer "project_id" t.text "data" @@ -749,9 +757,9 @@ ActiveRecord::Schema.define(version: 20160309140734) do t.string "type" t.string "title" t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "active", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "active", default: false, null: false t.text "properties" t.boolean "template", default: false t.boolean "push_events", default: true diff --git a/spec/factories/project_group_links.rb b/spec/factories/project_group_links.rb new file mode 100644 index 00000000000..e73cc05f9d7 --- /dev/null +++ b/spec/factories/project_group_links.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :project_group_link do + project + group + end +end diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb new file mode 100644 index 00000000000..2fa6715fcaf --- /dev/null +++ b/spec/models/project_group_link_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe ProjectGroupLink do + describe "Associations" do + it { should belong_to(:group) } + it { should belong_to(:project) } + end + + describe "Validation" do + let!(:project_group_link) { create(:project_group_link) } + + it { should validate_presence_of(:project_id) } + it { should validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) } + it { should validate_presence_of(:group_id) } + it { should validate_presence_of(:group_access) } + end +end -- cgit v1.2.1 From 95e73f87a99a0869d503451ab93a36bdc2530445 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Fri, 11 Mar 2016 11:47:34 -0500 Subject: Doc syntax fixes [ci skip] --- doc/integration/saml.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/integration/saml.md b/doc/integration/saml.md index b61b7cb9678..1c3dc707f6d 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -133,15 +133,15 @@ will be returned to GitLab and will be signed in. ## Customization -### attribute_statements: +### `attribute_statements` >**Note:** This setting is only available on GitLab 8.6 and above. This setting should only be used to map attributes that are part of the OmniAuth info hash schema. -Used to map Attribute Names in a SAMLResponse to entries in the OmniAuth -[info hash](https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema#schema-10-and-later). +`attribute_statements` is used to map Attribute Names in a SAMLResponse to entries +in the OmniAuth [info hash](https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema#schema-10-and-later). For example, if your SAMLResponse contains an Attribute called 'EmailAddress', specify `{ email: ['EmailAddress'] }` to map the Attribute to the @@ -149,7 +149,7 @@ corresponding key in the info hash. URI-named Attributes are also supported, e. `{ email: ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] }`. This setting allows you tell GitLab where to look for certain attributes required -to create an account. Like mentioned above, if your IdP send the user's email +to create an account. Like mentioned above, if your IdP sends the user's email address as `EmailAddress` instead of `email`, let GitLab know by setting it on your configuration: @@ -164,12 +164,12 @@ args: { } ``` -### allowed_clock_drift: +### `allowed_clock_drift` The clock of the Identity Provider may drift slightly ahead of your system clocks. -To allow for a small amount of clock drift you can use this argument within your -settings. Its value must be given in a number (and/or fraction) of seconds. The -value given is added to the current time at which the response is validated. +To allow for a small amount of clock drift you can use `allowed_clock_drift` within +your settings. Its value must be given in a number (and/or fraction) of seconds. +The value given is added to the current time at which the response is validated. ```yaml args: { -- cgit v1.2.1 From 25c2349200153db914ab498bb4b14099a79db01a Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 11 Mar 2016 17:59:51 +0100 Subject: Added 8.6 upgrade guide for PostgreSQL users --- doc/update/8.5-to-8.6.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/doc/update/8.5-to-8.6.md b/doc/update/8.5-to-8.6.md index e42f691cd32..a8167b2cf0b 100644 --- a/doc/update/8.5-to-8.6.md +++ b/doc/update/8.5-to-8.6.md @@ -105,6 +105,23 @@ via [/etc/default/gitlab]. [Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache [/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-6-stable/lib/support/init.d/gitlab.default.example#L37 +### 8. Updates for PostgreSQL Users + +Starting with 8.6 users using GitLab in combination with PostgreSQL are required +to have the "pg_trgm" extension enabled for all GitLab databases. If you're +using GitLab's Omnibus packages there's nothing you'll need to do manually as +this extension is enabled automatically. Users who install GitLab without using +Omnibus (e.g. by building from source) have to enable this extension manually. +To enable this extension run the following SQL command as a PostgreSQL super +user for _every_ GitLab database: + + CREATE EXTENSION IF NOT EXISTS pg_trgm; + +Certain operating systems might require the installation of extra packages for +this extension to be available. For example, users using Ubuntu will have to +install the "postgresql-contrib" package in order for this extension to be +available. + #### Init script Ensure you're still up-to-date with the latest init script changes: -- cgit v1.2.1 From 746ac56b6f92991bc15d3cd787094b9555825ac3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 11 Mar 2016 18:37:46 +0100 Subject: Add functionality to setup share of project with group via project settings Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/dispatcher.js.coffee | 2 ++ app/controllers/projects/group_links_controller.rb | 23 ++++++++++++ app/models/project.rb | 5 +++ app/views/layouts/nav/_project_settings.html.haml | 6 ++++ app/views/projects/group_links/index.html.haml | 41 ++++++++++++++++++++++ config/routes.rb | 2 ++ 6 files changed, 79 insertions(+) create mode 100644 app/controllers/projects/group_links_controller.rb create mode 100644 app/views/projects/group_links/index.html.haml diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 54b28f2dd8d..e964bba1325 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -103,6 +103,8 @@ class Dispatcher new ProjectFork() when 'projects:artifacts:browse' new BuildArtifacts() + when 'projects:group_links:index' + new GroupsSelect() switch path.first() when 'admin' diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb new file mode 100644 index 00000000000..4159e53bfa9 --- /dev/null +++ b/app/controllers/projects/group_links_controller.rb @@ -0,0 +1,23 @@ +class Projects::GroupLinksController < Projects::ApplicationController + layout 'project_settings' + before_action :authorize_admin_project! + + def index + @group_links = project.project_group_links.all + end + + def create + link = project.project_group_links.new + link.group_id = params[:link_group_id] + link.group_access = params[:link_group_access] + link.save + + redirect_to namespace_project_group_links_path(project.namespace, project) + end + + def destroy + project.project_group_links.find(params[:id]).destroy + + redirect_to namespace_project_group_links_path(project.namespace, project) + end +end diff --git a/app/models/project.rb b/app/models/project.rb index 2232637f1f1..56865459724 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -880,6 +880,11 @@ class Project < ActiveRecord::Base jira_tracker? && jira_service.active end + def allowed_to_share_with_group? + # TODO: replace with logic + true + end + def ci_commit(sha) ci_commits.find_by(sha: sha) end diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index 3359716202f..dc3050f02e5 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -13,6 +13,12 @@ = icon('pencil-square-o fw') %span Project Settings + - if @project.allowed_to_share_with_group? + = nav_link(controller: :group_links) do + = link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do + = icon('share-square-o fw') + %span + Groups = nav_link(controller: :deploy_keys) do = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do = icon('key fw') diff --git a/app/views/projects/group_links/index.html.haml b/app/views/projects/group_links/index.html.haml new file mode 100644 index 00000000000..13f5fc141fa --- /dev/null +++ b/app/views/projects/group_links/index.html.haml @@ -0,0 +1,41 @@ +- page_title "Groups" +%h3.page_title Share project with other groups +%p.light + Projects can be stored in only one group at once. However you can share a project with other groups here. +%hr +- if @group_links.present? + .enabled-groups.panel.panel-default + .panel-heading + Already shared with + %ul.well-list + - @group_links.each do |group_link| + - group = group_link.group + %li + .pull-right + = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: 'btn btn-sm' do + %i.icon-remove + disable sharing + = link_to group do + %strong + %i.icon-folder-open + = group.name + %br + .light up to #{group_link.human_access} + + +.available-groups + %h4 + Can be shared with + %div + = form_tag namespace_project_group_links_path(@project.namespace, @project), method: :post, class: 'form-horizontal' do + .form-group + = label_tag :link_group_id, 'Group', class: 'control-label' + .col-sm-10 + = groups_select_tag(:link_group_id, skip_group: @project.group.try(:path)) + .form-group + = label_tag :link_group_access, 'Max access level', class: 'control-label' + .col-sm-10 + = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control" + .form-actions + = submit_tag "Share", class: "btn btn-create" + diff --git a/config/routes.rb b/config/routes.rb index a918b5bd3f0..a0e0cc87f51 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -701,6 +701,8 @@ Rails.application.routes.draw do end end + resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ } + resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do member do delete :delete_attachment -- cgit v1.2.1 From f8163c81e7c765ea654ea81818eb3f8a9da7648e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 11 Mar 2016 18:42:16 +0100 Subject: Show shared projects on group page Signed-off-by: Dmitriy Zaporozhets --- app/controllers/groups_controller.rb | 2 ++ app/views/groups/_shared_projects.html.haml | 18 ++++++++++++++++++ app/views/groups/show.html.haml | 7 +++++++ 3 files changed, 27 insertions(+) create mode 100644 app/views/groups/_shared_projects.html.haml diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index f05c29e9974..bd6b44bdbbc 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -46,6 +46,8 @@ class GroupsController < Groups::ApplicationController @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank? + @shared_projects = @group.shared_projects + respond_to do |format| format.html diff --git a/app/views/groups/_shared_projects.html.haml b/app/views/groups/_shared_projects.html.haml new file mode 100644 index 00000000000..d707ad4272d --- /dev/null +++ b/app/views/groups/_shared_projects.html.haml @@ -0,0 +1,18 @@ +- if projects.present? + .panel.panel-default + .panel-heading + Projects shared with + %strong #{@group.name} + (#{projects.count}) + %ul.well-list + - projects.each do |project| + %li.project-row + = link_to namespace_project_path(project.namespace, project), class: dom_class(project) do + %span.namespace-name + - if project.namespace + = project.namespace.human_name + \/ + %span.project-name + = truncate(project.name, length: 25) + %span.arrow + %i.icon-angle-right diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 6148d8cb3d2..c64c986b5a4 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -35,6 +35,10 @@ %li = link_to "#projects", 'data-toggle' => 'tab' do Projects + - if @shared_projects.present? + %li + = link_to "#shared", 'data-toggle' => 'tab' do + Shared Projects - if can?(current_user, :read_group, @group) %div{ class: container_class } @@ -52,6 +56,9 @@ .tab-pane#projects = render "projects", projects: @projects + .tab-pane#shared + = render "shared_projects", projects: @shared_projects + - else %p.nav-links.no-top No projects to show -- cgit v1.2.1 From 8901336c78a9075a6a64205500e6019c40fd632f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 11 Mar 2016 18:46:01 +0100 Subject: Allow users to access project shared with their group Signed-off-by: Dmitriy Zaporozhets --- app/models/project_team.rb | 52 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 9629c7e1bb9..70a8bbaba65 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -160,7 +160,27 @@ class ProjectTeam end end - access.max + if project.invited_groups.any? && project.allowed_to_share_with_group? + access << max_invited_level(user_id) + end + + access.compact.max + end + + + def max_invited_level(user_id) + project.project_group_links.map do |group_link| + invited_group = group_link.group + access = invited_group.group_members.find_by(user_id: user_id).try(:access_field) + + # If group member has higher access level we should restrict it + # to max allowed access level + if access && access > group_link.group_access + access = group_link.group_access + end + + access + end.compact.max end private @@ -168,6 +188,35 @@ class ProjectTeam def fetch_members(level = nil) project_members = project.project_members group_members = group ? group.group_members : [] + invited_members = [] + + if project.invited_groups.any? && project.allowed_to_share_with_group? + project.project_group_links.each do |group_link| + invited_group = group_link.group + im = invited_group.group_members + + if level + int_level = GroupMember.access_level_roles[level.to_s.singularize.titleize] + + # Skip group members if we ask for masters + # but max group access is developers + next if int_level > group_link.group_access + + # If we ask for developers and max + # group access is developers we need to provide + # both group master, developers as devs + if int_level == group_link.group_access + im.where("access_level >= ?)", group_link.group_access) + else + im.send(level) + end + end + + invited_members << im + end + + invited_members = invited_members.flatten.compact + end if level project_members = project_members.send(level) @@ -175,6 +224,7 @@ class ProjectTeam end user_ids = project_members.pluck(:user_id) + user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) -- cgit v1.2.1 From 068fd5de8a45ef0814c500df10d3b9d39496fcd9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 11 Mar 2016 18:55:17 +0100 Subject: Add finders logic and tests for shared projects feature Signed-off-by: Dmitriy Zaporozhets --- app/finders/projects_finder.rb | 3 +- app/models/user.rb | 3 +- app/views/admin/groups/show.html.haml | 16 +++++++++ features/project/group_links.feature | 16 +++++++++ features/steps/project/project_group_links.rb | 50 +++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 features/project/group_links.feature create mode 100644 features/steps/project/project_group_links.rb diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 3b4e0362e04..93fc7c4b01c 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -43,7 +43,8 @@ class ProjectsFinder if current_user [ group_projects_for_user(current_user, group), - group.projects.public_and_internal_only + group.projects.public_and_internal_only, + group.shared_projects.visible_to_user(current_user) ] else [group.projects.public_only] diff --git a/app/models/user.rb b/app/models/user.rb index 505a547d8ec..e9d1f479433 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -818,7 +818,8 @@ class User < ActiveRecord::Base def projects_union Gitlab::SQL::Union.new([personal_projects.select(:id), groups_projects.select(:id), - projects.select(:id)]) + projects.select(:id), + groups.joins(:shared_projects).select(:project_id)]) end def ci_projects_union diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index f7fd156b84a..264fa1bf0cd 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -50,6 +50,22 @@ .panel-footer = paginate @projects, param_name: 'projects_page', theme: 'gitlab' + - if @group.shared_projects.any? + .panel.panel-default + .panel-heading + Projects shared with #{@group.name} + %span.badge + #{@group.shared_projects.count} + %ul.well-list + - @group.shared_projects.sort_by(&:name).each do |project| + %li + %strong + = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] + %span.label.label-gray + = repository_size(project) + %span.pull-right.light + %span.monospace= project.path_with_namespace + ".git" + .col-md-6 - if can?(current_user, :admin_group_member, @group) .panel.panel-default diff --git a/features/project/group_links.feature b/features/project/group_links.feature new file mode 100644 index 00000000000..2657c4487ad --- /dev/null +++ b/features/project/group_links.feature @@ -0,0 +1,16 @@ +Feature: Project Group Links + Background: + Given I sign in as a user + And I own project "Shop" + And project "Shop" is shared with group "Ops" + And project "Shop" is not shared with group "Market" + And I visit project group links page + + Scenario: I should see list of groups + Then I should see project already shared with group "Ops" + Then I should see project is not shared with group "Market" + + @javascript + Scenario: I share project with group + When I select group "Market" for share + Then I should see project is shared with group "Market" diff --git a/features/steps/project/project_group_links.rb b/features/steps/project/project_group_links.rb new file mode 100644 index 00000000000..739a85e5fa4 --- /dev/null +++ b/features/steps/project/project_group_links.rb @@ -0,0 +1,50 @@ +class Spinach::Features::ProjectGroupLinks < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + include Select2Helper + + step 'I should see project already shared with group "Ops"' do + page.within '.enabled-groups' do + expect(page).to have_content "Ops" + end + end + + step 'I should see project is not shared with group "Market"' do + page.within '.enabled-groups' do + expect(page).not_to have_content "Market" + end + end + + step 'I select group "Market" for share' do + group = Group.find_by(path: 'market') + select2(group.id, from: "#link_group_id") + select "Master", from: 'link_group_access' + click_button "Share" + end + + step 'I should see project is shared with group "Market"' do + page.within '.enabled-groups' do + expect(page).to have_content "Market" + end + end + + step 'project "Shop" is shared with group "Ops"' do + group = create(:group, name: 'Ops') + share_link = project.project_group_links.new(group_access: Gitlab::Access::MASTER) + share_link.group_id = group.id + share_link.save! + end + + step 'project "Shop" is not shared with group "Market"' do + create(:group, name: 'Market', path: 'market') + end + + step 'I visit project group links page' do + visit namespace_project_group_links_path(project.namespace, project) + end + + def project + @project ||= Project.find_by_name "Shop" + end +end -- cgit v1.2.1 From 9a95b15552fa8920800274324aa65900360e8038 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 11 Mar 2016 19:03:19 +0100 Subject: Add share project from group lock Signed-off-by: Dmitriy Zaporozhets --- app/controllers/groups_controller.rb | 2 +- app/models/project.rb | 3 +-- app/views/groups/edit.html.haml | 9 +++++++++ db/migrate/20150930110012_add_group_share_lock.rb | 5 +++++ db/schema.rb | 7 ++++--- 5 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20150930110012_add_group_share_lock.rb diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index bd6b44bdbbc..e8d9d9fb344 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -133,7 +133,7 @@ class GroupsController < Groups::ApplicationController end def group_params - params.require(:group).permit(:name, :description, :path, :avatar, :public) + params.require(:group).permit(:name, :description, :path, :avatar, :public, :share_with_group_lock) end def load_events diff --git a/app/models/project.rb b/app/models/project.rb index 56865459724..40f8c4c83da 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -881,8 +881,7 @@ class Project < ActiveRecord::Base end def allowed_to_share_with_group? - # TODO: replace with logic - true + !namespace.share_with_group_lock end def ci_commit(sha) diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 3430f56a9c9..83936d39b16 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -23,6 +23,15 @@ %hr = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" + .form-group + %hr + = f.label :share_with_group_lock, class: 'control-label' do + Share with group lock + .col-sm-10 + .checkbox + = f.check_box :share_with_group_lock + %span.descr Prevent sharing a project with another group within this group + .form-actions = f.submit 'Save group', class: "btn btn-save" diff --git a/db/migrate/20150930110012_add_group_share_lock.rb b/db/migrate/20150930110012_add_group_share_lock.rb new file mode 100644 index 00000000000..78d1a4538f2 --- /dev/null +++ b/db/migrate/20150930110012_add_group_share_lock.rb @@ -0,0 +1,5 @@ +class AddGroupShareLock < ActiveRecord::Migration + def change + add_column :namespaces, :share_with_group_lock, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 89e8c9e67b0..20dbb4f5a3e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -568,14 +568,15 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "milestones", ["title"], name: "index_milestones_on_title", using: :btree create_table "namespaces", force: :cascade do |t| - t.string "name", null: false - t.string "path", null: false + t.string "name", null: false + t.string "path", null: false t.integer "owner_id" t.datetime "created_at" t.datetime "updated_at" t.string "type" - t.string "description", default: "", null: false + t.string "description", default: "", null: false t.string "avatar" + t.boolean "share_with_group_lock", default: false end add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree -- cgit v1.2.1 From 5461170fb39d62d8b780d4bd02eaddc8b4918686 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 11 Mar 2016 21:26:18 +0200 Subject: Fix incorrect gitlab.rb variable in CI docs [ci skip] --- doc/ci/enable_or_disable_ci.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/enable_or_disable_ci.md b/doc/ci/enable_or_disable_ci.md index 9bd2f5aff22..c10f82054e2 100644 --- a/doc/ci/enable_or_disable_ci.md +++ b/doc/ci/enable_or_disable_ci.md @@ -64,7 +64,7 @@ Save the file and restart GitLab: `sudo service gitlab restart`. For Omnibus installations, edit `/etc/gitlab/gitlab.rb` and add the line: ``` -gitlab-rails['gitlab_default_projects_features_builds'] = false +gitlab_rails['gitlab_default_projects_features_builds'] = false ``` Save the file and reconfigure GitLab: `sudo gitlab-ctl reconfigure`. -- cgit v1.2.1 From d24ee2a2065692bd4edff59af55353de3c1c49e6 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 26 Feb 2016 17:14:44 +0100 Subject: Added trigram indexes for various searched columns This allows the LIKE condition to use an index. Without a GIN + trigram index LIKE queries using a wildcard at the start _won't_ use an index and instead perform a sequence scan. --- ...0226114608_add_trigram_indexes_for_searching.rb | 50 ++++++++++++++++++++++ db/schema.rb | 21 +++++++++ 2 files changed, 71 insertions(+) create mode 100644 db/migrate/20160226114608_add_trigram_indexes_for_searching.rb diff --git a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb new file mode 100644 index 00000000000..fca5ac01a08 --- /dev/null +++ b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb @@ -0,0 +1,50 @@ +class AddTrigramIndexesForSearching < ActiveRecord::Migration + disable_ddl_transaction! + + def up + return unless Gitlab::Database.postgresql? + + unless trigrams_enabled? + raise 'You must enable the pg_trgm extension as a PostgreSQL super user' + end + + # trigram indexes are case-insensitive so we can just index the column + # instead of indexing lower(column) + to_index.each do |table, columns| + columns.each do |column| + execute "CREATE INDEX CONCURRENTLY index_#{table}_on_#{column}_trigram ON #{table} USING gin(#{column} gin_trgm_ops);" + end + end + end + + def down + return unless Gitlab::Database.postgresql? + + to_index.each do |table, columns| + columns.each do |column| + remove_index table, name: "index_#{table}_on_#{column}_trigram" + end + end + end + + def trigrams_enabled? + res = execute("SELECT true AS enabled FROM pg_available_extensions WHERE name = 'pg_trgm' AND installed_version IS NOT NULL;") + row = res.first + + row && row['enabled'] == 't' ? true : false + end + + def to_index + { + ci_runners: [:token, :description], + issues: [:title, :description], + merge_requests: [:title, :description], + milestones: [:title, :description], + namespaces: [:name, :path], + notes: [:note], + projects: [:name, :path, :description], + snippets: [:title, :file_name], + users: [:username, :name, :email] + } + end +end diff --git a/db/schema.rb b/db/schema.rb index a74b86d8e2f..8f44ccebac8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -15,6 +15,7 @@ ActiveRecord::Schema.define(version: 20160309140734) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + enable_extension "pg_trgm" create_table "abuse_reports", force: :cascade do |t| t.integer "reporter_id" @@ -258,6 +259,9 @@ ActiveRecord::Schema.define(version: 20160309140734) do t.string "architecture" end + add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin + add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin + create_table "ci_services", force: :cascade do |t| t.string "type" t.string "title" @@ -417,11 +421,13 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree add_index "issues", ["created_at", "id"], name: "index_issues_on_created_at_and_id", using: :btree add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree + add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree add_index "issues", ["state"], name: "index_issues_on_state", using: :btree add_index "issues", ["title"], name: "index_issues_on_title", using: :btree + add_index "issues", ["title"], name: "index_issues_on_title_trigram", using: :gin create_table "keys", force: :cascade do |t| t.integer "user_id" @@ -543,12 +549,14 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree add_index "merge_requests", ["created_at", "id"], name: "index_merge_requests_on_created_at_and_id", using: :btree add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree + add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree add_index "merge_requests", ["source_project_id"], name: "index_merge_requests_on_source_project_id", using: :btree add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree + add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin create_table "milestones", force: :cascade do |t| t.string "title", null: false @@ -562,10 +570,12 @@ ActiveRecord::Schema.define(version: 20160309140734) do end add_index "milestones", ["created_at", "id"], name: "index_milestones_on_created_at_and_id", using: :btree + add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree add_index "milestones", ["title"], name: "index_milestones_on_title", using: :btree + add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin create_table "namespaces", force: :cascade do |t| t.string "name", null: false @@ -580,8 +590,10 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree + add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree + add_index "namespaces", ["path"], name: "index_namespaces_on_path_trigram", using: :gin add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree create_table "notes", force: :cascade do |t| @@ -607,6 +619,7 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree add_index "notes", ["is_award"], name: "index_notes_on_is_award", using: :btree add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree + add_index "notes", ["note"], name: "index_notes_on_note_trigram", using: :gin add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree @@ -705,9 +718,12 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree + add_index "projects", ["description"], name: "index_projects_on_description_trigram", using: :gin add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree + add_index "projects", ["name"], name: "index_projects_on_name_trigram", using: :gin add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree add_index "projects", ["path"], name: "index_projects_on_path", using: :btree + add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree @@ -785,7 +801,9 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree add_index "snippets", ["created_at", "id"], name: "index_snippets_on_created_at_and_id", using: :btree add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree + add_index "snippets", ["file_name"], name: "index_snippets_on_file_name_trigram", using: :gin add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree + add_index "snippets", ["title"], name: "index_snippets_on_title_trigram", using: :gin add_index "snippets", ["updated_at"], name: "index_snippets_on_updated_at", using: :btree add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree @@ -919,9 +937,12 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree + add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin add_index "users", ["name"], name: "index_users_on_name", using: :btree + add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["username"], name: "index_users_on_username", using: :btree + add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin create_table "users_star_projects", force: :cascade do |t| t.integer "project_id", null: false -- cgit v1.2.1 From 135659a75135b47563b349d13a9846b9b017af15 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 1 Mar 2016 12:02:06 +0100 Subject: Use ILIKE/LIKE + UNION in Project.search This chance is broken up in two steps: 1. Use ILIKE on PostgreSQL and LIKE on MySQL, instead of using "WHERE lower(x) LIKE lower(y)" as ILIKE is significantly faster than using lower(). In many cases the use of lower() will force a slow sequence scan. 2. Instead of using 1 query that searches both projects and namespaces using a JOIN we're using 2 separate queries that are UNION'd together. Using a JOIN would force a slow sequence scan, using a UNION avoids this. This method now uses Arel as Arel automatically uses ILIKE on PostgreSQL and LIKE on MySQL, removing the need to handle this manually. --- app/models/project.rb | 30 ++++++++++++++++++++------ spec/models/project_spec.rb | 51 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 65829bec77a..2f19ec9ba89 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -266,13 +266,31 @@ class Project < ActiveRecord::Base joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') end + # Searches for a list of projects based on the query given in `query`. + # + # On PostgreSQL this method uses "ILIKE" to perform a case-insensitive + # search. On MySQL a regular "LIKE" is used as it's already + # case-insensitive. + # + # query - The search query as a String. def search(query) - joins(:namespace). - where('LOWER(projects.name) LIKE :query OR - LOWER(projects.path) LIKE :query OR - LOWER(namespaces.name) LIKE :query OR - LOWER(projects.description) LIKE :query', - query: "%#{query.try(:downcase)}%") + ptable = Project.arel_table + ntable = Namespace.arel_table + pattern = "%#{query}%" + + projects = select(:id).where( + ptable[:path].matches(pattern). + or(ptable[:name].matches(pattern)). + or(ptable[:description].matches(pattern)) + ) + + namespaces = select(:id). + joins(:namespace). + where(ntable[:name].matches(pattern)) + + union = Gitlab::SQL::Union.new([projects, namespaces]) + + where("projects.id IN (#{union.to_sql})") end def search_by_visibility(level) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 2fa38a5d3d3..6627432aa83 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -582,7 +582,58 @@ describe Project, models: true do it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy } it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey } end + end + + describe '.search' do + let(:project) { create(:project, description: 'kitten mittens') } + + it 'returns projects with a matching name' do + expect(described_class.search(project.name)).to eq([project]) + end + it 'returns projects with a partially matching name' do + expect(described_class.search(project.name[0..2])).to eq([project]) + end + + it 'returns projects with a matching name regardless of the casing' do + expect(described_class.search(project.name.upcase)).to eq([project]) + end + + it 'returns projects with a matching description' do + expect(described_class.search(project.description)).to eq([project]) + end + + it 'returns projects with a partially matching description' do + expect(described_class.search('kitten')).to eq([project]) + end + + it 'returns projects with a matching description regardless of the casing' do + expect(described_class.search('KITTEN')).to eq([project]) + end + + it 'returns projects with a matching path' do + expect(described_class.search(project.path)).to eq([project]) + end + + it 'returns projects with a partially matching path' do + expect(described_class.search(project.path[0..2])).to eq([project]) + end + + it 'returns projects with a matching path regardless of the casing' do + expect(described_class.search(project.path.upcase)).to eq([project]) + end + + it 'returns projects with a matching namespace name' do + expect(described_class.search(project.namespace.name)).to eq([project]) + end + + it 'returns projects with a partially matching namespace name' do + expect(described_class.search(project.namespace.name[0..2])).to eq([project]) + end + + it 'returns projects with a matching namespace name regardless of the casing' do + expect(described_class.search(project.namespace.name.upcase)).to eq([project]) + end end describe '#rename_repo' do -- cgit v1.2.1 From db615d0a7992d5118c3e9e8914064eb26970666b Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 1 Mar 2016 12:16:15 +0100 Subject: Use ILIKE in Project.search_by_title Similar to the changes made to Project.search the method Project.search_by_title now also uses Arel so it can automatically use ILIKE/LIKE instead of the lower() function. --- app/models/project.rb | 5 ++++- spec/models/project_spec.rb | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 2f19ec9ba89..cb236bf41cd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -298,7 +298,10 @@ class Project < ActiveRecord::Base end def search_by_title(query) - non_archived.where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%") + pattern = "%#{query}%" + table = Project.arel_table + + non_archived.where(table[:name].matches(pattern)) end def find_with_namespace(id) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 6627432aa83..59c5ffa6b9c 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -698,4 +698,20 @@ describe Project, models: true do project.expire_caches_before_rename('foo') end end + + describe '.search_by_title' do + let(:project) { create(:project, name: 'kittens') } + + it 'returns projects with a matching name' do + expect(described_class.search_by_title(project.name)).to eq([project]) + end + + it 'returns projects with a partially matching name' do + expect(described_class.search_by_title('kitten')).to eq([project]) + end + + it 'returns projects with a matching name regardless of the casing' do + expect(described_class.search_by_title('KITTENS')).to eq([project]) + end + end end -- cgit v1.2.1 From 1f5284e5ddf2ce9b555799f43ca73be32d9bdf67 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 1 Mar 2016 12:51:01 +0100 Subject: Use ILIKE/LIKE for searching snippets Previously this used a regular LIKE which is case-sensitive on PostgreSQL. This ensures that for both PostgreSQL and MySQL the searching is case-insensitive similar to searching for projects. --- app/models/snippet.rb | 24 ++++++++++++++++++++++-- spec/models/snippet_spec.rb | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/app/models/snippet.rb b/app/models/snippet.rb index dd3925c7a7d..35d05af38bf 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -113,12 +113,32 @@ class Snippet < ActiveRecord::Base end class << self + # Searches for snippets with a matching title or file name. + # + # This method uses ILIKE on PostgreSQL and LIKE on MySQL. + # + # query - The search query as a String. + # + # Returns an ActiveRecord::Relation. def search(query) - where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%") + t = Snippet.arel_table + pattern = "%#{query}%" + + where(t[:title].matches(pattern).or(t[:file_name].matches(pattern))) end + # Searches for snippets with matching content. + # + # This method uses ILIKE on PostgreSQL and LIKE on MySQL. + # + # query - The search query as a String. + # + # Returns an ActiveRecord::Relation. def search_code(query) - where('(content LIKE :query)', query: "%#{query}%") + table = Snippet.arel_table + pattern = "%#{query}%" + + where(table[:content].matches(pattern)) end def accessible_to(user) diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 7e5b5499aea..5077ac7b62b 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -59,4 +59,48 @@ describe Snippet, models: true do expect(snippet.to_reference(cross)).to eq "#{project.to_reference}$#{snippet.id}" end end + + describe '.search' do + let(:snippet) { create(:snippet) } + + it 'returns snippets with a matching title' do + expect(described_class.search(snippet.title)).to eq([snippet]) + end + + it 'returns snippets with a partially matching title' do + expect(described_class.search(snippet.title[0..2])).to eq([snippet]) + end + + it 'returns snippets with a matching title regardless of the casing' do + expect(described_class.search(snippet.title.upcase)).to eq([snippet]) + end + + it 'returns snippets with a matching file name' do + expect(described_class.search(snippet.file_name)).to eq([snippet]) + end + + it 'returns snippets with a partially matching file name' do + expect(described_class.search(snippet.file_name[0..2])).to eq([snippet]) + end + + it 'returns snippets with a matching file name regardless of the casing' do + expect(described_class.search(snippet.file_name.upcase)).to eq([snippet]) + end + end + + describe '#search_code' do + let(:snippet) { create(:snippet, content: 'class Foo; end') } + + it 'returns snippets with matching content' do + expect(described_class.search_code(snippet.content)).to eq([snippet]) + end + + it 'returns snippets with partially matching content' do + expect(described_class.search_code('class')).to eq([snippet]) + end + + it 'returns snippets with matching content regardless of the casing' do + expect(described_class.search_code('FOO')).to eq([snippet]) + end + end end -- cgit v1.2.1 From 508b6b46fea6b4bae9ce19c0337bafdb694edda8 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 1 Mar 2016 15:43:19 +0100 Subject: Use ILIKE/LIKE for searching notes --- app/models/note.rb | 12 +++++++++++- spec/models/note_spec.rb | 12 +++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/models/note.rb b/app/models/note.rb index 3b20d5d22b6..76e86fdbcaa 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -105,8 +105,18 @@ class Note < ActiveRecord::Base [:discussion, type.try(:underscore), id, line_code].join("-").to_sym end + # Searches for notes matching the given query. + # + # This method uses ILIKE on PostgreSQL and LIKE on MySQL. + # + # query - The search query as a String. + # + # Returns an ActiveRecord::Relation. def search(query) - where("LOWER(note) like :query", query: "%#{query.downcase}%") + table = Note.arel_table + pattern = "%#{query}%" + + where(table[:note].matches(pattern)) end def grouped_awards diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 33085dac4ea..cd620ea5440 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -140,10 +140,16 @@ describe Note, models: true do end end - describe :search do - let!(:note) { create(:note, note: "WoW") } + describe '.search' do + let(:note) { create(:note, note: 'WoW') } - it { expect(Note.search('wow')).to include(note) } + it 'returns notes with matching content' do + expect(described_class.search(note.note)).to eq([note]) + end + + it 'returns notes with matching content regardless of the casing' do + expect(described_class.search('WOW')).to eq([note]) + end end describe :grouped_awards do -- cgit v1.2.1 From 800aa296953c4a99e16e0493d9260359ef8c8414 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 1 Mar 2016 16:08:48 +0100 Subject: Use ILIKE/LIKE for searching users --- app/models/user.rb | 16 +++++++++++++++- spec/models/user_spec.rb | 48 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 505a547d8ec..fc4cf92be60 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -286,8 +286,22 @@ class User < ActiveRecord::Base end end + # Searches users matching the given query. + # + # This method uses ILIKE on PostgreSQL and LIKE on MySQL. + # + # query - The search query as a String + # + # Returns an ActiveRecord::Relation. def search(query) - where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%") + table = User.arel_table + pattern = "%#{query}%" + + where( + table[:name].matches(pattern). + or(table[:email].matches(pattern)). + or(table[:username].matches(pattern)) + ) end def by_login(login) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 412101ac9f9..90207f5153a 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -463,17 +463,43 @@ describe User, models: true do end end - describe 'search' do - let(:user1) { create(:user, username: 'James', email: 'james@testing.com') } - let(:user2) { create(:user, username: 'jameson', email: 'jameson@example.com') } - - it "should be case insensitive" do - expect(User.search(user1.username.upcase).to_a).to eq([user1]) - expect(User.search(user1.username.downcase).to_a).to eq([user1]) - expect(User.search(user2.username.upcase).to_a).to eq([user2]) - expect(User.search(user2.username.downcase).to_a).to eq([user2]) - expect(User.search(user1.username.downcase).to_a.size).to eq(2) - expect(User.search(user2.username.downcase).to_a.size).to eq(1) + describe '.search' do + let(:user) { create(:user) } + + it 'returns users with a matching name' do + expect(described_class.search(user.name)).to eq([user]) + end + + it 'returns users with a partially matching name' do + expect(described_class.search(user.name[0..2])).to eq([user]) + end + + it 'returns users with a matching name regarding of the casing' do + expect(described_class.search(user.name.upcase)).to eq([user]) + end + + it 'returns users with a matching Email' do + expect(described_class.search(user.email)).to eq([user]) + end + + it 'returns users with a partially matching Email' do + expect(described_class.search(user.email[0..2])).to eq([user]) + end + + it 'returns users with a matching Email regarding of the casing' do + expect(described_class.search(user.email.upcase)).to eq([user]) + end + + it 'returns users with a matching username' do + expect(described_class.search(user.username)).to eq([user]) + end + + it 'returns users with a partially matching username' do + expect(described_class.search(user.username[0..2])).to eq([user]) + end + + it 'returns users with a matching username regarding of the casing' do + expect(described_class.search(user.username.upcase)).to eq([user]) end end -- cgit v1.2.1 From ce5e831bcfd7daf1a12d488e2857d9424de091dd Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 1 Mar 2016 16:15:42 +0100 Subject: Use ILIKE/LIKE for searching groups --- app/models/group.rb | 12 +++++++++++- spec/models/group_spec.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/app/models/group.rb b/app/models/group.rb index 76042b3e3fd..bfeddf8b4d2 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -33,8 +33,18 @@ class Group < Namespace after_destroy :post_destroy_hook class << self + # Searches for groups matching the given query. + # + # This method uses ILIKE on PostgreSQL and LIKE on MySQL. + # + # query - The search query as a String + # + # Returns an ActiveRecord::Relation. def search(query) - where("LOWER(namespaces.name) LIKE :query or LOWER(namespaces.path) LIKE :query", query: "%#{query.downcase}%") + table = Group.arel_table + pattern = "%#{query}%" + + where(table[:name].matches(pattern).or(table[:path].matches(pattern))) end def sort(method) diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 3c995053eec..c9245fc9535 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -103,4 +103,30 @@ describe Group, models: true do expect(group.avatar_type).to eq(["only images allowed"]) end end + + describe '.search' do + it 'returns groups with a matching name' do + expect(described_class.search(group.name)).to eq([group]) + end + + it 'returns groups with a partially matching name' do + expect(described_class.search(group.name[0..2])).to eq([group]) + end + + it 'returns groups with a matching name regardless of the casing' do + expect(described_class.search(group.name.upcase)).to eq([group]) + end + + it 'returns groups with a matching path' do + expect(described_class.search(group.path)).to eq([group]) + end + + it 'returns groups with a partially matching path' do + expect(described_class.search(group.path[0..2])).to eq([group]) + end + + it 'returns groups with a matching path regardless of the casing' do + expect(described_class.search(group.path.upcase)).to eq([group]) + end + end end -- cgit v1.2.1 From 2076bdb62e6535cd8b5027c5636610eb06af00a0 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 1 Mar 2016 16:41:18 +0100 Subject: Use ILIKE/LIKE for searching CI runners --- app/models/ci/runner.rb | 20 +++++++++++++++++--- spec/models/ci/runner_spec.rb | 28 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index e725a6d468c..ce2750b071c 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -23,7 +23,7 @@ module Ci LAST_CONTACT_TIME = 5.minutes.ago AVAILABLE_SCOPES = ['specific', 'shared', 'active', 'paused', 'online'] - + has_many :builds, class_name: 'Ci::Build' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' has_many :projects, through: :runner_projects, class_name: '::Project', foreign_key: :gl_project_id @@ -46,9 +46,23 @@ module Ci acts_as_taggable + # Searches for runners matching the given query. + # + # This method uses ILIKE on PostgreSQL and LIKE on MySQL. + # + # This method performs a *partial* match on tokens, thus a query for "a" + # will match any runner where the token contains the letter "a". As a result + # you should *not* use this method for non-admin purposes as otherwise users + # might be able to query a list of all runners. + # + # query - The search query as a String + # + # Returns an ActiveRecord::Relation. def self.search(query) - where('LOWER(ci_runners.token) LIKE :query OR LOWER(ci_runners.description) like :query', - query: "%#{query.try(:downcase)}%") + t = Ci::Runner.arel_table + pattern = "%#{query}%" + + where(t[:token].matches(pattern).or(t[:description].matches(pattern))) end def set_default_values diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index e891838672e..25e9e5eca48 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -132,4 +132,32 @@ describe Ci::Runner, models: true do expect(runner.belongs_to_one_project?).to be_truthy end end + + describe '#search' do + let(:runner) { create(:ci_runner, token: '123abc') } + + it 'returns runners with a matching token' do + expect(described_class.search(runner.token)).to eq([runner]) + end + + it 'returns runners with a partially matching token' do + expect(described_class.search(runner.token[0..2])).to eq([runner]) + end + + it 'returns runners with a matching token regardless of the casing' do + expect(described_class.search(runner.token.upcase)).to eq([runner]) + end + + it 'returns runners with a matching description' do + expect(described_class.search(runner.description)).to eq([runner]) + end + + it 'returns runners with a partially matching description' do + expect(described_class.search(runner.description[0..2])).to eq([runner]) + end + + it 'returns runners with a matching description regardless of the casing' do + expect(described_class.search(runner.description.upcase)).to eq([runner]) + end + end end -- cgit v1.2.1 From 87e7c3e1321ac5ae26d6a23d7b16e8dadaff98d2 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 1 Mar 2016 16:59:36 +0100 Subject: Use ILIKE/LIKE for Issuable.search and full_search --- app/models/concerns/issuable.rb | 21 ++++++++++++++-- spec/models/concerns/issuable_spec.rb | 47 ++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 27b97944e38..3c42f582937 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -61,12 +61,29 @@ module Issuable end module ClassMethods + # Searches for records with a matching title. + # + # This method uses ILIKE on PostgreSQL and LIKE on MySQL. + # + # query - The search query as a String + # + # Returns an ActiveRecord::Relation. def search(query) - where("LOWER(title) like :query", query: "%#{query.downcase}%") + where(arel_table[:title].matches("%#{query}%")) end + # Searches for records with a matching title or description. + # + # This method uses ILIKE on PostgreSQL and LIKE on MySQL. + # + # query - The search query as a String + # + # Returns an ActiveRecord::Relation. def full_search(query) - where("LOWER(title) like :query OR LOWER(description) like :query", query: "%#{query.downcase}%") + t = arel_table + pattern = "%#{query}%" + + where(t[:title].matches(pattern).or(t[:description].matches(pattern))) end def sort(method) diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 600089802b2..f0e4de8539e 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -32,9 +32,54 @@ describe Issue, "Issuable" do describe ".search" do let!(:searchable_issue) { create(:issue, title: "Searchable issue") } - it "matches by title" do + it 'returns notches with a matching title' do + expect(described_class.search(searchable_issue.title)). + to eq([searchable_issue]) + end + + it 'returns notes with a partially matching title' do expect(described_class.search('able')).to eq([searchable_issue]) end + + it 'returns notes with a matching title regardless of the casing' do + expect(described_class.search(searchable_issue.title.upcase)). + to eq([searchable_issue]) + end + end + + describe ".full_search" do + let!(:searchable_issue) do + create(:issue, title: "Searchable issue", description: 'kittens') + end + + it 'returns notches with a matching title' do + expect(described_class.full_search(searchable_issue.title)). + to eq([searchable_issue]) + end + + it 'returns notes with a partially matching title' do + expect(described_class.full_search('able')).to eq([searchable_issue]) + end + + it 'returns notes with a matching title regardless of the casing' do + expect(described_class.full_search(searchable_issue.title.upcase)). + to eq([searchable_issue]) + end + + it 'returns notches with a matching description' do + expect(described_class.full_search(searchable_issue.description)). + to eq([searchable_issue]) + end + + it 'returns notes with a partially matching description' do + expect(described_class.full_search(searchable_issue.description)). + to eq([searchable_issue]) + end + + it 'returns notes with a matching description regardless of the casing' do + expect(described_class.full_search(searchable_issue.description.upcase)). + to eq([searchable_issue]) + end end describe "#today?" do -- cgit v1.2.1 From 2cf7f3f410832560bf049b24fbcaa27c1e3f30c7 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 1 Mar 2016 17:05:26 +0100 Subject: Use ILIKE/LIKE for searching milestones --- app/models/milestone.rb | 13 +++++++++++-- spec/models/milestone_spec.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/app/models/milestone.rb b/app/models/milestone.rb index e3969f32dd6..e3b6c552f92 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -58,9 +58,18 @@ class Milestone < ActiveRecord::Base alias_attribute :name, :title class << self + # Searches for milestones matching the given query. + # + # This method uses ILIKE on PostgreSQL and LIKE on MySQL. + # + # query - The search query as a String + # + # Returns an ActiveRecord::Relation. def search(query) - query = "%#{query}%" - where("title like ? or description like ?", query, query) + t = arel_table + pattern = "%#{query}%" + + where(t[:title].matches(pattern).or(t[:description].matches(pattern))) end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 28f13100d15..de1757bf67a 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -181,4 +181,34 @@ describe Milestone, models: true do expect(issue4.position).to eq(42) end end + + describe '.search' do + let(:milestone) { create(:milestone, title: 'foo', description: 'bar') } + + it 'returns milestones with a matching title' do + expect(described_class.search(milestone.title)).to eq([milestone]) + end + + it 'returns milestones with a partially matching title' do + expect(described_class.search(milestone.title[0..2])).to eq([milestone]) + end + + it 'returns milestones with a matching title regardless of the casing' do + expect(described_class.search(milestone.title.upcase)).to eq([milestone]) + end + + it 'returns milestones with a matching description' do + expect(described_class.search(milestone.description)).to eq([milestone]) + end + + it 'returns milestones with a partially matching description' do + expect(described_class.search(milestone.description[0..2])). + to eq([milestone]) + end + + it 'returns milestones with a matching description regardless of the casing' do + expect(described_class.search(milestone.description.upcase)). + to eq([milestone]) + end + end end -- cgit v1.2.1 From 013542965c2b6d84d28aab4823e5400897610087 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 1 Mar 2016 17:56:14 +0100 Subject: Refactor Gitlab::SearchResults Instead of plucking IDs this class now uses ActiveRecord::Relation objects. Plucking IDs is problematic as searching for projects can lead to a huge amount of IDs being loaded into memory only to be used as an argument for another query (instead of just using a sub-query). --- app/services/search/global_service.rb | 3 +- lib/gitlab/search_results.rb | 25 ++++++++++------ spec/lib/gitlab/search_results_spec.rb | 55 ++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 spec/lib/gitlab/search_results_spec.rb diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb index e904cb6c6fc..e1e94c5cc38 100644 --- a/app/services/search/global_service.rb +++ b/app/services/search/global_service.rb @@ -10,9 +10,8 @@ module Search group = Group.find_by(id: params[:group_id]) if params[:group_id].present? projects = ProjectsFinder.new.execute(current_user) projects = projects.in_namespace(group.id) if group - project_ids = projects.pluck(:id) - Gitlab::SearchResults.new(project_ids, params[:search]) + Gitlab::SearchResults.new(projects, params[:search]) end end end diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index 2ab2d4af797..036fa6f937c 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -2,12 +2,12 @@ module Gitlab class SearchResults attr_reader :query - # Limit search results by passed project ids + # Limit search results by passed projects # It allows us to search only for projects user has access to - attr_reader :limit_project_ids + attr_reader :limit_projects - def initialize(limit_project_ids, query) - @limit_project_ids = limit_project_ids || Project.all + def initialize(limit_projects, query) + @limit_projects = limit_projects || Project.all @query = Shellwords.shellescape(query) if query.present? end @@ -27,7 +27,8 @@ module Gitlab end def total_count - @total_count ||= projects_count + issues_count + merge_requests_count + milestones_count + @total_count ||= projects_count + issues_count + merge_requests_count + + milestones_count end def projects_count @@ -53,27 +54,29 @@ module Gitlab private def projects - Project.where(id: limit_project_ids).search(query) + limit_projects.search(query) end def issues - issues = Issue.where(project_id: limit_project_ids) + issues = Issue.where(project_id: project_ids_relation) + if query =~ /#(\d+)\z/ issues = issues.where(iid: $1) else issues = issues.full_search(query) end + issues.order('updated_at DESC') end def milestones - milestones = Milestone.where(project_id: limit_project_ids) + milestones = Milestone.where(project_id: project_ids_relation) milestones = milestones.search(query) milestones.order('updated_at DESC') end def merge_requests - merge_requests = MergeRequest.in_projects(limit_project_ids) + merge_requests = MergeRequest.in_projects(project_ids_relation) if query =~ /[#!](\d+)\z/ merge_requests = merge_requests.where(iid: $1) else @@ -89,5 +92,9 @@ module Gitlab def per_page 20 end + + def project_ids_relation + limit_projects.select(:id) + end end end diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb new file mode 100644 index 00000000000..bb18f417858 --- /dev/null +++ b/spec/lib/gitlab/search_results_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Gitlab::SearchResults do + let!(:project) { create(:project, name: 'foo') } + let!(:issue) { create(:issue, project: project, title: 'foo') } + + let!(:merge_request) do + create(:merge_request, source_project: project, title: 'foo') + end + + let!(:milestone) { create(:milestone, project: project, title: 'foo') } + let(:results) { described_class.new(Project.all, 'foo') } + + describe '#total_count' do + it 'returns the total amount of search hits' do + expect(results.total_count).to eq(4) + end + end + + describe '#projects_count' do + it 'returns the total amount of projects' do + expect(results.projects_count).to eq(1) + end + end + + describe '#issues_count' do + it 'returns the total amount of issues' do + expect(results.issues_count).to eq(1) + end + end + + describe '#merge_requests_count' do + it 'returns the total amount of merge requests' do + expect(results.merge_requests_count).to eq(1) + end + end + + describe '#milestones_count' do + it 'returns the total amount of milestones' do + expect(results.milestones_count).to eq(1) + end + end + + describe '#empty?' do + it 'returns true when there are no search results' do + allow(results).to receive(:total_count).and_return(0) + + expect(results.empty?).to eq(true) + end + + it 'returns false when there are search results' do + expect(results.empty?).to eq(false) + end + end +end -- cgit v1.2.1 From 42fde69d39234368f8252febc9bf6ca3eca6f275 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 1 Mar 2016 18:01:09 +0100 Subject: Refactor Gitlab::SnippetSearchResults This removes the need for plucking snippet IDs into memory. --- app/services/search/snippet_service.rb | 5 +++-- lib/gitlab/snippet_search_results.rb | 10 +++++----- spec/lib/gitlab/snippet_search_results_spec.rb | 25 +++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 spec/lib/gitlab/snippet_search_results_spec.rb diff --git a/app/services/search/snippet_service.rb b/app/services/search/snippet_service.rb index 8ca0877321d..0b3e713e220 100644 --- a/app/services/search/snippet_service.rb +++ b/app/services/search/snippet_service.rb @@ -7,8 +7,9 @@ module Search end def execute - snippet_ids = Snippet.accessible_to(current_user).pluck(:id) - Gitlab::SnippetSearchResults.new(snippet_ids, params[:search]) + snippets = Snippet.accessible_to(current_user) + + Gitlab::SnippetSearchResults.new(snippets, params[:search]) end end end diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb index addda95be2b..e0e74ff8359 100644 --- a/lib/gitlab/snippet_search_results.rb +++ b/lib/gitlab/snippet_search_results.rb @@ -2,10 +2,10 @@ module Gitlab class SnippetSearchResults < SearchResults include SnippetsHelper - attr_reader :limit_snippet_ids + attr_reader :limit_snippets - def initialize(limit_snippet_ids, query) - @limit_snippet_ids = limit_snippet_ids + def initialize(limit_snippets, query) + @limit_snippets = limit_snippets @query = query end @@ -35,11 +35,11 @@ module Gitlab private def snippet_titles - Snippet.where(id: limit_snippet_ids).search(query).order('updated_at DESC') + limit_snippets.search(query).order('updated_at DESC') end def snippet_blobs - Snippet.where(id: limit_snippet_ids).search_code(query).order('updated_at DESC') + limit_snippets.search_code(query).order('updated_at DESC') end def default_scope diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb new file mode 100644 index 00000000000..e86b9ef6a63 --- /dev/null +++ b/spec/lib/gitlab/snippet_search_results_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Gitlab::SnippetSearchResults do + let!(:snippet) { create(:snippet, content: 'foo', file_name: 'foo') } + + let(:results) { described_class.new(Snippet.all, 'foo') } + + describe '#total_count' do + it 'returns the total amount of search hits' do + expect(results.total_count).to eq(2) + end + end + + describe '#snippet_titles_count' do + it 'returns the amount of matched snippet titles' do + expect(results.snippet_titles_count).to eq(1) + end + end + + describe '#snippet_blobs_count' do + it 'returns the amount of matched snippet blobs' do + expect(results.snippet_blobs_count).to eq(1) + end + end +end -- cgit v1.2.1 From ec349dc1b6659d8f691cb1c1fb287aa05c9d9246 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 1 Mar 2016 18:04:37 +0100 Subject: Refactor Gitlab::ProjectSearchResults Previously this class would be given a project ID which was then used to retrieve the corresponding Project object. However, in all cases the Project object was already known as it was used to grab the ID to pass to ProjectSearchResults. By just passing a Project instead we remove the need for an extra query as well as the need for some other complexity in this class. --- app/services/search/project_service.rb | 2 +- lib/gitlab/project_search_results.rb | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb index f630c0a3790..c08881dce4b 100644 --- a/app/services/search/project_service.rb +++ b/app/services/search/project_service.rb @@ -7,7 +7,7 @@ module Search end def execute - Gitlab::ProjectSearchResults.new(project.id, + Gitlab::ProjectSearchResults.new(project, params[:search], params[:repository_ref]) end diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index 70de6a74e76..f36600c5756 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -2,8 +2,8 @@ module Gitlab class ProjectSearchResults < SearchResults attr_reader :project, :repository_ref - def initialize(project_id, query, repository_ref = nil) - @project = Project.find(project_id) + def initialize(project, query, repository_ref = nil) + @project = project @repository_ref = if repository_ref.present? repository_ref else @@ -73,7 +73,7 @@ module Gitlab end def notes - Note.where(project_id: limit_project_ids).user.search(query).order('updated_at DESC') + project.notes.user.search(query).order('updated_at DESC') end def commits @@ -83,9 +83,5 @@ module Gitlab project.repository.find_commits_by_message(query).compact end end - - def limit_project_ids - [project.id] - end end end -- cgit v1.2.1 From 49e122df852170cc1e5fc99f7e64e9f47408e1a8 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 3 Mar 2016 12:35:06 +0100 Subject: Backport Rails support for PostgreSQL opclasses This is needed to support creating/dumping/loading indexes that use the gin_trgm_ops operator class on PostgreSQL. These changes are taken from Rails pull request https://github.com/rails/rails/pull/19090. --- .../initializers/postgresql_opclasses_support.rb | 186 +++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 config/initializers/postgresql_opclasses_support.rb diff --git a/config/initializers/postgresql_opclasses_support.rb b/config/initializers/postgresql_opclasses_support.rb new file mode 100644 index 00000000000..ac803778ab0 --- /dev/null +++ b/config/initializers/postgresql_opclasses_support.rb @@ -0,0 +1,186 @@ +# These changes add support for PostgreSQL operator classes when creating +# indexes and dumping/loading schemas. Taken from Rails pull request +# https://github.com/rails/rails/pull/19090. +# +# License: +# +# Copyright (c) 2004-2016 David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +require 'date' +require 'set' +require 'bigdecimal' +require 'bigdecimal/util' + +# As the Struct definition is changed in this PR/patch we have to first remove +# the existing one. +ActiveRecord::ConnectionAdapters.send(:remove_const, :IndexDefinition) + +module ActiveRecord + module ConnectionAdapters #:nodoc: + # Abstract representation of an index definition on a table. Instances of + # this type are typically created and returned by methods in database + # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes + class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :opclasses) #:nodoc: + end + end +end + + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module SchemaStatements + def add_index_options(table_name, column_name, options = {}) #:nodoc: + column_names = Array(column_name) + index_name = index_name(table_name, column: column_names) + + options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclasses) + + index_type = options[:unique] ? "UNIQUE" : "" + index_type = options[:type].to_s if options.key?(:type) + index_name = options[:name].to_s if options.key?(:name) + max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length + + if options.key?(:algorithm) + algorithm = index_algorithms.fetch(options[:algorithm]) { + raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}") + } + end + + using = "USING #{options[:using]}" if options[:using].present? + + if supports_partial_index? + index_options = options[:where] ? " WHERE #{options[:where]}" : "" + end + + if index_name.length > max_index_length + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters" + end + if table_exists?(table_name) && index_name_exists?(table_name, index_name, false) + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" + end + index_columns = quoted_columns_for_index(column_names, options).join(", ") + + [index_name, index_type, index_columns, index_options, algorithm, using] + end + end + end +end + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module SchemaStatements + # Returns an array of indexes for the given table. + def indexes(table_name, name = nil) + result = query(<<-SQL, 'SCHEMA') + SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid + FROM pg_class t + INNER JOIN pg_index d ON t.oid = d.indrelid + INNER JOIN pg_class i ON d.indexrelid = i.oid + WHERE i.relkind = 'i' + AND d.indisprimary = 'f' + AND t.relname = '#{table_name}' + AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) ) + ORDER BY i.relname + SQL + + result.map do |row| + index_name = row[0] + unique = row[1] == 't' + indkey = row[2].split(" ") + inddef = row[3] + oid = row[4] + + columns = Hash[query(<<-SQL, "SCHEMA")] + SELECT a.attnum, a.attname + FROM pg_attribute a + WHERE a.attrelid = #{oid} + AND a.attnum IN (#{indkey.join(",")}) + SQL + + column_names = columns.values_at(*indkey).compact + + unless column_names.empty? + # add info on sort order for columns (only desc order is explicitly specified, asc is the default) + desc_order_columns = inddef.scan(/(\w+) DESC/).flatten + orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {} + where = inddef.scan(/WHERE (.+)$/).flatten[0] + using = inddef.scan(/USING (.+?) /).flatten[0].to_sym + opclasses = Hash[inddef.scan(/\((.+)\)$/).flatten[0].split(',').map do |column_and_opclass| + column, opclass = column_and_opclass.split(' ').map(&:strip) + [column, opclass] if opclass + end.compact] + + IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using, opclasses) + end + end.compact + end + + def add_index(table_name, column_name, options = {}) #:nodoc: + index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options) + execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}" + end + + protected + + def quoted_columns_for_index(column_names, options = {}) + column_opclasses = options[:opclasses] || {} + column_names.map {|name| "#{quote_column_name(name)} #{column_opclasses[name]}"} + end + end + end + end +end + +module ActiveRecord + class SchemaDumper + private + + def indexes(table, stream) + if (indexes = @connection.indexes(table)).any? + add_index_statements = indexes.map do |index| + statement_parts = [ + "add_index #{remove_prefix_and_suffix(index.table).inspect}", + index.columns.inspect, + "name: #{index.name.inspect}", + ] + statement_parts << 'unique: true' if index.unique + + index_lengths = (index.lengths || []).compact + statement_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any? + + index_orders = index.orders || {} + statement_parts << "order: #{index.orders.inspect}" if index_orders.any? + statement_parts << "where: #{index.where.inspect}" if index.where + statement_parts << "using: #{index.using.inspect}" if index.using + statement_parts << "type: #{index.type.inspect}" if index.type + statement_parts << "opclasses: #{index.opclasses}" if index.opclasses.present? + + " #{statement_parts.join(', ')}" + end + + stream.puts add_index_statements.sort.join("\n") + stream.puts + end + end + end +end -- cgit v1.2.1 From 2080fd395c98e1f5c822450b6dc9e8638b4e0329 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 3 Mar 2016 12:36:28 +0100 Subject: Updated schema to include Pg operator classes This also includes e.g. the appearances table which apparently wasn't already included in the schema. --- db/schema.rb | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 8f44ccebac8..3ac6203632d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -259,8 +259,8 @@ ActiveRecord::Schema.define(version: 20160309140734) do t.string "architecture" end - add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin - add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin + add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} + add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"} create_table "ci_services", force: :cascade do |t| t.string "type" @@ -421,13 +421,13 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree add_index "issues", ["created_at", "id"], name: "index_issues_on_created_at_and_id", using: :btree add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree - add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin + add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree add_index "issues", ["state"], name: "index_issues_on_state", using: :btree add_index "issues", ["title"], name: "index_issues_on_title", using: :btree - add_index "issues", ["title"], name: "index_issues_on_title_trigram", using: :gin + add_index "issues", ["title"], name: "index_issues_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "keys", force: :cascade do |t| t.integer "user_id" @@ -549,14 +549,14 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree add_index "merge_requests", ["created_at", "id"], name: "index_merge_requests_on_created_at_and_id", using: :btree add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree - add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin + add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree add_index "merge_requests", ["source_project_id"], name: "index_merge_requests_on_source_project_id", using: :btree add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree - add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin + add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "milestones", force: :cascade do |t| t.string "title", null: false @@ -570,12 +570,12 @@ ActiveRecord::Schema.define(version: 20160309140734) do end add_index "milestones", ["created_at", "id"], name: "index_milestones_on_created_at_and_id", using: :btree - add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin + add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree add_index "milestones", ["title"], name: "index_milestones_on_title", using: :btree - add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin + add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "namespaces", force: :cascade do |t| t.string "name", null: false @@ -590,10 +590,10 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree - add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin + add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree - add_index "namespaces", ["path"], name: "index_namespaces_on_path_trigram", using: :gin + add_index "namespaces", ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"} add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree create_table "notes", force: :cascade do |t| @@ -619,7 +619,7 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree add_index "notes", ["is_award"], name: "index_notes_on_is_award", using: :btree add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree - add_index "notes", ["note"], name: "index_notes_on_note_trigram", using: :gin + add_index "notes", ["note"], name: "index_notes_on_note_trigram", using: :gin, opclasses: {"note"=>"gin_trgm_ops"} add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree @@ -718,12 +718,12 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree - add_index "projects", ["description"], name: "index_projects_on_description_trigram", using: :gin + add_index "projects", ["description"], name: "index_projects_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree - add_index "projects", ["name"], name: "index_projects_on_name_trigram", using: :gin + add_index "projects", ["name"], name: "index_projects_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree add_index "projects", ["path"], name: "index_projects_on_path", using: :btree - add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin + add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"} add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree @@ -801,9 +801,9 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree add_index "snippets", ["created_at", "id"], name: "index_snippets_on_created_at_and_id", using: :btree add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree - add_index "snippets", ["file_name"], name: "index_snippets_on_file_name_trigram", using: :gin + add_index "snippets", ["file_name"], name: "index_snippets_on_file_name_trigram", using: :gin, opclasses: {"file_name"=>"gin_trgm_ops"} add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree - add_index "snippets", ["title"], name: "index_snippets_on_title_trigram", using: :gin + add_index "snippets", ["title"], name: "index_snippets_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} add_index "snippets", ["updated_at"], name: "index_snippets_on_updated_at", using: :btree add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree @@ -937,12 +937,12 @@ ActiveRecord::Schema.define(version: 20160309140734) do add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree - add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin + add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"} add_index "users", ["name"], name: "index_users_on_name", using: :btree - add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin + add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["username"], name: "index_users_on_username", using: :btree - add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin + add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"} create_table "users_star_projects", force: :cascade do |t| t.integer "project_id", null: false -- cgit v1.2.1 From d38dcf1e3b08608aab15e9dd811f9fc878751ce5 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 3 Mar 2016 12:55:44 +0100 Subject: Patch MySQL to ignore PostgreSQL schema options This ensures that options such as `using: :gin` and PostgreSQL operator classes are ignored when loading a schema into a MySQL database. --- .../mysql_ignore_postgresql_options.rb | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 config/initializers/mysql_ignore_postgresql_options.rb diff --git a/config/initializers/mysql_ignore_postgresql_options.rb b/config/initializers/mysql_ignore_postgresql_options.rb new file mode 100644 index 00000000000..835f3ec5574 --- /dev/null +++ b/config/initializers/mysql_ignore_postgresql_options.rb @@ -0,0 +1,49 @@ +# This patches ActiveRecord so indexes created using the MySQL adapter ignore +# any PostgreSQL specific options (e.g. `using: :gin`). +# +# These patches do the following for MySQL: +# +# 1. Indexes created using the :opclasses option are ignored (as they serve no +# purpose on MySQL). +# 2. When creating an index with `using: :gin` the `using` option is discarded +# as :gin is not a valid value for MySQL. +# 3. The `:opclasses` option is stripped from add_index_options in case it's +# used anywhere other than in the add_index methods. + +if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) + module ActiveRecord + module ConnectionAdapters + class Mysql2Adapter < AbstractMysqlAdapter + alias_method :__gitlab_add_index, :add_index + alias_method :__gitlab_add_index_sql, :add_index_sql + alias_method :__gitlab_add_index_options, :add_index_options + + def add_index(table_name, column_name, options = {}) + unless options[:opclasses] + __gitlab_add_index(table_name, column_name, options) + end + end + + def add_index_sql(table_name, column_name, options = {}) + unless options[:opclasses] + __gitlab_add_index_sql(table_name, column_name, options) + end + end + + def add_index_options(table_name, column_name, options = {}) + if options[:using] and options[:using] == :gin + options = options.dup + options.delete(:using) + end + + if options[:opclasses] + options = options.dup + options.delete(:opclasses) + end + + __gitlab_add_index_options(table_name, column_name, options) + end + end + end + end +end -- cgit v1.2.1 From 12082db41ea8e1718012f95f3a4c42dafaa55392 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 3 Mar 2016 13:09:28 +0100 Subject: Disable Rubocop for PostgreSQL patches This code is mostly a copy-paste from existing pull requests so there's no point in running Rubocop on it. --- config/initializers/postgresql_opclasses_support.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/initializers/postgresql_opclasses_support.rb b/config/initializers/postgresql_opclasses_support.rb index ac803778ab0..820cc89ef57 100644 --- a/config/initializers/postgresql_opclasses_support.rb +++ b/config/initializers/postgresql_opclasses_support.rb @@ -1,3 +1,5 @@ +# rubocop:disable all + # These changes add support for PostgreSQL operator classes when creating # indexes and dumping/loading schemas. Taken from Rails pull request # https://github.com/rails/rails/pull/19090. -- cgit v1.2.1 From 300332bbf6061267176d83ef8431daf6fae47e2b Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 3 Mar 2016 13:12:00 +0100 Subject: Fixed ProjectSearchResults spec to use a Project This spec was still passing an ID to the #initialize method instead of a Project instance. --- spec/lib/gitlab/project_search_results_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index efc2e5f4ef1..09adbc07dcb 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::ProjectSearchResults, lib: true do let(:query) { 'hello world' } describe 'initialize with empty ref' do - let(:results) { Gitlab::ProjectSearchResults.new(project.id, query, '') } + let(:results) { Gitlab::ProjectSearchResults.new(project, query, '') } it { expect(results.project).to eq(project) } it { expect(results.repository_ref).to be_nil } @@ -14,7 +14,7 @@ describe Gitlab::ProjectSearchResults, lib: true do describe 'initialize with ref' do let(:ref) { 'refs/heads/test' } - let(:results) { Gitlab::ProjectSearchResults.new(project.id, query, ref) } + let(:results) { Gitlab::ProjectSearchResults.new(project, query, ref) } it { expect(results.project).to eq(project) } it { expect(results.repository_ref).to eq(ref) } -- cgit v1.2.1 From 8c2868e8ea5d6340efcedfe5fd6388b0087eae89 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 3 Mar 2016 15:10:14 +0100 Subject: Added ProjectSearchResults#project_ids_relation This ensures some other methods such as the "issues" method still work. --- lib/gitlab/project_search_results.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index f36600c5756..0607a8b9592 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -83,5 +83,9 @@ module Gitlab project.repository.find_commits_by_message(query).compact end end + + def project_ids_relation + project + end end end -- cgit v1.2.1 From b77b3b16b6d34e19b07ec58bc9b1e1ca3b05ba25 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 3 Mar 2016 18:35:17 +0100 Subject: Removed order from sub-query projects for search There's no need to order queries used as sub-queries and doing so can add potential overhead. --- lib/gitlab/search_results.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index 036fa6f937c..f13528a2eea 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -94,7 +94,7 @@ module Gitlab end def project_ids_relation - limit_projects.select(:id) + limit_projects.select(:id).reorder(nil) end end end -- cgit v1.2.1 From 9e00a237161679b0d6afdefd246e0a7bf209510c Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 4 Mar 2016 11:39:00 +0100 Subject: Clean up ProjectsFinder for getting user projects We don't need the extra layer of nesting of UNION queries here (as User#authorized_projects already returns a UNION'd query). --- app/finders/projects_finder.rb | 5 ++++- app/models/user.rb | 9 ++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 3b4e0362e04..2b8fba77bb1 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -52,7 +52,10 @@ class ProjectsFinder def all_projects(current_user) if current_user - [current_user.authorized_projects, public_and_internal_projects] + [ + *current_user.project_relations, + public_and_internal_projects + ] else [Project.public_only] end diff --git a/app/models/user.rb b/app/models/user.rb index fc4cf92be60..725f748faf0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -442,6 +442,11 @@ class User < ActiveRecord::Base Project.where("projects.id IN (#{projects_union.to_sql})") end + # Returns all the project relations + def project_relations + [personal_projects, groups_projects, projects] + end + def owned_projects @owned_projects ||= Project.where('namespace_id IN (?) OR namespace_id = ?', @@ -830,9 +835,7 @@ class User < ActiveRecord::Base private def projects_union - Gitlab::SQL::Union.new([personal_projects.select(:id), - groups_projects.select(:id), - projects.select(:id)]) + Gitlab::SQL::Union.new(project_relations.map { |r| r.select(:id) }) end def ci_projects_union -- cgit v1.2.1 From 0ab9571ad791c608ec258426edb195ae95b45220 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 4 Mar 2016 11:54:37 +0100 Subject: Fixed a few spec typos --- spec/models/concerns/issuable_spec.rb | 6 +++--- spec/models/user_spec.rb | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index f0e4de8539e..aff384c2949 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -32,7 +32,7 @@ describe Issue, "Issuable" do describe ".search" do let!(:searchable_issue) { create(:issue, title: "Searchable issue") } - it 'returns notches with a matching title' do + it 'returns notes with a matching title' do expect(described_class.search(searchable_issue.title)). to eq([searchable_issue]) end @@ -52,7 +52,7 @@ describe Issue, "Issuable" do create(:issue, title: "Searchable issue", description: 'kittens') end - it 'returns notches with a matching title' do + it 'returns notes with a matching title' do expect(described_class.full_search(searchable_issue.title)). to eq([searchable_issue]) end @@ -66,7 +66,7 @@ describe Issue, "Issuable" do to eq([searchable_issue]) end - it 'returns notches with a matching description' do + it 'returns notes with a matching description' do expect(described_class.full_search(searchable_issue.description)). to eq([searchable_issue]) end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 90207f5153a..909b6796591 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -474,7 +474,7 @@ describe User, models: true do expect(described_class.search(user.name[0..2])).to eq([user]) end - it 'returns users with a matching name regarding of the casing' do + it 'returns users with a matching name regardless of the casing' do expect(described_class.search(user.name.upcase)).to eq([user]) end @@ -486,7 +486,7 @@ describe User, models: true do expect(described_class.search(user.email[0..2])).to eq([user]) end - it 'returns users with a matching Email regarding of the casing' do + it 'returns users with a matching Email regardless of the casing' do expect(described_class.search(user.email.upcase)).to eq([user]) end @@ -498,7 +498,7 @@ describe User, models: true do expect(described_class.search(user.username[0..2])).to eq([user]) end - it 'returns users with a matching username regarding of the casing' do + it 'returns users with a matching username regardless of the casing' do expect(described_class.search(user.username.upcase)).to eq([user]) end end -- cgit v1.2.1 From d7d5937531350283f6cbd68dead5750227e6d962 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 4 Mar 2016 12:01:21 +0100 Subject: Removed arel_table receiver from search methods We can just use "arel_table" in these cases instead of "SomeClass.arel_table". --- app/models/ci/runner.rb | 2 +- app/models/group.rb | 2 +- app/models/note.rb | 2 +- app/models/project.rb | 2 +- app/models/snippet.rb | 2 +- app/models/user.rb | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index ce2750b071c..90349a07594 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -59,7 +59,7 @@ module Ci # # Returns an ActiveRecord::Relation. def self.search(query) - t = Ci::Runner.arel_table + t = arel_table pattern = "%#{query}%" where(t[:token].matches(pattern).or(t[:description].matches(pattern))) diff --git a/app/models/group.rb b/app/models/group.rb index bfeddf8b4d2..afbc2922013 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -41,7 +41,7 @@ class Group < Namespace # # Returns an ActiveRecord::Relation. def search(query) - table = Group.arel_table + table = Namespace.arel_table pattern = "%#{query}%" where(table[:name].matches(pattern).or(table[:path].matches(pattern))) diff --git a/app/models/note.rb b/app/models/note.rb index 76e86fdbcaa..8b0610ff77e 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -113,7 +113,7 @@ class Note < ActiveRecord::Base # # Returns an ActiveRecord::Relation. def search(query) - table = Note.arel_table + table = arel_table pattern = "%#{query}%" where(table[:note].matches(pattern)) diff --git a/app/models/project.rb b/app/models/project.rb index cb236bf41cd..ce103398a9a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -274,7 +274,7 @@ class Project < ActiveRecord::Base # # query - The search query as a String. def search(query) - ptable = Project.arel_table + ptable = arel_table ntable = Namespace.arel_table pattern = "%#{query}%" diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 35d05af38bf..b9e835a4486 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -121,7 +121,7 @@ class Snippet < ActiveRecord::Base # # Returns an ActiveRecord::Relation. def search(query) - t = Snippet.arel_table + t = arel_table pattern = "%#{query}%" where(t[:title].matches(pattern).or(t[:file_name].matches(pattern))) diff --git a/app/models/user.rb b/app/models/user.rb index 725f748faf0..101303e1f1f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -294,7 +294,7 @@ class User < ActiveRecord::Base # # Returns an ActiveRecord::Relation. def search(query) - table = User.arel_table + table = arel_table pattern = "%#{query}%" where( -- cgit v1.2.1 From ee75c49313127f53fe60a0701f40024b897a15e8 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 4 Mar 2016 12:06:25 +0100 Subject: Make Namespace.search case-insensitive This ensures searching namespaces works exactly the same as searching for any other resource. --- app/models/namespace.rb | 12 +++++++++++- spec/models/namespace_spec.rb | 29 ++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index bdb33f37495..55842df1e2d 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -52,8 +52,18 @@ class Namespace < ActiveRecord::Base find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase) end + # Searches for namespaces matching the given query. + # + # This method uses ILIKE on PostgreSQL and LIKE on MySQL. + # + # query - The search query as a String + # + # Returns an ActiveRecord::Relation def search(query) - where("name LIKE :query OR path LIKE :query", query: "%#{query}%") + t = arel_table + pattern = "%#{query}%" + + where(t[:name].matches(pattern).or(t[:path].matches(pattern))) end def clean_path(path) diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index e0b3290e416..bc31b49742f 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -41,13 +41,32 @@ describe Namespace, models: true do it { expect(namespace.human_name).to eq(namespace.owner_name) } end - describe :search do - before do - @namespace = create :namespace + describe '#search' do + let(:namespace) { create(:namespace) } + + it 'returns namespaces with a matching name' do + expect(described_class.search(namespace.name)).to eq([namespace]) + end + + it 'returns namespaces with a partially matching name' do + expect(described_class.search(namespace.name[0..2])).to eq([namespace]) + end + + it 'returns namespaces with a matching name regardless of the casing' do + expect(described_class.search(namespace.name.upcase)).to eq([namespace]) + end + + it 'returns namespaces with a matching path' do + expect(described_class.search(namespace.path)).to eq([namespace]) end - it { expect(Namespace.search(@namespace.path)).to eq([@namespace]) } - it { expect(Namespace.search('unknown')).to eq([]) } + it 'returns namespaces with a partially matching path' do + expect(described_class.search(namespace.path[0..2])).to eq([namespace]) + end + + it 'returns namespaces with a matching path regardless of the casing' do + expect(described_class.search(namespace.path.upcase)).to eq([namespace]) + end end describe :move_dir do -- cgit v1.2.1 From 78244ffcbff2d5f31611c7a793cad797905cd384 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 4 Mar 2016 12:15:30 +0100 Subject: Corrected spec title for Namespace.search --- spec/models/namespace_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index bc31b49742f..3c3a580942a 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -41,7 +41,7 @@ describe Namespace, models: true do it { expect(namespace.human_name).to eq(namespace.owner_name) } end - describe '#search' do + describe '.search' do let(:namespace) { create(:namespace) } it 'returns namespaces with a matching name' do -- cgit v1.2.1 From 0ba20457fb7e16538e447bd6d3782cb8b7b506fa Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 4 Mar 2016 12:21:26 +0100 Subject: Added pg_trgm to the PostgreSQL requirements --- doc/install/requirements.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 8df142c531b..d59b7f0e84d 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -97,6 +97,17 @@ To change the Unicorn workers when you have the Omnibus package please see [the If you want to run the database separately expect a size of about 1 MB per user. +### PostgreSQL Requirements + +Users using PostgreSQL must ensure the `pg_trgm` extension is loaded into every +GitLab database. This extension can be enabled (using a PostgreSQL super user) +by running the following query for every database: + + CREATE EXTENSION pg_trgm; + +On some systems you may need to install an additional package (e.g. +`postgresql-contrib`) for this extension to become available. + ## Redis and Sidekiq Redis stores all user sessions and the background task queue. -- cgit v1.2.1 From c2df121f8eeaf8e0fccab43f5b181f70f3725e58 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 4 Mar 2016 12:23:51 +0100 Subject: Added changelog entry for search performance --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 8917eebafda..d4554b96190 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.6.0 (unreleased) - Return empty array instead of 404 when commit has no statuses in commit status API - Decrease the font size and the padding of the `.anchor` icons used in the README (Roberto Dip) - Rewrite logo to simplify SVG code (Sean Lang) + - Refactor and greatly improve search performance - Add support for cross-project label references - Update documentation to reflect Guest role not being enforced on internal projects - Allow search for logged out users -- cgit v1.2.1 From 4f3fa519c627b7caf45273226ae438181dfbc392 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 7 Mar 2016 12:56:46 +0100 Subject: Use a UNION in MergeRequest.in_projects The OR condition for source_project_id/target_project_id leads to a query plan that performs rather poorly on PostgreSQL due to the use of sub-queries. Because Rails offers no easy alternative for this particular problem we're forced to using a UNION for both conditions. The resulting query performs much faster than just using an OR. --- app/models/merge_request.rb | 19 ++++++++++++++++++- spec/models/merge_request_spec.rb | 6 ++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index c1e18bb3cc5..188325045e2 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -135,7 +135,6 @@ class MergeRequest < ActiveRecord::Base scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } - scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) } scope :of_projects, ->(ids) { where(target_project_id: ids) } scope :merged, -> { with_state(:merged) } scope :closed_and_merged, -> { with_states(:closed, :merged) } @@ -161,6 +160,24 @@ class MergeRequest < ActiveRecord::Base super("merge_requests", /(?\d+)/) end + # Returns all the merge requests from an ActiveRecord:Relation. + # + # This method uses a UNION as it usually operates on the result of + # ProjectsFinder#execute. PostgreSQL in particular doesn't always like queries + # using multiple sub-queries especially when combined with an OR statement. + # UNIONs on the other hand perform much better in these cases. + # + # relation - An ActiveRecord::Relation that returns a list of Projects. + # + # Returns an ActiveRecord::Relation. + def self.in_projects(relation) + source = where(source_project_id: relation).select(:id) + target = where(target_project_id: relation).select(:id) + union = Gitlab::SQL::Union.new([source, target]) + + where("merge_requests.id IN (#{union.to_sql})") + end + def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 59c40922abb..8bf68013fd2 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -80,6 +80,12 @@ describe MergeRequest, models: true do it { is_expected.to respond_to(:merge_when_build_succeeds) } end + describe '.in_projects' do + it 'returns the merge requests for a set of projects' do + expect(described_class.in_projects(Project.all)).to eq([subject]) + end + end + describe '#to_reference' do it 'returns a String reference to the object' do expect(subject.to_reference).to eq "!#{subject.iid}" -- cgit v1.2.1 From 67143e25a2395c743e0537e7f40b893a65912b7d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 10 Mar 2016 14:49:15 +0100 Subject: More detailed trigram migration error message This explains the user what they need to run and where to go in case they want to learn more about "CREATE EXTENSION". --- db/migrate/20160226114608_add_trigram_indexes_for_searching.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb index fca5ac01a08..003169c13c6 100644 --- a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb +++ b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb @@ -5,7 +5,10 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration return unless Gitlab::Database.postgresql? unless trigrams_enabled? - raise 'You must enable the pg_trgm extension as a PostgreSQL super user' + raise 'You must enable the pg_trgm extension. You can do so by running ' \ + '"CREATE EXTENSION pg_trgm;" as a PostgreSQL super user, this must be ' \ + 'done for every GitLab database. For more information see ' \ + 'http://www.postgresql.org/docs/current/static/sql-createextension.html' end # trigram indexes are case-insensitive so we can just index the column -- cgit v1.2.1 From e932a4164f527e51567e4476eee51dbc4102cd7c Mon Sep 17 00:00:00 2001 From: Dennis van de Hoef Date: Fri, 11 Mar 2016 22:34:51 +0100 Subject: Fix responsive bug top navigation --- app/assets/stylesheets/framework/nav.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 7de874c8bcd..b2fbc95e043 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -63,7 +63,7 @@ border-bottom: none; /* Small devices (phones, tablets, 768px and lower) */ - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-sm-max) { width: 100%; } } -- cgit v1.2.1 From 01f6db4f643380d143772958b22d3f318b1db3a7 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 11 Mar 2016 17:46:50 -0500 Subject: Disallow blank (non-null) values for a Note's `line_code` attribute It's unclear how these blank values got added, but GitLab.com had a few: ``` irb(main):002:0> Note.where("line_code IS NOT NULL AND line_code = ''").count => 439 ``` We've added a migration to convert any existing records to use a NULL value when blank, and updated Note to set blank values to nil before validation. --- app/models/note.rb | 7 ++++++- db/migrate/20160307221555_disallow_blank_line_code_on_note.rb | 9 +++++++++ spec/models/note_spec.rb | 8 ++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160307221555_disallow_blank_line_code_on_note.rb diff --git a/app/models/note.rb b/app/models/note.rb index 8b0610ff77e..2e084b5c80c 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -44,6 +44,7 @@ class Note < ActiveRecord::Base delegate :name, :email, to: :author, prefix: 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 } @@ -63,7 +64,7 @@ class Note < ActiveRecord::Base 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 :not_inline, ->{ where(line_code: nil) } scope :system, ->{ where(system: true) } scope :user, ->{ where(system: false) } scope :common, ->{ where(noteable_type: ["", nil]) } @@ -375,6 +376,10 @@ class Note < ActiveRecord::Base private + def clear_blank_line_code! + self.line_code = nil if self.line_code.blank? + end + def awards_supported? (for_issue? || for_merge_request?) && !for_diff_line? end diff --git a/db/migrate/20160307221555_disallow_blank_line_code_on_note.rb b/db/migrate/20160307221555_disallow_blank_line_code_on_note.rb new file mode 100644 index 00000000000..49e787d9a9a --- /dev/null +++ b/db/migrate/20160307221555_disallow_blank_line_code_on_note.rb @@ -0,0 +1,9 @@ +class DisallowBlankLineCodeOnNote < ActiveRecord::Migration + def up + execute("UPDATE notes SET line_code = NULL WHERE line_code = ''") + end + + def down + # noop + end +end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index cd620ea5440..b854de1d3d5 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -226,4 +226,12 @@ describe Note, models: true do expect(note.is_award?).to be_falsy end end + + describe 'clear_blank_line_code!' do + it 'clears a blank line code before validation' do + note = build(:note, line_code: ' ') + + expect { note.valid? }.to change(note, :line_code).to(nil) + end + end end -- cgit v1.2.1 From a63eba9a2bebd6e33c3d1051a0d2fd08e024f546 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 8 Mar 2016 20:04:13 -0500 Subject: Add unit specs for `Note#active?` --- app/models/note.rb | 33 ++++++++++++++++---------- spec/models/note_spec.rb | 62 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 13 deletions(-) diff --git a/app/models/note.rb b/app/models/note.rb index 8b0610ff77e..0ea61b24955 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -172,26 +172,29 @@ class Note < ActiveRecord::Base Note.where(noteable_id: noteable_id, noteable_type: noteable_type, line_code: line_code).last.try(:diff) end - # Check if such line of code exists in merge request diff - # If exists - its active discussion - # If not - its outdated diff + # 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) - diffs = noteable.diffs(Commit.max_diff_options) - notable_diff = diffs.find { |d| d.new_path == self.diff.new_path } + noteable_diff = find_noteable_diff - return @active = false if notable_diff.nil? + if noteable_diff + parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line) - parsed_lines = Gitlab::Diff::Parser.new.parse(notable_diff.diff.each_line) - # We cannot use ||= because @active may be false - @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line } - end + @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line } + else + @active = false + end - def outdated? - !active? + @active end def diff_file_index @@ -375,6 +378,12 @@ class Note < ActiveRecord::Base private + # 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? end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index cd620ea5440..b3ed7d6f8bd 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -152,7 +152,7 @@ describe Note, models: true do end end - describe :grouped_awards do + describe '.grouped_awards' do before do create :note, note: "smile", is_award: true create :note, note: "smile", is_award: true @@ -169,6 +169,66 @@ 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) -- cgit v1.2.1 From aff1058c61cb8bc2ffbec42babb935828aa766ec Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 11 Mar 2016 15:32:12 -0500 Subject: Fix ordering in 8.5-to-8.6 update guide [ci skip] --- doc/update/8.5-to-8.6.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/doc/update/8.5-to-8.6.md b/doc/update/8.5-to-8.6.md index a8167b2cf0b..9a1b94ab369 100644 --- a/doc/update/8.5-to-8.6.md +++ b/doc/update/8.5-to-8.6.md @@ -105,35 +105,37 @@ via [/etc/default/gitlab]. [Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache [/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-6-stable/lib/support/init.d/gitlab.default.example#L37 +#### Init script + +Ensure you're still up-to-date with the latest init script changes: + + sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + ### 8. Updates for PostgreSQL Users Starting with 8.6 users using GitLab in combination with PostgreSQL are required -to have the "pg_trgm" extension enabled for all GitLab databases. If you're +to have the `pg_trgm` extension enabled for all GitLab databases. If you're using GitLab's Omnibus packages there's nothing you'll need to do manually as this extension is enabled automatically. Users who install GitLab without using Omnibus (e.g. by building from source) have to enable this extension manually. To enable this extension run the following SQL command as a PostgreSQL super user for _every_ GitLab database: - CREATE EXTENSION IF NOT EXISTS pg_trgm; +```sql +CREATE EXTENSION IF NOT EXISTS pg_trgm; +``` Certain operating systems might require the installation of extra packages for this extension to be available. For example, users using Ubuntu will have to -install the "postgresql-contrib" package in order for this extension to be +install the `postgresql-contrib` package in order for this extension to be available. -#### Init script - -Ensure you're still up-to-date with the latest init script changes: - - sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab - -### 8. Start application +### 9. Start application sudo service gitlab start sudo service nginx restart -### 9. Check application status +### 10. Check application status Check if GitLab and its environment are configured correctly: -- cgit v1.2.1 From cc065fbeaa8b5e480d6ba5349198042e8a84b011 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 12 Mar 2016 01:09:37 -0800 Subject: Support Golang subpackage fetching Closes #13805 --- CHANGELOG | 1 + config/initializers/go_get.rb | 1 + lib/gitlab/middleware/go.rb | 51 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 config/initializers/go_get.rb create mode 100644 lib/gitlab/middleware/go.rb diff --git a/CHANGELOG b/CHANGELOG index 1847c5193ab..b29ad11e6ad 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.6.0 (unreleased) + - Support Golang subpackage fetching (Stan Hu) - Contributions to forked projects are included in calendar - Improve the formatting for the user page bio (Connor Shea) - Removed the default password from the initial admin account created during diff --git a/config/initializers/go_get.rb b/config/initializers/go_get.rb new file mode 100644 index 00000000000..7e7896b4900 --- /dev/null +++ b/config/initializers/go_get.rb @@ -0,0 +1 @@ +Rails.application.config.middleware.use(Gitlab::Middleware::Go) diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb new file mode 100644 index 00000000000..bc9bee7cbb6 --- /dev/null +++ b/lib/gitlab/middleware/go.rb @@ -0,0 +1,51 @@ +# A dumb middleware that returns a Go HTML document if the go-get=1 query string +# is used irrespective if the namespace/project exists +module Gitlab + module Middleware + class Go + def initialize(app) + @app = app + end + + def call(env) + request = Rack::Request.new(env) + + if go_request?(request) + render_go_doc(request) + else + @app.call(env) + end + end + + private + + def render_go_doc(request) + body = go_body(request) + response = Rack::Response.new(body, 200, { 'Content-Type' => 'text/html' }) + response.finish + end + + def go_request?(request) + return request["go-get"].to_i == 1 + end + + def go_body(request) + base_url = Settings.gitlab['url'] + # Go subpackages may be in the form of namespace/project/path1/path2/../pathN + # We can just ignore the paths and leave the namespace/project + path_info = request.env["PATH_INFO"] + path_info.sub!(/^\//, '') + project_path = path_info.split('/').first(2).join('/') + request_url = URI.join(base_url, project_path) + domain_path = strip_url(request_url.to_s) + + "\n"; + end + + def strip_url(url) + url.gsub('http://', ''). + gsub('https://', '') + end + end + end +end -- cgit v1.2.1 From 55ceda12045ffb59187b7104a3b28a34e28a8b87 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 12 Mar 2016 14:45:14 +0100 Subject: Bring shared project feature tests from EE Signed-off-by: Dmitriy Zaporozhets --- features/admin/groups.feature | 5 ++++ features/project/team_management.feature | 5 ++++ features/steps/admin/groups.rb | 19 +++++++++++++ features/steps/project/team_management.rb | 19 +++++++++++++ spec/finders/projects_finder_spec.rb | 34 +++++++++++++++++++++++- spec/models/project_team_spec.rb | 44 +++++++++++++++++++++++++++++++ 6 files changed, 125 insertions(+), 1 deletion(-) diff --git a/features/admin/groups.feature b/features/admin/groups.feature index 2edb3964f70..ab7de7ac315 100644 --- a/features/admin/groups.feature +++ b/features/admin/groups.feature @@ -21,6 +21,11 @@ Feature: Admin Groups When I select user "John Doe" from user list as "Reporter" Then I should see "John Doe" in team list in every project as "Reporter" + Scenario: Shared projects + Given group has shared projects + When I visit group page + Then I should see project shared with group + @javascript Scenario: Remove user from group Given we have user "John Doe" in group diff --git a/features/project/team_management.feature b/features/project/team_management.feature index 06fb45c8bde..5888662fc3f 100644 --- a/features/project/team_management.feature +++ b/features/project/team_management.feature @@ -39,3 +39,8 @@ Feature: Project Team Management And I click link "Import team from another project" And I submit "Website" project for import team Then I should see "Mike" in team list as "Reporter" + + Scenario: See all members of projects shared group + Given I share project with group "OpenSource" + And I visit project "Shop" team page + Then I should see "Opensource" group user listing diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index 43fd91d0d4c..e1f1db2872f 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -73,6 +73,21 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps end end + step 'group has shared projects' do + share_link = shared_project.project_group_links.new(group_access: Gitlab::Access::MASTER) + share_link.group_id = current_group.id + share_link.save! + end + + step 'I visit group page' do + visit admin_group_path(current_group) + end + + step 'I should see project shared with group' do + expect(page).to have_content(shared_project.name_with_namespace) + expect(page).to have_content "Projects shared with" + end + step 'we have user "John Doe" in group' do current_group.add_reporter(user_john) end @@ -123,6 +138,10 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps @group ||= Group.first end + def shared_project + @shared_project ||= create(:empty_project) + end + def user_john @user_john ||= User.find_by(name: "John Doe") end diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb index caad52def79..3fbcf770b62 100644 --- a/features/steps/project/team_management.rb +++ b/features/steps/project/team_management.rb @@ -123,4 +123,23 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps click_link('Remove user from team') end end + + step 'I share project with group "OpenSource"' do + project = Project.find_by(name: 'Shop') + os_group = create(:group, name: 'OpenSource') + create(:project, group: os_group) + @os_user1 = create(:user) + @os_user2 = create(:user) + os_group.add_owner(@os_user1) + os_group.add_user(@os_user2, Gitlab::Access::DEVELOPER) + share_link = project.project_group_links.new(group_access: Gitlab::Access::MASTER) + share_link.group_id = os_group.id + share_link.save! + end + + step 'I should see "Opensource" group user listing' do + expect(page).to have_content("Shared with OpenSource group, members with Master role (2)") + expect(page).to have_content(@os_user1.name) + expect(page).to have_content(@os_user2.name) + end end diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index f32641ef0f6..fae0da9d898 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -17,6 +17,10 @@ describe ProjectsFinder do create(:project, :public, group: group, name: 'C', path: 'C') end + let!(:shared_project) do + create(:project, :private, name: 'D', path: 'D') + end + let(:finder) { described_class.new } describe 'without a group' do @@ -56,7 +60,35 @@ describe ProjectsFinder do describe 'with a user' do subject { finder.execute(user, group: group) } - it { is_expected.to eq([public_project, internal_project]) } + describe 'without shared projects' do + it { is_expected.to eq([public_project, internal_project]) } + end + + describe 'with shared projects and group membership' do + before do + group.add_user(user, Gitlab::Access::DEVELOPER) + + shared_project.project_group_links. + create(group_access: Gitlab::Access::MASTER, group: group) + end + + it do + is_expected.to eq([shared_project, public_project, internal_project]) + end + end + + describe 'with shared projects and project membership' do + before do + shared_project.team.add_user(user, Gitlab::Access::DEVELOPER) + + shared_project.project_group_links. + create(group_access: Gitlab::Access::MASTER, group: group) + end + + it do + is_expected.to eq([shared_project, public_project, internal_project]) + end + end end end end diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 7b63da005f0..bacb17a8883 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -67,6 +67,50 @@ describe ProjectTeam, models: true do end end + describe :max_invited_level do + let(:group) { create(:group) } + let(:project) { create(:empty_project) } + + before do + project.project_group_links.create( + group: group, + group_access: Gitlab::Access::DEVELOPER + ) + + group.add_user(master, Gitlab::Access::MASTER) + group.add_user(reporter, Gitlab::Access::REPORTER) + end + + it { expect(project.team.max_invited_level(master.id)).to eq(Gitlab::Access::DEVELOPER) } + it { expect(project.team.max_invited_level(reporter.id)).to eq(Gitlab::Access::REPORTER) } + it { expect(project.team.max_invited_level(nonmember.id)).to be_nil } + end + + describe :max_member_access do + let(:group) { create(:group) } + let(:project) { create(:empty_project) } + + before do + project.project_group_links.create( + group: group, + group_access: Gitlab::Access::DEVELOPER + ) + + group.add_user(master, Gitlab::Access::MASTER) + group.add_user(reporter, Gitlab::Access::REPORTER) + end + + it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::DEVELOPER) } + it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) } + it { expect(project.team.max_member_access(nonmember.id)).to be_nil } + + it "does not have an access" do + project.namespace.update(share_with_group_lock: true) + expect(project.team.max_member_access(master.id)).to be_nil + expect(project.team.max_member_access(reporter.id)).to be_nil + end + end + describe "#human_max_access" do it 'returns Master role' do user = create(:user) -- cgit v1.2.1 From e12e937b943be2d9dce062cde10b02b6474e170b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 12 Mar 2016 15:05:52 +0100 Subject: Bring share with group feature documentation from EE Signed-off-by: Dmitriy Zaporozhets --- doc/workflow/README.md | 2 ++ doc/workflow/groups/max_access_level.png | Bin 0 -> 135354 bytes .../groups/other_group_sees_shared_project.png | Bin 0 -> 118382 bytes doc/workflow/groups/share_project_with_groups.png | Bin 0 -> 118868 bytes doc/workflow/share_projects_with_other_groups.md | 30 +++++++++++++++++++++ doc/workflow/share_with_group.md | 13 +++++++++ doc/workflow/share_with_group.png | Bin 0 -> 53784 bytes 7 files changed, 45 insertions(+) create mode 100644 doc/workflow/groups/max_access_level.png create mode 100644 doc/workflow/groups/other_group_sees_shared_project.png create mode 100644 doc/workflow/groups/share_project_with_groups.png create mode 100644 doc/workflow/share_projects_with_other_groups.md create mode 100644 doc/workflow/share_with_group.md create mode 100644 doc/workflow/share_with_group.png diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 2ac32373ce9..25893f948ea 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -13,6 +13,8 @@ - [Project forking workflow](forking_workflow.md) - [Project users](add-user/add-user.md) - [Protected branches](protected_branches.md) +- [Sharing a project with a group](share_with_group.md) +- [Share projects with other groups](share_projects_with_other_groups.md) - [Web Editor](web_editor.md) - [Releases](releases.md) - [Milestones](milestones.md) diff --git a/doc/workflow/groups/max_access_level.png b/doc/workflow/groups/max_access_level.png new file mode 100644 index 00000000000..71106a8a5a0 Binary files /dev/null and b/doc/workflow/groups/max_access_level.png differ diff --git a/doc/workflow/groups/other_group_sees_shared_project.png b/doc/workflow/groups/other_group_sees_shared_project.png new file mode 100644 index 00000000000..cbf2c3c1fdc Binary files /dev/null and b/doc/workflow/groups/other_group_sees_shared_project.png differ diff --git a/doc/workflow/groups/share_project_with_groups.png b/doc/workflow/groups/share_project_with_groups.png new file mode 100644 index 00000000000..a5dbc89fe90 Binary files /dev/null and b/doc/workflow/groups/share_project_with_groups.png differ diff --git a/doc/workflow/share_projects_with_other_groups.md b/doc/workflow/share_projects_with_other_groups.md new file mode 100644 index 00000000000..4c59f59c587 --- /dev/null +++ b/doc/workflow/share_projects_with_other_groups.md @@ -0,0 +1,30 @@ +# Share Projects with other Groups + +In GitLab Enterprise Edition you can share projects with other groups. +This makes it possible to add a group of users to a project with a single action. + +## Groups as collections of users + +In GitLab Community Edition groups are used primarily to [create collections of projects](groups.md). +In GitLab Enterprise Edition you can also take advantage of the fact that groups define collections of _users_, namely the group members. + +## Sharing a project with a group of users + +The primary mechanism to give a group of users, say 'Engineering', access to a project, say 'Project Acme', in GitLab is to make the 'Engineering' group the owner of 'Project Acme'. +But what if 'Project Acme' already belongs to another group, say 'Open Source'? +This is where the (Enterprise Edition only) group sharing feature can be of use. + +To share 'Project Acme' with the 'Engineering' group, go to the project settings page for 'Project Acme' and use the left navigation menu to go to the 'Groups' section. + +![The 'Groups' section in the project settings screen (Enterprise Edition only)](groups/share_project_with_groups.png) + +Now you can add the 'Engineering' group with the maximum access level of your choice. +After sharing 'Project Acme' with 'Engineering', the project is listed on the group dashboard. + +!['Project Acme' is listed as a shared project for 'Engineering'](groups/other_group_sees_shared_project.png) + +## Maximum access level + +!['Project Acme' is shared with 'Engineering' with a maximum access level of 'Developer'](groups/max_access_level.png) + +In the screenshot above, the maximum access level of 'Developer' for members from 'Engineering' means that users with higher access levels in 'Engineering' ('Master' or 'Owner') will only have 'Developer' access to 'Project Acme'. diff --git a/doc/workflow/share_with_group.md b/doc/workflow/share_with_group.md new file mode 100644 index 00000000000..3b7690973cb --- /dev/null +++ b/doc/workflow/share_with_group.md @@ -0,0 +1,13 @@ +# Sharing a project with a group + +If you want to share a single project in a group with another group, +you can do so easily. By setting the permission you can quickly +give a select group of users access to a project in a restricted manner. + +In a project go to the project settings -> groups. + +Now you can select a group that you want to share this project with and with +which maximum access level. Users in that group are able to access this project +with their set group access level, up to the maximum level that you've set. + +![Share a project with a group](share_with_group.png) diff --git a/doc/workflow/share_with_group.png b/doc/workflow/share_with_group.png new file mode 100644 index 00000000000..a0ca6f14552 Binary files /dev/null and b/doc/workflow/share_with_group.png differ -- cgit v1.2.1 From 3b76b73ab178f0504e2a9be934fb13f239c81857 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Sat, 12 Mar 2016 15:44:48 +0100 Subject: Removed User#project_relations GitLab EE adds an extra relation that selects a "project_id" column instead of an "id" column, making it very hard for this method to be re-used in EE. Since using User#authorized_groups in ProjectsFinder#all_groups apparently has no performance impact we can just use it and keep everything compatible with EE. --- app/finders/projects_finder.rb | 2 +- app/models/user.rb | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 2b8fba77bb1..0e5a8f5ee0f 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -53,7 +53,7 @@ class ProjectsFinder def all_projects(current_user) if current_user [ - *current_user.project_relations, + current_user.authorized_projects, public_and_internal_projects ] else diff --git a/app/models/user.rb b/app/models/user.rb index 101303e1f1f..043bc825ade 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -442,11 +442,6 @@ class User < ActiveRecord::Base Project.where("projects.id IN (#{projects_union.to_sql})") end - # Returns all the project relations - def project_relations - [personal_projects, groups_projects, projects] - end - def owned_projects @owned_projects ||= Project.where('namespace_id IN (?) OR namespace_id = ?', @@ -835,7 +830,9 @@ class User < ActiveRecord::Base private def projects_union - Gitlab::SQL::Union.new(project_relations.map { |r| r.select(:id) }) + Gitlab::SQL::Union.new([personal_projects.select(:id), + groups_projects.select(:id), + projects.select(:id)]) end def ci_projects_union -- cgit v1.2.1 From 2563213ccf3c1b26d58292570f9170f73034b221 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 12 Mar 2016 07:49:16 -0800 Subject: Remove existing go_import.html.haml implementation --- app/controllers/projects_controller.rb | 11 ----------- app/views/projects/go_import.html.haml | 5 ----- 2 files changed, 16 deletions(-) delete mode 100644 app/views/projects/go_import.html.haml diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index aea08ecce3e..c70add86a20 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,7 +1,6 @@ class ProjectsController < ApplicationController include ExtractsPath - prepend_before_action :render_go_import, only: [:show] skip_before_action :authenticate_user!, only: [:show, :activity] before_action :project, except: [:new, :create] before_action :repository, except: [:new, :create] @@ -242,16 +241,6 @@ class ProjectsController < ApplicationController end end - def render_go_import - return unless params["go-get"] == "1" - - @namespace = params[:namespace_id] - @id = params[:project_id] || params[:id] - @id = @id.gsub(/\.git\Z/, "") - - render "go_import", layout: false - end - def repo_exists? project.repository_exists? && !project.empty_repo? end diff --git a/app/views/projects/go_import.html.haml b/app/views/projects/go_import.html.haml deleted file mode 100644 index 87ac75a350f..00000000000 --- a/app/views/projects/go_import.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -!!! 5 -%html - %head - - web_url = [Gitlab.config.gitlab.url, @namespace, @id].join('/') - %meta{name: "go-import", content: "#{web_url.split('://')[1]} git #{web_url}.git"} -- cgit v1.2.1 From bb206f947fd66460c09f464e3667b80ca14086d6 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 12 Mar 2016 07:50:02 -0800 Subject: Simplify code --- lib/gitlab/middleware/go.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb index bc9bee7cbb6..44d378642a1 100644 --- a/lib/gitlab/middleware/go.rb +++ b/lib/gitlab/middleware/go.rb @@ -26,11 +26,11 @@ module Gitlab end def go_request?(request) - return request["go-get"].to_i == 1 + request["go-get"].to_i == 1 end def go_body(request) - base_url = Settings.gitlab['url'] + base_url = Gitlab.config.gitlab.url # Go subpackages may be in the form of namespace/project/path1/path2/../pathN # We can just ignore the paths and leave the namespace/project path_info = request.env["PATH_INFO"] @@ -43,8 +43,7 @@ module Gitlab end def strip_url(url) - url.gsub('http://', ''). - gsub('https://', '') + url.gsub(/\Ahttps?:\/\//, '') end end end -- cgit v1.2.1 From b23a05d09bef7b10722ae7975360ad51c5d7765f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 12 Mar 2016 08:10:38 -0800 Subject: Add spec for go-import middleware --- spec/lib/gitlab/middleware/go_spec.rb | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 spec/lib/gitlab/middleware/go_spec.rb diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb new file mode 100644 index 00000000000..117a15264da --- /dev/null +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe Gitlab::Middleware::Go, lib: true do + let(:app) { double(:app) } + let(:middleware) { described_class.new(app) } + + describe '#call' do + describe 'when go-get=0' do + it 'skips go-import generation' do + env = { 'rack.input' => '', + 'QUERY_STRING' => 'go-get=0' } + expect(app).to receive(:call).with(env).and_return('no-go') + middleware.call(env) + end + end + + describe 'when go-get=1' do + it 'returns a document' do + env = { 'rack.input' => '', + 'QUERY_STRING' => 'go-get=1', + 'PATH_INFO' => '/group/project/path' } + resp = middleware.call(env) + expect(resp[0]).to eq(200) + expect(resp[1]['Content-Type']).to eq('text/html') + expected_body = "\n" + expect(resp[2].body).to eq([expected_body]) + end + end + end +end -- cgit v1.2.1 From 380a67ad4c635ca6fbfc4e8845f97788873f4e36 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 12 Mar 2016 08:10:54 -0800 Subject: Ensure PATH_INFO exists for go-get --- lib/gitlab/middleware/go.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb index 44d378642a1..50b0dd32380 100644 --- a/lib/gitlab/middleware/go.rb +++ b/lib/gitlab/middleware/go.rb @@ -26,7 +26,7 @@ module Gitlab end def go_request?(request) - request["go-get"].to_i == 1 + request["go-get"].to_i == 1 && request.env["PATH_INFO"].present? end def go_body(request) -- cgit v1.2.1 From e1dffa32db3d2ac877c0fc6f1728566d46c9e93f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 12 Mar 2016 18:01:55 +0100 Subject: Render project members from shared group Signed-off-by: Dmitriy Zaporozhets --- .../projects/project_members_controller.rb | 1 + .../project_members/_shared_group_members.html.haml | 21 +++++++++++++++++++++ app/views/projects/project_members/index.html.haml | 3 +++ 3 files changed, 25 insertions(+) create mode 100644 app/views/projects/project_members/_shared_group_members.html.haml diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 8364fc293b7..e7bddc4a6f1 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -27,6 +27,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController end @project_member = @project.project_members.new + @project_group_links = @project.project_group_links end def create diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml new file mode 100644 index 00000000000..62888e41935 --- /dev/null +++ b/app/views/projects/project_members/_shared_group_members.html.haml @@ -0,0 +1,21 @@ +- @project_group_links.each do |group_links| + - shared_group = group_links.group + - shared_group_users_count = group_links.group.group_members.count + .panel.panel-default + .panel-heading + Shared with + %strong #{shared_group.name} + group, members with + %strong #{group_links.human_access} + role (#{shared_group_users_count}) + - if current_user.can?(:admin_group, shared_group) + .panel-head-actions + = link_to group_group_members_path(shared_group), class: 'btn btn-sm' do + %i.fa.fa-pencil-square-o + Edit group members + %ul.content-list + - shared_group.group_members.order('access_level DESC').limit(20).each do |member| + = render 'groups/group_members/group_member', member: member, show_controls: false, show_roles: false + - if shared_group_users_count > 20 + %li + and #{shared_group_users_count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(shared_group)} diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 0f8848a5cbe..ebcfc907ebb 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -18,3 +18,6 @@ - if @group = render "group_members", members: @group_members + + - if @project_group_links.any? && @project.allowed_to_share_with_group? + = render "shared_group_members" -- cgit v1.2.1 From 5a586f364c5d2b866d6a074eff7996f05585b7d5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sat, 12 Mar 2016 17:42:51 +0000 Subject: Revert "Merge branch 'support-go-subpackages' into 'master' " This reverts merge request !3191 --- CHANGELOG | 1 - app/controllers/projects_controller.rb | 11 ++++++++ app/views/projects/go_import.html.haml | 5 ++++ config/initializers/go_get.rb | 1 - lib/gitlab/middleware/go.rb | 50 ---------------------------------- spec/lib/gitlab/middleware/go_spec.rb | 30 -------------------- 6 files changed, 16 insertions(+), 82 deletions(-) create mode 100644 app/views/projects/go_import.html.haml delete mode 100644 config/initializers/go_get.rb delete mode 100644 lib/gitlab/middleware/go.rb delete mode 100644 spec/lib/gitlab/middleware/go_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 07b187b31a2..d4554b96190 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.6.0 (unreleased) - - Support Golang subpackage fetching (Stan Hu) - Contributions to forked projects are included in calendar - Improve the formatting for the user page bio (Connor Shea) - Removed the default password from the initial admin account created during diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index c70add86a20..aea08ecce3e 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,6 +1,7 @@ class ProjectsController < ApplicationController include ExtractsPath + prepend_before_action :render_go_import, only: [:show] skip_before_action :authenticate_user!, only: [:show, :activity] before_action :project, except: [:new, :create] before_action :repository, except: [:new, :create] @@ -241,6 +242,16 @@ class ProjectsController < ApplicationController end end + def render_go_import + return unless params["go-get"] == "1" + + @namespace = params[:namespace_id] + @id = params[:project_id] || params[:id] + @id = @id.gsub(/\.git\Z/, "") + + render "go_import", layout: false + end + def repo_exists? project.repository_exists? && !project.empty_repo? end diff --git a/app/views/projects/go_import.html.haml b/app/views/projects/go_import.html.haml new file mode 100644 index 00000000000..87ac75a350f --- /dev/null +++ b/app/views/projects/go_import.html.haml @@ -0,0 +1,5 @@ +!!! 5 +%html + %head + - web_url = [Gitlab.config.gitlab.url, @namespace, @id].join('/') + %meta{name: "go-import", content: "#{web_url.split('://')[1]} git #{web_url}.git"} diff --git a/config/initializers/go_get.rb b/config/initializers/go_get.rb deleted file mode 100644 index 7e7896b4900..00000000000 --- a/config/initializers/go_get.rb +++ /dev/null @@ -1 +0,0 @@ -Rails.application.config.middleware.use(Gitlab::Middleware::Go) diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb deleted file mode 100644 index 50b0dd32380..00000000000 --- a/lib/gitlab/middleware/go.rb +++ /dev/null @@ -1,50 +0,0 @@ -# A dumb middleware that returns a Go HTML document if the go-get=1 query string -# is used irrespective if the namespace/project exists -module Gitlab - module Middleware - class Go - def initialize(app) - @app = app - end - - def call(env) - request = Rack::Request.new(env) - - if go_request?(request) - render_go_doc(request) - else - @app.call(env) - end - end - - private - - def render_go_doc(request) - body = go_body(request) - response = Rack::Response.new(body, 200, { 'Content-Type' => 'text/html' }) - response.finish - end - - def go_request?(request) - request["go-get"].to_i == 1 && request.env["PATH_INFO"].present? - end - - def go_body(request) - base_url = Gitlab.config.gitlab.url - # Go subpackages may be in the form of namespace/project/path1/path2/../pathN - # We can just ignore the paths and leave the namespace/project - path_info = request.env["PATH_INFO"] - path_info.sub!(/^\//, '') - project_path = path_info.split('/').first(2).join('/') - request_url = URI.join(base_url, project_path) - domain_path = strip_url(request_url.to_s) - - "\n"; - end - - def strip_url(url) - url.gsub(/\Ahttps?:\/\//, '') - end - end - end -end diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb deleted file mode 100644 index 117a15264da..00000000000 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Middleware::Go, lib: true do - let(:app) { double(:app) } - let(:middleware) { described_class.new(app) } - - describe '#call' do - describe 'when go-get=0' do - it 'skips go-import generation' do - env = { 'rack.input' => '', - 'QUERY_STRING' => 'go-get=0' } - expect(app).to receive(:call).with(env).and_return('no-go') - middleware.call(env) - end - end - - describe 'when go-get=1' do - it 'returns a document' do - env = { 'rack.input' => '', - 'QUERY_STRING' => 'go-get=1', - 'PATH_INFO' => '/group/project/path' } - resp = middleware.call(env) - expect(resp[0]).to eq(200) - expect(resp[1]['Content-Type']).to eq('text/html') - expected_body = "\n" - expect(resp[2].body).to eq([expected_body]) - end - end - end -end -- cgit v1.2.1 From b9d13c11dee8f555b0d80fd5b9b6a42be7721461 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Thu, 10 Mar 2016 01:49:13 +0000 Subject: implements upcoming filter in milstones --- app/finders/issuable_finder.rb | 8 ++++++++ app/helpers/milestones_helper.rb | 1 + app/models/milestone.rb | 1 + 3 files changed, 10 insertions(+) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index c88a420b412..410e6f6456c 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -94,6 +94,10 @@ class IssuableFinder params[:milestone_title].present? end + def upcoming? + params[:milestone_title] == 'Upcoming' + end + def filter_by_no_milestone? milestones? && params[:milestone_title] == Milestone::None.title end @@ -248,6 +252,10 @@ class IssuableFinder if milestones? if filter_by_no_milestone? items = items.where(milestone_id: [-1, nil]) + elsif upcoming? + upcoming = Milestone.where(project_id: projects) + .where('due_date > ?', Time.now).order(due_date: :asc).first + items = items.joins(:milestone).where(milestone: { title: upcoming.title }) else items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] }) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index e3e7daa49c5..e8ac8788d9d 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -59,6 +59,7 @@ module MilestonesHelper grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date } grouped_milestones.unshift(Milestone::None) grouped_milestones.unshift(Milestone::Any) + grouped_milestones.unshift(Milestone::Upcoming) options_from_collection_for_select(grouped_milestones, 'name', 'title', params[:milestone_title]) end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index e3b6c552f92..85f7d8a7754 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -19,6 +19,7 @@ class Milestone < ActiveRecord::Base MilestoneStruct = Struct.new(:title, :name, :id) None = MilestoneStruct.new('No Milestone', 'No Milestone', 0) Any = MilestoneStruct.new('Any Milestone', '', -1) + Upcoming = MilestoneStruct.new('Upcoming', '', -2) include InternalId include Sortable -- cgit v1.2.1 From 7530827ecae0596616623d1c4f7775b08d5ada3c Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Fri, 11 Mar 2016 17:46:14 +0000 Subject: fixes issues for mr acceptance --- app/finders/issuable_finder.rb | 13 ++++++------- app/models/milestone.rb | 7 ++++++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 410e6f6456c..d592bdd0eb5 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -94,10 +94,6 @@ class IssuableFinder params[:milestone_title].present? end - def upcoming? - params[:milestone_title] == 'Upcoming' - end - def filter_by_no_milestone? milestones? && params[:milestone_title] == Milestone::None.title end @@ -248,14 +244,17 @@ class IssuableFinder items end + def upcoming? + params[:milestone_title] == '#upcoming' && projects + end + def by_milestone(items) if milestones? if filter_by_no_milestone? items = items.where(milestone_id: [-1, nil]) elsif upcoming? - upcoming = Milestone.where(project_id: projects) - .where('due_date > ?', Time.now).order(due_date: :asc).first - items = items.joins(:milestone).where(milestone: { title: upcoming.title }) + upcoming = Milestone.upcoming(projects) + items = items.joins(:milestone).where(milestones: { title: upcoming.title }) else items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] }) diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 85f7d8a7754..7697072d231 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -19,7 +19,7 @@ class Milestone < ActiveRecord::Base MilestoneStruct = Struct.new(:title, :name, :id) None = MilestoneStruct.new('No Milestone', 'No Milestone', 0) Any = MilestoneStruct.new('Any Milestone', '', -1) - Upcoming = MilestoneStruct.new('Upcoming', '', -2) + Upcoming = MilestoneStruct.new('Upcoming', '#upcoming', -2) include InternalId include Sortable @@ -82,6 +82,11 @@ class Milestone < ActiveRecord::Base super("milestones", /(?\d+)/) end + def self.upcoming(projects) + self.where(project_id: projects) + .where('due_date > ?', Time.now). order(due_date: :asc).first + end + def to_reference(from_project = nil) escaped_title = self.title.gsub("]", "\\]") -- cgit v1.2.1 From 0291473b98b457e85657aac29520b5f2b5b631ec Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Sat, 12 Mar 2016 16:54:05 +0000 Subject: fixes issues --- app/finders/issuable_finder.rb | 4 ++-- app/models/milestone.rb | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index d592bdd0eb5..5e22aca45f7 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -245,7 +245,7 @@ class IssuableFinder end def upcoming? - params[:milestone_title] == '#upcoming' && projects + params[:milestone_title] == '#upcoming' end def by_milestone(items) @@ -253,7 +253,7 @@ class IssuableFinder if filter_by_no_milestone? items = items.where(milestone_id: [-1, nil]) elsif upcoming? - upcoming = Milestone.upcoming(projects) + upcoming = Milestone.where(project_id: projects).upcoming items = items.joins(:milestone).where(milestones: { title: upcoming.title }) else items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] }) diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 7697072d231..374590ba0c5 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -82,9 +82,8 @@ class Milestone < ActiveRecord::Base super("milestones", /(?\d+)/) end - def self.upcoming(projects) - self.where(project_id: projects) - .where('due_date > ?', Time.now). order(due_date: :asc).first + def self.upcoming + self.where('due_date > ?', Time.now).order(due_date: :asc).first end def to_reference(from_project = nil) -- cgit v1.2.1 From 8e6bd86cf26dd6e659fc7ccaf7361029b256891a Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Sat, 12 Mar 2016 17:24:33 +0000 Subject: adds my name to changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index d4554b96190..afef4b578c4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -35,6 +35,7 @@ v 8.5.5 - Prevent a 500 error in Todos when author was removed - Fix pagination for filtered dashboard and explore pages - Fix "Show all" link behavior + - Add #upcoming filter to Milestone filter (Tiago Botelho) v 8.5.4 - Do not cache requests for badges (including builds badge) -- cgit v1.2.1 From b94bdb698dedb519a8478440a3dc8a763856b6a0 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Sat, 12 Mar 2016 17:44:29 +0000 Subject: removes tab and replaces with space in changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index afef4b578c4..2535a78af48 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.5.5 - Fix pagination for filtered dashboard and explore pages - Fix "Show all" link behavior - Add #upcoming filter to Milestone filter (Tiago Botelho) + - Add #upcoming filter to Milestone filter (Tiago Botelho) v 8.5.4 - Do not cache requests for badges (including builds badge) -- cgit v1.2.1 From 19d1455ac136ab06c2153714761eadfb9ada0f0d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 12 Mar 2016 20:22:38 +0200 Subject: Change notes to new styleguide using blockquotes --- doc/ci/yaml/README.md | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index fb62ed25d64..a4d621c0e57 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -116,7 +116,8 @@ Alias for [stages](#stages). ### variables -_**Note:** Introduced in GitLab Runner v0.5.0._ +>**Note:** +Introduced in GitLab Runner v0.5.0. GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build environment. The variables are stored in the git repository and are meant to @@ -153,7 +154,8 @@ cache: #### cache:key -_**Note:** Introduced in GitLab Runner v1.0.0._ +>**Note:** +Introduced in GitLab Runner v1.0.0. The `key` directive allows you to define the affinity of caching between jobs, allowing to have a single cache for all jobs, @@ -394,12 +396,12 @@ The above script will: ### artifacts -_**Note:** Introduced in GitLab Runner v0.7.0 for non-Windows platforms._ - -_**Note:** Limited Windows support was added in GitLab Runner v.1.0.0. -Currently not all executors are supported._ - -_**Note:** Build artifacts are only collected for successful builds._ +>**Notes:** +> +> - Introduced in GitLab Runner v0.7.0 for non-Windows platforms. +> - Limited Windows support was added in GitLab Runner v.1.0.0. +> - Currently not all executors are supported. +> - Build artifacts are only collected for successful builds. `artifacts` is used to specify list of files and directories which should be attached to build after success. Below are some examples. @@ -456,12 +458,13 @@ be available for download in the GitLab UI. #### artifacts:name -_**Note:** Introduced in GitLab 8.6 and GitLab Runner v1.1.0._ +>**Note:** +Introduced in GitLab 8.6 and GitLab Runner v1.1.0. The `name` directive allows you to define the name of created artifacts archive. -Currently the `artifacts` is used. -It may be useful when you will want to download the archive from GitLab. +Currently the `artifacts` is used. +It may be useful when you will want to download the archive from GitLab. You could possible have the unique name of every archive. The `artifacts:name` variable can use any of the [predefined variables](../variables/README.md). @@ -517,13 +520,14 @@ job: ### dependencies -_**Note:** Introduced in GitLab 8.6 and GitLab Runner v1.1.1._ +>**Note:** +Introduced in GitLab 8.6 and GitLab Runner v1.1.1. This feature should be used with `artifacts` and allows to define artifacts passing between different builds. `artifacts` from previous stages are passed by default. -To use a feature define `dependencies` in context of the build and pass +To use a feature define `dependencies` in context of the build and pass a list of all previous builds from which the artifacts should be downloaded. You can only define a builds from stages that are executed before this one. Error will be shown if you define builds from current stage or next stages. @@ -537,7 +541,7 @@ build:osx: artifacts: paths: - binaries/ - + build:linux: stage: build script: ... @@ -571,7 +575,8 @@ However, only the `build:osx` and `build:linux` exports artifacts so only these ### cache -_**Note:** Introduced in GitLab Runner v0.7.0._ +>**Note:** +Introduced in GitLab Runner v0.7.0. `cache` is used to specify list of files and directories which should be cached between builds. Below are some examples: @@ -665,7 +670,7 @@ It will create a two jobs: `test1` and `test2` that will have the parameters of - postgres - ruby -.mysql_services: +.mysql_services: services: &mysql_definition - mysql - ruby -- cgit v1.2.1 From 3315fb820b50a5005200a41221434a2e7cf8cf14 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 12 Mar 2016 11:14:56 -0800 Subject: Remove unnecessary go-get test (superseded by middleware) --- spec/controllers/projects_controller_spec.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 6eee4dfe229..1893e946f5c 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -9,19 +9,6 @@ describe ProjectsController do describe "GET show" do - context "when requested by `go get`" do - render_views - - it "renders the go-import meta tag" do - get :show, "go-get" => "1", namespace_id: "bogus_namespace", id: "bogus_project" - - expect(response.body).to include("name='go-import'") - - content = "localhost/bogus_namespace/bogus_project git http://localhost/bogus_namespace/bogus_project.git" - expect(response.body).to include("content='#{content}'") - end - end - context "rendering default project view" do render_views -- cgit v1.2.1 From 74d7de81960e6d7037c5ae7d4ecd953ac6ea5deb Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 12 Mar 2016 21:05:23 +0000 Subject: Revert "Revert "Merge branch 'support-go-subpackages' into 'master' "" This reverts commit 5a586f364c5d2b866d6a074eff7996f05585b7d5 --- CHANGELOG | 1 + app/controllers/projects_controller.rb | 11 -------- app/views/projects/go_import.html.haml | 5 ---- config/initializers/go_get.rb | 1 + lib/gitlab/middleware/go.rb | 50 ++++++++++++++++++++++++++++++++++ spec/lib/gitlab/middleware/go_spec.rb | 30 ++++++++++++++++++++ 6 files changed, 82 insertions(+), 16 deletions(-) delete mode 100644 app/views/projects/go_import.html.haml create mode 100644 config/initializers/go_get.rb create mode 100644 lib/gitlab/middleware/go.rb create mode 100644 spec/lib/gitlab/middleware/go_spec.rb diff --git a/CHANGELOG b/CHANGELOG index d4554b96190..07b187b31a2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.6.0 (unreleased) + - Support Golang subpackage fetching (Stan Hu) - Contributions to forked projects are included in calendar - Improve the formatting for the user page bio (Connor Shea) - Removed the default password from the initial admin account created during diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index aea08ecce3e..c70add86a20 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,7 +1,6 @@ class ProjectsController < ApplicationController include ExtractsPath - prepend_before_action :render_go_import, only: [:show] skip_before_action :authenticate_user!, only: [:show, :activity] before_action :project, except: [:new, :create] before_action :repository, except: [:new, :create] @@ -242,16 +241,6 @@ class ProjectsController < ApplicationController end end - def render_go_import - return unless params["go-get"] == "1" - - @namespace = params[:namespace_id] - @id = params[:project_id] || params[:id] - @id = @id.gsub(/\.git\Z/, "") - - render "go_import", layout: false - end - def repo_exists? project.repository_exists? && !project.empty_repo? end diff --git a/app/views/projects/go_import.html.haml b/app/views/projects/go_import.html.haml deleted file mode 100644 index 87ac75a350f..00000000000 --- a/app/views/projects/go_import.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -!!! 5 -%html - %head - - web_url = [Gitlab.config.gitlab.url, @namespace, @id].join('/') - %meta{name: "go-import", content: "#{web_url.split('://')[1]} git #{web_url}.git"} diff --git a/config/initializers/go_get.rb b/config/initializers/go_get.rb new file mode 100644 index 00000000000..7e7896b4900 --- /dev/null +++ b/config/initializers/go_get.rb @@ -0,0 +1 @@ +Rails.application.config.middleware.use(Gitlab::Middleware::Go) diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb new file mode 100644 index 00000000000..50b0dd32380 --- /dev/null +++ b/lib/gitlab/middleware/go.rb @@ -0,0 +1,50 @@ +# A dumb middleware that returns a Go HTML document if the go-get=1 query string +# is used irrespective if the namespace/project exists +module Gitlab + module Middleware + class Go + def initialize(app) + @app = app + end + + def call(env) + request = Rack::Request.new(env) + + if go_request?(request) + render_go_doc(request) + else + @app.call(env) + end + end + + private + + def render_go_doc(request) + body = go_body(request) + response = Rack::Response.new(body, 200, { 'Content-Type' => 'text/html' }) + response.finish + end + + def go_request?(request) + request["go-get"].to_i == 1 && request.env["PATH_INFO"].present? + end + + def go_body(request) + base_url = Gitlab.config.gitlab.url + # Go subpackages may be in the form of namespace/project/path1/path2/../pathN + # We can just ignore the paths and leave the namespace/project + path_info = request.env["PATH_INFO"] + path_info.sub!(/^\//, '') + project_path = path_info.split('/').first(2).join('/') + request_url = URI.join(base_url, project_path) + domain_path = strip_url(request_url.to_s) + + "\n"; + end + + def strip_url(url) + url.gsub(/\Ahttps?:\/\//, '') + end + end + end +end diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb new file mode 100644 index 00000000000..117a15264da --- /dev/null +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe Gitlab::Middleware::Go, lib: true do + let(:app) { double(:app) } + let(:middleware) { described_class.new(app) } + + describe '#call' do + describe 'when go-get=0' do + it 'skips go-import generation' do + env = { 'rack.input' => '', + 'QUERY_STRING' => 'go-get=0' } + expect(app).to receive(:call).with(env).and_return('no-go') + middleware.call(env) + end + end + + describe 'when go-get=1' do + it 'returns a document' do + env = { 'rack.input' => '', + 'QUERY_STRING' => 'go-get=1', + 'PATH_INFO' => '/group/project/path' } + resp = middleware.call(env) + expect(resp[0]).to eq(200) + expect(resp[1]['Content-Type']).to eq('text/html') + expected_body = "\n" + expect(resp[2].body).to eq([expected_body]) + end + end + end +end -- cgit v1.2.1 From 4e27284a7b9cbd153d61b831b3794f6a2b5ef6a1 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Sat, 12 Mar 2016 17:28:54 -0800 Subject: Remove duplicate entries. --- doc/ci/examples/README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 0a83c4dd858..cc059dc4376 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -1,14 +1,9 @@ # CI Examples -- [Testing PHP](php.md) +- [Testing a PHP application](php.md) - [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md) - [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md) - [Test a Clojure application](test-clojure-application.md) -- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) -- [Test your PHP applications](examples/php.md) -- [Test and deploy Ruby applications to Heroku](examples/test-and-deploy-ruby-application-to-heroku.md) -- [Test and deploy Python applications to Heroku](examples/test-and-deploy-python-application-to-heroku.md) -- [Test Clojure applications](examples/test-clojure-application.md) - [Using `dpl` as deployment tool](deployment/README.md) - Help your favorite programming language and GitLab by sending a merge request with a guide for that language. @@ -17,3 +12,4 @@ - [Blost post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples) +- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) -- cgit v1.2.1 From 49e51753001d0b54adf785792141844c0e89dd70 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 13 Mar 2016 09:33:10 +0200 Subject: Refactor artifacts:name --- doc/ci/yaml/README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index a4d621c0e57..83928b81594 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -461,19 +461,16 @@ be available for download in the GitLab UI. >**Note:** Introduced in GitLab 8.6 and GitLab Runner v1.1.0. -The `name` directive allows you to define the name of created artifacts archive. - -Currently the `artifacts` is used. -It may be useful when you will want to download the archive from GitLab. -You could possible have the unique name of every archive. - -The `artifacts:name` variable can use any of the [predefined variables](../variables/README.md). +The `name` directive allows you to define the name of the created artifacts +archive. That way, you can have a unique name of every archive which could be +useful when you'd like to download the archive from GitLab. The `artifacts:name` +variable can make use of any of the [predefined variables](../variables/README.md). --- **Example configurations** -To create a archive with a name of current build: +To create an archive with a name of the current build: ```yaml job: @@ -481,7 +478,8 @@ job: name: "$CI_BUILD_NAME" ``` -To create a archive with a name of current branch or tag: +To create an archive with a name of the current branch or tag including only +the files that are untracked by Git: ```yaml job: @@ -490,7 +488,8 @@ job: untracked: true ``` -To create a archive with a name of current branch or tag: +To create an archive with a name of the current build and the current branch or +tag including only the files that are untracked by Git: ```yaml job: @@ -499,7 +498,7 @@ job: untracked: true ``` -To create a archive with a name of stage and branch name: +To create an archive with a name of the current [stage](#stages) and branch name: ```yaml job: @@ -508,6 +507,8 @@ job: untracked: true ``` +--- + If you use **Windows Batch** to run your shell scripts you need to replace `$` with `%`: -- cgit v1.2.1 From 372abbe7f95d89290d46ef356fc13cc0a886c317 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 13 Mar 2016 09:34:46 +0200 Subject: Refactor YAML anchors and explain the examples --- doc/ci/yaml/README.md | 104 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 82 insertions(+), 22 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 83928b81594..244cec71c8c 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -631,35 +631,78 @@ rspec: The cache is provided on best effort basis, so don't expect that cache will be always present. For implementation details please check GitLab Runner. -## Special features +## Special YAML features -It's possible special YAML features like anchors and map merging. -Thus allowing to greatly reduce the complexity of `.gitlab-ci.yml`. +It's possible to use special YAML features like anchors (`&`), aliases (`*`) +and map merging (`<<`), which will allow you to greatly reduce the complexity +of `.gitlab-ci.yml`. -#### Anchors +Read more about the various [YAML features](https://learnxinyminutes.com/docs/yaml/). -You can read more about YAML features [here](https://learnxinyminutes.com/docs/yaml/). +### Anchors + +>**Note:** +Introduced in GitLab 8.6 and GitLab Runner v1.1.1. + +YAML also has a handy feature called 'anchors', which let you easily duplicate +content across your document. Anchors can be used to duplicate/inherit +properties, and is a perfect example to be used with [hidden jobs](#hidden-jobs) +to provide templates for your jobs. + +The following example uses anchors and map merging. It will create two jobs, +`test1` and `test2`, that will inherit the parameters of `.job_template`, each +having their own custom `script` defined: ```yaml -.job_template: &job_definition +.job_template: &job_definition # Hidden job that defines an anchor named 'job_definition' image: ruby:2.1 services: - postgres - redis test1: - << *job_definition + <<: *job_definition # Merge the contents of the 'job_definition' alias script: - - test project + - test1 project test2: - << *job_definition + <<: *job_definition # Merge the contents of the 'job_definition' alias script: - - test project + - test2 project +``` + +`&` sets up the name of the anchor (`job_definition`), `<<` means "merge the +given hash into the current one", and `*` includes the named anchor +(`job_definition` again). The expanded version looks like this: + +```yaml +.job_template: + image: ruby:2.1 + services: + - postgres + - redis + +test1: + image: ruby:2.1 + services: + - postgres + - redis + script: + - test1 project + +test2: + image: ruby:2.1 + services: + - postgres + - redis + script: + - test2 project ``` -The above example uses anchors and map merging. -It will create a two jobs: `test1` and `test2` that will have the parameters of `.job_template` and custom `script` defined. +Let's see another one example. This time we will use anchors to define two sets +of services. This will create two jobs, `test:postgres` and `test:mysql`, that +will share the `script` directive defined in `.job_template`, and the `services` +directive defined in `.postgres_services` and `.mysql_services` respectively: ```yaml .job_template: &job_definition @@ -685,22 +728,39 @@ test:mysql: services: *mysql_definition ``` -The above example uses anchors to define two set of services. -It will create a two jobs: `test:postgres` and `test:mysql` that will have the script defined in `.job_template` -and one, the service defined in `.postgres_services` and second the services defined in `.mysql_services`. +The expanded version looks like this: -### Hidden jobs +```yaml +.job_template: + script: + - test project -The jobs that start with `.` will be not processed by GitLab. +.postgres_services: + services: + - postgres + - ruby -Example of such hidden jobs: -```yaml -.job_name: +.mysql_services: + services: + - mysql + - ruby + +test:postgres: script: - - rake spec + - test project + services: + - postgres + - ruby + +test:mysql: + script: + - test project + services: + - mysql + - ruby ``` -The `.job_name` will be ignored. You can use this feature to ignore jobs, or use them as templates with special YAML features. +You can see that the hidden jobs are conveniently used as templates. ## Validate the .gitlab-ci.yml -- cgit v1.2.1 From de7c3316b08e7763ac860503578e7fa4c78d2b9d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 13 Mar 2016 09:35:40 +0200 Subject: Add hidden jobs --- doc/ci/yaml/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 244cec71c8c..5f3a53dcf8e 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -631,6 +631,24 @@ rspec: The cache is provided on best effort basis, so don't expect that cache will be always present. For implementation details please check GitLab Runner. +## Hidden jobs + +>**Note:** +Introduced in GitLab 8.6 and GitLab Runner v1.1.1. + +Jobs that start with a dot (`.`) will be not processed by GitLab CI. You can +use this feature to ignore jobs, or use the +[special YAML features](#special-yaml-features) and transform the hidden jobs +into templates. + +In the following example, `.job_name` will be ignored: + +```yaml +.job_name: + script: + - rake spec +``` + ## Special YAML features It's possible to use special YAML features like anchors (`&`), aliases (`*`) -- cgit v1.2.1 From d8eeeb692ed61454eb06e3276f368f4dc0f11d81 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 13 Mar 2016 09:57:36 +0200 Subject: Refactor dependencies directive [ci skip] --- doc/ci/yaml/README.md | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 5f3a53dcf8e..beaa86250a9 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -236,14 +236,14 @@ job_name: | Keyword | Required | Description | |---------------|----------|-------------| | script | yes | Defines a shell script which is executed by runner | -| stage | no (default: `test`) | Defines a build stage | +| stage | no | Defines a build stage (default: `test`) | | type | no | Alias for `stage` | | only | no | Defines a list of git refs for which build is created | | except | no | Defines a list of git refs for which build is not created | | tags | no | Defines a list of tags which are used to select runner | | allow_failure | no | Allow build to fail. Failed build doesn't contribute to commit status | | when | no | Define when to run build. Can be `on_success`, `on_failure` or `always` | -| dependencies | no | Define a builds that this build depends on | +| dependencies | no | Define other builds that a build depends on so that you can pass artifacts between them| | artifacts | no | Define list build artifacts | | cache | no | Define list of files that should be cached between subsequent runs | @@ -404,7 +404,10 @@ The above script will: > - Build artifacts are only collected for successful builds. `artifacts` is used to specify list of files and directories which should be -attached to build after success. Below are some examples. +attached to build after success. To pass artifacts between different builds, +see [dependencies](#dependencies). + +Below are some examples. Send all files in `binaries` and `.config`: @@ -524,56 +527,58 @@ job: >**Note:** Introduced in GitLab 8.6 and GitLab Runner v1.1.1. -This feature should be used with `artifacts` and allows to define artifacts passing between different builds. +This feature should be used in conjunction with [`artifacts`](#artifacts) and +allows you to define the artifacts to pass between different builds. -`artifacts` from previous stages are passed by default. +Note that `artifacts` from previous [stages](#stages) are passed by default. -To use a feature define `dependencies` in context of the build and pass +To use this feature, define `dependencies` in context of the job and pass a list of all previous builds from which the artifacts should be downloaded. -You can only define a builds from stages that are executed before this one. -Error will be shown if you define builds from current stage or next stages. +You can only define builds from stages that are executed before the current one. +An error will be shown if you define builds from the current stage or next ones. + +--- -How to use artifacts passing between stages: +In the following example, we define two jobs with artifacts, `build:osx` and +`build:linux`. When the `test:osx` is executed, the artifacts from `build:osx` +will be downloaded and extracted in the context of the build. The same happens +for `test:linux` and artifacts from `build:linux`. + +The job `deploy` will download artifacts from all previous builds because of +the [stage](#stages) precedence: ``` build:osx: stage: build - script: ... + script: make build:osx artifacts: paths: - binaries/ build:linux: stage: build - script: ... + script: make build:linux artifacts: paths: - binaries/ test:osx: stage: test - script: ... + script: make test:osx dependencies: - build:osx test:linux: stage: test - script: ... + script: make test:linux dependencies: - build:linux deploy: stage: deploy - script: ... + script: make deploy ``` -The above will create a build artifacts for two jobs: `build:osx` and `build:linux`. -When executing the `test:osx` the artifacts for `build:osx` will be downloaded and extracted in context of the build. -The same happens for `test:linux` and artifacts from `build:linux`. - -The job `deploy` will download artifacts from all previous builds. -However, only the `build:osx` and `build:linux` exports artifacts so only these will be downloaded. - ### cache >**Note:** -- cgit v1.2.1 From e8b3b92ddebc47595fe4b69dc5b5a3a6dd1365ab Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 13 Mar 2016 11:46:16 +0100 Subject: Bring share project with group API from EE Signed-off-by: Dmitriy Zaporozhets --- doc/api/projects.md | 14 ++++++++++++++ lib/api/entities.rb | 4 ++++ lib/api/projects.rb | 27 +++++++++++++++++++++++++++ spec/requests/api/projects_spec.rb | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/doc/api/projects.md b/doc/api/projects.md index 9e9486cd87a..3703f4b327a 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -619,6 +619,20 @@ Revoking team membership for a user who is not currently a team member is consid Please note that the returned JSON currently differs slightly. Thus you should not rely on the returned JSON structure. +### Share project with group + +Allow to share project with group. + +``` +POST /projects/:id/share +``` + +Parameters: + +- `id` (required) - The ID of a project +- `group_id` (required) - The ID of a group +- `group_access` (required) - Level of permissions for sharing + ## Hooks Also called Project Hooks and Webhooks. diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 5b5b8bd044b..d96eaef7c0a 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -243,6 +243,10 @@ module API end end + class ProjectGroupLink < Grape::Entity + expose :id, :project_id, :group_id, :group_access + end + class Namespace < Grape::Entity expose :id, :path, :kind end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 6067c8b4a5e..6fcb5261e40 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -290,6 +290,33 @@ module API end end + # Share project with group + # + # Parameters: + # id (required) - The ID of a project + # group_id (required) - The ID of a group + # group_access (required) - Level of permissions for sharing + # + # Example Request: + # POST /projects/:id/share + post ":id/share" do + authorize! :admin_project, user_project + required_attributes! [:group_id, :group_access] + + unless user_project.allowed_to_share_with_group? + return render_api_error!("The project sharing with group is disabled", 400) + end + + link = user_project.project_group_links.new + link.group_id = params[:group_id] + link.group_access = params[:group_access] + if link.save + present link, with: Entities::ProjectGroupLink + else + render_api_error!(link.errors.full_messages.first, 409) + end + end + # Upload a file # # Parameters: diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 9f2365a4832..a6699cdc81c 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -747,6 +747,42 @@ describe API::API, api: true do end end + describe "POST /projects/:id/share" do + let(:group) { create(:group) } + + it "should share project with group" do + expect do + post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER + end.to change { ProjectGroupLink.count }.by(1) + + expect(response.status).to eq 201 + expect(json_response['group_id']).to eq group.id + expect(json_response['group_access']).to eq Gitlab::Access::DEVELOPER + end + + it "should return a 400 error when group id is not given" do + post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER + expect(response.status).to eq 400 + end + + it "should return a 400 error when access level is not given" do + post api("/projects/#{project.id}/share", user), group_id: group.id + expect(response.status).to eq 400 + end + + it "should return a 400 error when sharing is disabled" do + project.namespace.update(share_with_group_lock: true) + post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER + expect(response.status).to eq 400 + end + + it "should return a 409 error when wrong params passed" do + post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 + expect(response.status).to eq 409 + expect(json_response['message']).to eq 'Group access is not included in the list' + end + end + describe 'GET /projects/search/:query' do let!(:query) { 'query'} let!(:search) { create(:empty_project, name: query, creator_id: user.id, namespace: user.namespace) } -- cgit v1.2.1 From 1cefb73a9c067b1e2367a28b5c6852cf52d6b886 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 7 Mar 2016 10:06:54 +0100 Subject: Check redirect path in the continue_params Fixes https://dev.gitlab.org/gitlab/gitlabhq/issues/2649 https://gitlab.com/gitlab-org/gitlab-ce/issues/13956 --- app/controllers/concerns/continue_to_params.rb | 13 +++++++++++++ app/controllers/projects/forks_controller.rb | 13 ++----------- app/controllers/projects/imports_controller.rb | 12 ++---------- 3 files changed, 17 insertions(+), 21 deletions(-) create mode 100644 app/controllers/concerns/continue_to_params.rb diff --git a/app/controllers/concerns/continue_to_params.rb b/app/controllers/concerns/continue_to_params.rb new file mode 100644 index 00000000000..8b6c7051968 --- /dev/null +++ b/app/controllers/concerns/continue_to_params.rb @@ -0,0 +1,13 @@ +module ContinueToParams + extend ActiveSupport::Concern + + def continue_params + continue_params = params[:continue] + return nil unless continue_params + + continue_params = continue_params.permit(:to, :notice, :notice_now) + continue_params[:to] = root_url unless continue_params[:to].start_with?('/') + + continue_params + end +end diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index 7b202f3862f..c4884c13b12 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -1,4 +1,6 @@ class Projects::ForksController < Projects::ApplicationController + include ContinueToParams + # Authorize before_action :require_non_empty_project before_action :authorize_download_code! @@ -53,15 +55,4 @@ class Projects::ForksController < Projects::ApplicationController render :error end end - - private - - def continue_params - continue_params = params[:continue] - if continue_params - continue_params.permit(:to, :notice, :notice_now) - else - nil - end - end end diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index 196996f1752..3756fc9139c 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -1,4 +1,6 @@ class Projects::ImportsController < Projects::ApplicationController + include ContinueToParams + # Authorize before_action :authorize_admin_project! before_action :require_no_repo, only: [:new, :create] @@ -44,16 +46,6 @@ class Projects::ImportsController < Projects::ApplicationController private - def continue_params - continue_params = params[:continue] - - if continue_params - continue_params.permit(:to, :notice, :notice_now) - else - nil - end - end - def finished_notice if @project.forked? 'The project was successfully forked.' -- cgit v1.2.1 From dfb96ed84bd7533abc411b148f0b27bf65321b3e Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 7 Mar 2016 10:36:16 +0100 Subject: ContinueToParams -> ContinueParams --- CHANGELOG | 1 + app/controllers/concerns/continue_params.rb | 13 +++++++++++++ app/controllers/concerns/continue_to_params.rb | 13 ------------- app/controllers/projects/forks_controller.rb | 2 +- app/controllers/projects/imports_controller.rb | 2 +- 5 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 app/controllers/concerns/continue_params.rb delete mode 100644 app/controllers/concerns/continue_to_params.rb diff --git a/CHANGELOG b/CHANGELOG index d4554b96190..1929b6306db 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -45,6 +45,7 @@ v 8.5.3 - Show commit message in JIRA mention comment - Makes issue page and merge request page usable on mobile browsers. - Improved UI for profile settings + - Continue parameters are checked to ensure redirection goes to the same instance v 8.5.2 - Fix sidebar overlapping content when screen width was below 1200px diff --git a/app/controllers/concerns/continue_params.rb b/app/controllers/concerns/continue_params.rb new file mode 100644 index 00000000000..2ff7250922d --- /dev/null +++ b/app/controllers/concerns/continue_params.rb @@ -0,0 +1,13 @@ +module ContinueParams + extend ActiveSupport::Concern + + def continue_params + continue_params = params[:continue] + return nil unless continue_params + + continue_params = continue_params.permit(:to, :notice, :notice_now) + return unless continue_params[:to] && continue_params[:to].start_with?('/') + + continue_params + end +end diff --git a/app/controllers/concerns/continue_to_params.rb b/app/controllers/concerns/continue_to_params.rb deleted file mode 100644 index 8b6c7051968..00000000000 --- a/app/controllers/concerns/continue_to_params.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ContinueToParams - extend ActiveSupport::Concern - - def continue_params - continue_params = params[:continue] - return nil unless continue_params - - continue_params = continue_params.permit(:to, :notice, :notice_now) - continue_params[:to] = root_url unless continue_params[:to].start_with?('/') - - continue_params - end -end diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index c4884c13b12..a1b8632df98 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -1,5 +1,5 @@ class Projects::ForksController < Projects::ApplicationController - include ContinueToParams + include ContinueParams # Authorize before_action :require_non_empty_project diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index 3756fc9139c..7756f0f0ed3 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -1,5 +1,5 @@ class Projects::ImportsController < Projects::ApplicationController - include ContinueToParams + include ContinueParams # Authorize before_action :authorize_admin_project! -- cgit v1.2.1 From d3b7633da4a3964eb4609c7591763aa8e43fd0eb Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Sun, 13 Mar 2016 12:19:27 +0000 Subject: fixes small issues --- CHANGELOG | 1 - app/finders/issuable_finder.rb | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2535a78af48..b7bc48802d3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -35,7 +35,6 @@ v 8.5.5 - Prevent a 500 error in Todos when author was removed - Fix pagination for filtered dashboard and explore pages - Fix "Show all" link behavior - - Add #upcoming filter to Milestone filter (Tiago Botelho) - Add #upcoming filter to Milestone filter (Tiago Botelho) v 8.5.4 diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 5e22aca45f7..19e8c7a92be 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -244,7 +244,7 @@ class IssuableFinder items end - def upcoming? + def filter_by_upcoming_milestone? params[:milestone_title] == '#upcoming' end @@ -252,7 +252,7 @@ class IssuableFinder if milestones? if filter_by_no_milestone? items = items.where(milestone_id: [-1, nil]) - elsif upcoming? + elsif filter_by_upcoming_milestone? upcoming = Milestone.where(project_id: projects).upcoming items = items.joins(:milestone).where(milestones: { title: upcoming.title }) else -- cgit v1.2.1 From 5352ec2e21ba72d77a542b158ce1a98a1a3a9389 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 7 Mar 2016 11:45:14 +0100 Subject: Fix denting and spec --- CHANGELOG | 2 +- app/controllers/concerns/continue_params.rb | 2 +- spec/controllers/projects/imports_controller_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1929b6306db..7c63414e580 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ v 8.6.0 (unreleased) - Add main language of a project in the list of projects (Tiago Botelho) - Add ability to show archived projects on dashboard, explore and group pages - Move group activity to separate page + - Continue parameters are checked to ensure redirection goes to the same instance v 8.5.5 - Ensure removing a project removes associated Todo entries @@ -45,7 +46,6 @@ v 8.5.3 - Show commit message in JIRA mention comment - Makes issue page and merge request page usable on mobile browsers. - Improved UI for profile settings - - Continue parameters are checked to ensure redirection goes to the same instance v 8.5.2 - Fix sidebar overlapping content when screen width was below 1200px diff --git a/app/controllers/concerns/continue_params.rb b/app/controllers/concerns/continue_params.rb index 2ff7250922d..0a995c45bdf 100644 --- a/app/controllers/concerns/continue_params.rb +++ b/app/controllers/concerns/continue_params.rb @@ -5,7 +5,7 @@ module ContinueParams continue_params = params[:continue] return nil unless continue_params - continue_params = continue_params.permit(:to, :notice, :notice_now) + continue_params = continue_params.permit(:to, :notice, :notice_now) return unless continue_params[:to] && continue_params[:to].start_with?('/') continue_params diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb index 0147bd2b953..2acbba469e3 100644 --- a/spec/controllers/projects/imports_controller_spec.rb +++ b/spec/controllers/projects/imports_controller_spec.rb @@ -19,7 +19,7 @@ describe Projects::ImportsController do end it 'sets flash.now if params is present' do - get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { notice_now: 'Started' } + get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { to: '/', notice_now: 'Started' } expect(flash.now[:notice]).to eq 'Started' end @@ -45,7 +45,7 @@ describe Projects::ImportsController do end it 'sets flash.now if params is present' do - get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { notice_now: 'In progress' } + get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { to: '/', notice_now: 'In progress' } expect(flash.now[:notice]).to eq 'In progress' end -- cgit v1.2.1 From a181722ae4dc0b1bb564d34b30e30f823cc72bc0 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 13 Mar 2016 13:39:28 -0700 Subject: Improve award emoji test reliability and eliminate sleeps Closes #14129 --- features/steps/project/issues/award_emoji.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index 135e1d016ae..ce2554bc80d 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -13,7 +13,6 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps thumbsup = page.first('.award-control') thumbsup.click thumbsup.hover - sleep 0.3 end end @@ -46,12 +45,10 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end step 'I have award added' do - sleep 0.2 - page.within '.awards' do expect(page).to have_selector '.js-emoji-btn' expect(page.find('.js-emoji-btn.active .js-counter')).to have_content '1' - expect(page.find('.js-emoji-btn.active')['data-original-title']).to eq('me') + expect(page).to have_css(".js-emoji-btn.active[data-original-title='me']") end end -- cgit v1.2.1 From ab0af56201413fea81cf42367671ec9a845315c7 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 13 Mar 2016 17:22:10 -0700 Subject: Use Capybara find methods and remove sleeps in feature specs in "All Issues" filter --- app/views/shared/issuable/_filter.html.haml | 2 +- features/steps/dashboard/issues.rb | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index c3fbba2ba54..e62d80aeb2c 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -15,7 +15,7 @@ .filter-item.inline - if params[:assignee_id] = hidden_field_tag(:assignee_id, params[:assignee_id]) - = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-filter-submit", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable", + = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable", placeholder: "Search assignee", data: { any_user: "Any Author", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id" } }) .filter-item.inline.milestone-filter diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb index d723300f485..3072c790a4a 100644 --- a/features/steps/dashboard/issues.rb +++ b/features/steps/dashboard/issues.rb @@ -44,14 +44,11 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps end step 'I click "All" link' do - execute_script('$(".js-user-search").first().click()') - sleep 1 - execute_script('$(".js-user-search").first().parent().find("li a").first().click()') - sleep 1 - execute_script('$(".js-user-search").eq(1).click()') - sleep 1 - execute_script('$(".js-user-search").eq(1).parent().find("li a").first().click()') - sleep 1 + find('.js-author-search').click + find('.dropdown-menu-user-full-name', match: :first).click + + find('.js-assignee-search').click + find('.dropdown-menu-user-full-name', match: :first).click end def should_see(issue) -- cgit v1.2.1 From f0c73a5bdbbf07c6668b9ee50f38ba4f4813eabd Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 13 Mar 2016 22:09:42 -0700 Subject: Remove sleeps from network graph feature spec --- features/project/network_graph.feature | 3 ++- features/steps/project/network_graph.rb | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/features/project/network_graph.feature b/features/project/network_graph.feature index 6cc89a15a78..89a02706bd2 100644 --- a/features/project/network_graph.feature +++ b/features/project/network_graph.feature @@ -34,9 +34,10 @@ Feature: Project Network Graph @javascript Scenario: I should filter selected tag When I switch ref to "v1.0.0" + Then page should have "v1.0.0" in title Then page should have content not containing "v1.0.0" When click "Show only selected branch" checkbox - Then page should not have content not containing "v1.0.0" + Then page should only have content from "v1.0.0" When click "Show only selected branch" checkbox Then page should have content not containing "v1.0.0" diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb index 7a83d32a240..9b59b682676 100644 --- a/features/steps/project/network_graph.rb +++ b/features/steps/project/network_graph.rb @@ -41,17 +41,14 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps When 'I switch ref to "feature"' do select 'feature', from: 'ref' - sleep 2 end When 'I switch ref to "v1.0.0"' do select 'v1.0.0', from: 'ref' - sleep 2 end When 'click "Show only selected branch" checkbox' do find('#filter_ref').click - sleep 2 end step 'page should have content not containing "v1.0.0"' do @@ -60,7 +57,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps end end - step 'page should not have content not containing "v1.0.0"' do + step 'page should have "v1.0.0" in title' do + expect(page).to have_css 'title', text: 'Network ยท v1.0.0', visible: false + end + + step 'page should only have content from "v1.0.0"' do page.within '.network-graph' do expect(page).not_to have_content 'Change some files' end -- cgit v1.2.1 From 7ebb932070f03e8fb6116b9f54148cae2d782776 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 14 Mar 2016 10:14:32 +0200 Subject: Add yaml definition to codeblock [ci skip] --- doc/ci/yaml/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index beaa86250a9..4d27c07913d 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -547,7 +547,7 @@ for `test:linux` and artifacts from `build:linux`. The job `deploy` will download artifacts from all previous builds because of the [stage](#stages) precedence: -``` +```yaml build:osx: stage: build script: make build:osx -- cgit v1.2.1 From 9d6e0c5071e640685f16d5f6045e89851db4b868 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 4 Mar 2016 09:45:05 +0000 Subject: Left sidebar overlaps the content on mobile devices --- app/assets/javascripts/sidebar.js.coffee | 17 +++++++++++++---- app/assets/stylesheets/framework/header.scss | 10 +++++----- app/assets/stylesheets/framework/sidebar.scss | 8 ++++++-- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee index cff309c5972..dfa69dd6a47 100644 --- a/app/assets/javascripts/sidebar.js.coffee +++ b/app/assets/javascripts/sidebar.js.coffee @@ -1,8 +1,8 @@ -$(document).on("click", '.toggle-nav-collapse', (e) -> - e.preventDefault() - collapsed = 'page-sidebar-collapsed' - expanded = 'page-sidebar-expanded' +mobileWidth = 768 +collapsed = 'page-sidebar-collapsed' +expanded = 'page-sidebar-expanded' +toggleSidebar = -> $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") $('header').toggleClass("header-collapsed header-expanded") $('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded") @@ -14,4 +14,13 @@ $(document).on("click", '.toggle-nav-collapse', (e) -> niceScrollBars.updateScrollBar(); ), 300 +$(document).on("click", '.toggle-nav-collapse', (e) -> + e.preventDefault() + + toggleSidebar() ) + +$(document).ready -> + if $(window).width() < mobileWidth + if $('.page-with-sidebar').hasClass(expanded) + toggleSidebar() diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index e624982c5c9..3d3d3295615 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -141,17 +141,17 @@ header { margin-left: $sidebar_collapsed_width; } -@media (max-width: $screen-md-max) { +@media (max-width: $screen-sm) { .header-collapsed { margin-left: $sidebar_collapsed_width; } - .header-expanded { - margin-left: $sidebar_width; - } + .header-expanded { + margin-left: $sidebar_collapsed_width; + } } -@media(min-width: $screen-md-max) { +@media(min-width: $screen-sm) { .header-collapsed { @include collapsed-header; } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 6b382e4b1b2..0f83bd8b556 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -39,7 +39,7 @@ } .sidebar-wrapper { - z-index: 99; + z-index: 999; background: $background-color; } @@ -203,7 +203,11 @@ } @mixin expanded-sidebar { - padding-left: $sidebar_width; + padding-left: $sidebar_collapsed_width; + + @media (min-width: $screen-sm-min) { + padding-left: $sidebar_width; + } &.right-sidebar-collapsed { /* Extra small devices (phones, less than 768px) */ -- cgit v1.2.1 From b155d54941ab5bb4eb07c1774ba3a2dfbb9f2707 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 10 Mar 2016 16:55:44 +0000 Subject: Removed deprecated bootstrap variables --- app/assets/stylesheets/framework/header.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 3d3d3295615..9aa97f6ac47 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -141,7 +141,7 @@ header { margin-left: $sidebar_collapsed_width; } -@media (max-width: $screen-sm) { +@media (max-width: $screen-sm-min) { .header-collapsed { margin-left: $sidebar_collapsed_width; } @@ -151,7 +151,7 @@ header { } } -@media(min-width: $screen-sm) { +@media(min-width: $screen-sm-min) { .header-collapsed { @include collapsed-header; } -- cgit v1.2.1 From ce9bbce78a258b43181766bcbebad7add90968ff Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 10 Mar 2016 17:04:45 +0000 Subject: changed jquery document ready --- app/assets/javascripts/sidebar.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee index dfa69dd6a47..1ffb6619ab5 100644 --- a/app/assets/javascripts/sidebar.js.coffee +++ b/app/assets/javascripts/sidebar.js.coffee @@ -20,7 +20,7 @@ $(document).on("click", '.toggle-nav-collapse', (e) -> toggleSidebar() ) -$(document).ready -> +$ -> if $(window).width() < mobileWidth if $('.page-with-sidebar').hasClass(expanded) toggleSidebar() -- cgit v1.2.1 From f0f6723fe02bfb1a79c1cbefed156a420dddc352 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 11 Mar 2016 15:11:11 +0000 Subject: Created bootstrap breakpoint class This class checks the current bootstrap breakpoint --- app/assets/javascripts/application.js.coffee | 27 ++++----------------------- app/assets/javascripts/breakpoints.coffee | 24 ++++++++++++++++++++++++ app/assets/javascripts/sidebar.js.coffee | 7 +++++-- app/assets/stylesheets/framework/header.scss | 18 +++++++----------- app/assets/stylesheets/framework/sidebar.scss | 2 +- 5 files changed, 41 insertions(+), 37 deletions(-) create mode 100644 app/assets/javascripts/breakpoints.coffee diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 1212e89975b..9e114a80c27 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -256,35 +256,15 @@ $ -> $('.right-sidebar') .hasClass('right-sidebar-collapsed'), { path: '/' }) - bootstrapBreakpoint = undefined; - checkBootstrapBreakpoints = -> - if $('.device-xs').is(':visible') - bootstrapBreakpoint = "xs" - else if $('.device-sm').is(':visible') - bootstrapBreakpoint = "sm" - else if $('.device-md').is(':visible') - bootstrapBreakpoint = "md" - else if $('.device-lg').is(':visible') - bootstrapBreakpoint = "lg" - - setBootstrapBreakpoints = -> - if $('.device-xs').length - return - - $("body") - .append('
      '+ - '
      '+ - '
      '+ - '
      ') - checkBootstrapBreakpoints() - fitSidebarForSize = -> oldBootstrapBreakpoint = bootstrapBreakpoint checkBootstrapBreakpoints() + bootstrapBreakpoint = breakpoints.getBreakpointSize() if bootstrapBreakpoint != oldBootstrapBreakpoint $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) checkInitialSidebarSize = -> + bootstrapBreakpoint = breakpoints.getBreakpointSize() if bootstrapBreakpoint is "xs" or "sm" $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) @@ -293,6 +273,7 @@ $ -> .on "resize", (e) -> fitSidebarForSize() - setBootstrapBreakpoints() checkInitialSidebarSize() + breakpoints = new Breakpoints() + bootstrapBreakpoint = breakpoints.getBreakpointSize() new Aside() diff --git a/app/assets/javascripts/breakpoints.coffee b/app/assets/javascripts/breakpoints.coffee new file mode 100644 index 00000000000..fd2ee8efa2c --- /dev/null +++ b/app/assets/javascripts/breakpoints.coffee @@ -0,0 +1,24 @@ +class @Breakpoints + BREAKPOINTS = ["xs", "sm", "md", "lg"] + + constructor: -> + @setup() + + setup: -> + allDeviceSelector = BREAKPOINTS.map (breakpoint) -> + ".device-#{breakpoint}" + + if $(allDeviceSelector.join(",")).length + return + + # Create all the elements + $.each BREAKPOINTS, (i, breakpoint) -> + $("body").append "
      " + + getBreakpointSize: -> + allDeviceSelector = BREAKPOINTS.map (breakpoint) -> + ".device-#{breakpoint}" + + $visibleDevice = $(allDeviceSelector.join(",")).filter(":visible") + + return $visibleDevice.attr("class").split("visible-")[1] diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee index 1ffb6619ab5..5c68690979d 100644 --- a/app/assets/javascripts/sidebar.js.coffee +++ b/app/assets/javascripts/sidebar.js.coffee @@ -1,4 +1,4 @@ -mobileWidth = 768 +mobileWidth = 991 collapsed = 'page-sidebar-collapsed' expanded = 'page-sidebar-expanded' @@ -21,6 +21,9 @@ $(document).on("click", '.toggle-nav-collapse', (e) -> ) $ -> - if $(window).width() < mobileWidth + breakpoints = new Breakpoints() + size = breakpoints.getBreakpointSize() + + if size is "xs" or size is "sm" if $('.page-with-sidebar').hasClass(expanded) toggleSidebar() diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 9aa97f6ac47..a73643b35ad 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -141,22 +141,18 @@ header { margin-left: $sidebar_collapsed_width; } -@media (max-width: $screen-sm-min) { - .header-collapsed { - margin-left: $sidebar_collapsed_width; - } +.header-collapsed { + margin-left: $sidebar_collapsed_width; - .header-expanded { - margin-left: $sidebar_collapsed_width; + @media (min-width: $screen-sm-max) { + @include collapsed-header; } } -@media(min-width: $screen-sm-min) { - .header-collapsed { - @include collapsed-header; - } +.header-expanded { + margin-left: $sidebar_collapsed_width; - .header-expanded { + @media (min-width: $screen-sm-max) { margin-left: $sidebar_width; } } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 0f83bd8b556..24608e6cff2 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -205,7 +205,7 @@ @mixin expanded-sidebar { padding-left: $sidebar_collapsed_width; - @media (min-width: $screen-sm-min) { + @media (min-width: $screen-sm-max) { padding-left: $sidebar_width; } -- cgit v1.2.1 From 23b0eeca280de8c1f36f863396cb0aee912c56b9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 11 Mar 2016 15:13:47 +0000 Subject: removed un-used function call --- app/assets/javascripts/application.js.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 9e114a80c27..beee6f2ec6d 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -108,6 +108,9 @@ window.onload = -> setTimeout shiftWindow, 100 $ -> + breakpoints = new Breakpoints() + bootstrapBreakpoint = breakpoints.getBreakpointSize() + $(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF") # Click a .js-select-on-focus field, select the contents @@ -258,7 +261,6 @@ $ -> fitSidebarForSize = -> oldBootstrapBreakpoint = bootstrapBreakpoint - checkBootstrapBreakpoints() bootstrapBreakpoint = breakpoints.getBreakpointSize() if bootstrapBreakpoint != oldBootstrapBreakpoint $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) @@ -274,6 +276,4 @@ $ -> fitSidebarForSize() checkInitialSidebarSize() - breakpoints = new Breakpoints() - bootstrapBreakpoint = breakpoints.getBreakpointSize() new Aside() -- cgit v1.2.1 From 1a5992b5eb15a58223925877e90f7642aac6ce2d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 14 Mar 2016 11:51:07 +0000 Subject: Changed breakpoint size Changed breakpoint class into singleton --- app/assets/javascripts/application.js.coffee | 7 ++--- app/assets/javascripts/breakpoints.coffee | 43 ++++++++++++++++----------- app/assets/javascripts/sidebar.js.coffee | 4 +-- app/assets/stylesheets/framework/header.scss | 4 +-- app/assets/stylesheets/framework/sidebar.scss | 4 +-- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index beee6f2ec6d..e9c6196e926 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -108,8 +108,7 @@ window.onload = -> setTimeout shiftWindow, 100 $ -> - breakpoints = new Breakpoints() - bootstrapBreakpoint = breakpoints.getBreakpointSize() + bootstrapBreakpoint = bp.getBreakpointSize() $(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF") @@ -261,12 +260,12 @@ $ -> fitSidebarForSize = -> oldBootstrapBreakpoint = bootstrapBreakpoint - bootstrapBreakpoint = breakpoints.getBreakpointSize() + bootstrapBreakpoint = bp.getBreakpointSize() if bootstrapBreakpoint != oldBootstrapBreakpoint $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) checkInitialSidebarSize = -> - bootstrapBreakpoint = breakpoints.getBreakpointSize() + bootstrapBreakpoint = bp.getBreakpointSize() if bootstrapBreakpoint is "xs" or "sm" $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) diff --git a/app/assets/javascripts/breakpoints.coffee b/app/assets/javascripts/breakpoints.coffee index fd2ee8efa2c..73a42d99982 100644 --- a/app/assets/javascripts/breakpoints.coffee +++ b/app/assets/javascripts/breakpoints.coffee @@ -1,24 +1,33 @@ class @Breakpoints - BREAKPOINTS = ["xs", "sm", "md", "lg"] + instance = null; - constructor: -> - @setup() + class BreakpointInstance + BREAKPOINTS = ["xs", "sm", "md", "lg"] - setup: -> - allDeviceSelector = BREAKPOINTS.map (breakpoint) -> - ".device-#{breakpoint}" + constructor: -> + @setup() - if $(allDeviceSelector.join(",")).length - return + setup: -> + allDeviceSelector = BREAKPOINTS.map (breakpoint) -> + ".device-#{breakpoint}" - # Create all the elements - $.each BREAKPOINTS, (i, breakpoint) -> - $("body").append "
      " + return if $(allDeviceSelector.join(",")).length - getBreakpointSize: -> - allDeviceSelector = BREAKPOINTS.map (breakpoint) -> - ".device-#{breakpoint}" + # Create all the elements + $.each BREAKPOINTS, (i, breakpoint) -> + $("body").append "
      " - $visibleDevice = $(allDeviceSelector.join(",")).filter(":visible") - - return $visibleDevice.attr("class").split("visible-")[1] + getBreakpointSize: -> + @setup() + + allDeviceSelector = BREAKPOINTS.map (breakpoint) -> + ".device-#{breakpoint}" + + $visibleDevice = $(allDeviceSelector.join(",")).filter(":visible") + + return $visibleDevice.attr("class").split("visible-")[1] + + @get: -> + return instance ?= new BreakpointInstance + +@bp = Breakpoints.get() diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee index 5c68690979d..eea3f5ee910 100644 --- a/app/assets/javascripts/sidebar.js.coffee +++ b/app/assets/javascripts/sidebar.js.coffee @@ -1,4 +1,3 @@ -mobileWidth = 991 collapsed = 'page-sidebar-collapsed' expanded = 'page-sidebar-expanded' @@ -21,8 +20,7 @@ $(document).on("click", '.toggle-nav-collapse', (e) -> ) $ -> - breakpoints = new Breakpoints() - size = breakpoints.getBreakpointSize() + size = bp.getBreakpointSize() if size is "xs" or size is "sm" if $('.page-with-sidebar').hasClass(expanded) diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index a73643b35ad..4c4033e3ae7 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -144,7 +144,7 @@ header { .header-collapsed { margin-left: $sidebar_collapsed_width; - @media (min-width: $screen-sm-max) { + @media (min-width: $screen-md-min) { @include collapsed-header; } } @@ -152,7 +152,7 @@ header { .header-expanded { margin-left: $sidebar_collapsed_width; - @media (min-width: $screen-sm-max) { + @media (min-width: $screen-md-min) { margin-left: $sidebar_width; } } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 24608e6cff2..26df9acd2ae 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -34,7 +34,7 @@ @media (min-width: $screen-sm-min) { padding-right: $gutter_width; } - + } } @@ -205,7 +205,7 @@ @mixin expanded-sidebar { padding-left: $sidebar_collapsed_width; - @media (min-width: $screen-sm-max) { + @media (min-width: $screen-md-min) { padding-left: $sidebar_width; } -- cgit v1.2.1 From a965b03369594736d586f5a04a14de4451b9edd1 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 14 Mar 2016 13:02:40 +0000 Subject: Removed call to setup from breakpoint size method --- app/assets/javascripts/breakpoints.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/breakpoints.coffee b/app/assets/javascripts/breakpoints.coffee index 73a42d99982..1ffaaddb055 100644 --- a/app/assets/javascripts/breakpoints.coffee +++ b/app/assets/javascripts/breakpoints.coffee @@ -14,12 +14,11 @@ class @Breakpoints return if $(allDeviceSelector.join(",")).length # Create all the elements - $.each BREAKPOINTS, (i, breakpoint) -> - $("body").append "
      " + els = $.map BREAKPOINTS, (breakpoint) -> + "
      " + $("body").append els.join('') getBreakpointSize: -> - @setup() - allDeviceSelector = BREAKPOINTS.map (breakpoint) -> ".device-#{breakpoint}" @@ -30,4 +29,5 @@ class @Breakpoints @get: -> return instance ?= new BreakpointInstance -@bp = Breakpoints.get() +$ => + @bp = Breakpoints.get() -- cgit v1.2.1 From 35aca2d5fdd8d59e2e8ed4e6ce28838ea5bc7f86 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 14 Mar 2016 13:49:53 +0000 Subject: Fixed tests for MR & issue filters --- app/assets/javascripts/gl_dropdown.js.coffee | 4 +++- app/views/shared/issuable/_filter.html.haml | 2 +- features/steps/dashboard/issues.rb | 8 +++----- features/steps/dashboard/merge_requests.rb | 20 +++++++------------- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 8e1449bc59c..b94de4c7b5e 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -238,13 +238,15 @@ class GitLabDropdown selectedObject = @renderedData[selectedIndex] value = if @options.id then @options.id(selectedObject, el) else selectedObject.id + if !value? + field.remove() + if @options.multiSelect oldValue = field.val() if oldValue value = "#{oldValue},#{value}" else @dropdown.find(ACTIVE_CLASS).removeClass ACTIVE_CLASS - field.remove() # Toggle active class for the tick mark el.toggleClass "is-active" diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index e62d80aeb2c..4df96a06dbe 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -15,7 +15,7 @@ .filter-item.inline - if params[:assignee_id] = hidden_field_tag(:assignee_id, params[:assignee_id]) - = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable", + = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee", placeholder: "Search assignee", data: { any_user: "Any Author", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id" } }) .filter-item.inline.milestone-filter diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb index 3072c790a4a..f4a56865532 100644 --- a/features/steps/dashboard/issues.rb +++ b/features/steps/dashboard/issues.rb @@ -36,11 +36,9 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps end step 'I click "Authored by me" link' do - execute_script('$("#assignee_id").val("")') - execute_script('$(".js-user-search").first().click()') - sleep 1 - execute_script("$('.dropdown-content li:contains(\"#{current_user.to_reference}\") a').click()") - sleep 1 + find("#assignee_id").set("") + find(".js-author-search", match: :first).click + find(".dropdown-menu-author li a", match: :first, text: current_user.to_reference).click end step 'I click "All" link' do diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb index 7fc0e444e86..a2adc87f8ef 100644 --- a/features/steps/dashboard/merge_requests.rb +++ b/features/steps/dashboard/merge_requests.rb @@ -40,22 +40,16 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps end step 'I click "Authored by me" link' do - execute_script('$("#assignee_id").val("")') - execute_script('$(".js-user-search").first().click()') - sleep 0.5 - execute_script("$('.dropdown-content li:contains(\"#{current_user.to_reference}\") a').click()") - sleep 2 + find("#assignee_id").set("") + find(".js-author-search", match: :first).click + find(".dropdown-menu-author li a", match: :first, text: current_user.to_reference).click end step 'I click "All" link' do - execute_script('$(".js-user-search").first().click()') - sleep 0.5 - execute_script('$(".js-user-search").first().parent().find("li a").first().click()') - sleep 2 - execute_script('$(".js-user-search").eq(1).click()') - sleep 0.5 - execute_script('$(".js-user-search").eq(1).parent().find("li a").first().click()') - sleep 2 + find(".js-author-search").click + find(".dropdown-menu-author li a", match: :first).click + find(".js-assignee-search").click + find(".dropdown-menu-assignee li a", match: :first).click end def should_see(merge_request) -- cgit v1.2.1 From d26bd026d92d9b06e207cc7ecf2d9e899151c155 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 14 Mar 2016 11:53:42 -0400 Subject: Update GITLAB_SHELL_VERSION to match EE [ci skip] --- GITLAB_SHELL_VERSION | 2 +- doc/update/8.5-to-8.6.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index a04abec9149..bc02b8685c1 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.6.10 +2.6.11 diff --git a/doc/update/8.5-to-8.6.md b/doc/update/8.5-to-8.6.md index 9a1b94ab369..024f6e8a433 100644 --- a/doc/update/8.5-to-8.6.md +++ b/doc/update/8.5-to-8.6.md @@ -37,7 +37,7 @@ sudo -u git -H git checkout 8-6-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all -sudo -u git -H git checkout v2.6.10 +sudo -u git -H git checkout v2.6.11 ``` ### 5. Update gitlab-workhorse -- cgit v1.2.1 From 09d3cdfd9eed89023a6dc0244df7165a1b3dc52a Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 14 Mar 2016 16:03:17 +0000 Subject: Changed project home icon Closes #14196 --- app/views/layouts/nav/_project.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 319974e12c5..0ae83ee01eb 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -16,7 +16,7 @@ = nav_link(path: 'projects#show', html_options: {class: 'home'}) do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do - = icon('home fw') + = icon('bookmark fw') %span Project = nav_link(path: 'projects#activity') do -- cgit v1.2.1