From 9a93fd9ec4e765873d49c3d8e29ca165b43ba083 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sat, 19 Aug 2017 20:41:23 +0100 Subject: Fixed fly-out nav jumping Closes #36699 --- app/assets/javascripts/fly_out_nav.js | 13 ++++++------- spec/javascripts/fly_out_nav_spec.js | 34 ++++++++++------------------------ 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js index 32cb42c8b10..81697af189b 100644 --- a/app/assets/javascripts/fly_out_nav.js +++ b/app/assets/javascripts/fly_out_nav.js @@ -1,4 +1,3 @@ -import Cookies from 'js-cookie'; import bp from './breakpoints'; const HIDE_INTERVAL_TIMEOUT = 300; @@ -8,9 +7,11 @@ const IS_SHOWING_FLY_OUT_CLASS = 'is-showing-fly-out'; let currentOpenMenu = null; let menuCornerLocs; let timeoutId; +let sidebar; export const mousePos = []; +export const setSidebar = (el) => { sidebar = el; }; export const setOpenMenu = (menu = null) => { currentOpenMenu = menu; }; export const slope = (a, b) => (b.y - a.y) / (b.x - a.x); @@ -20,10 +21,8 @@ let headerHeight = 50; export const getHeaderHeight = () => headerHeight; export const canShowActiveSubItems = (el) => { - const isHiddenByMedia = bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md'; - - if (el.classList.contains('active') && !isHiddenByMedia) { - return Cookies.get('sidebar_collapsed') === 'true'; + if (el.classList.contains('active') && (sidebar && !sidebar.classList.contains('sidebar-icons-only'))) { + return false; } return true; @@ -143,13 +142,13 @@ export const documentMouseMove = (e) => { }; export default () => { - const sidebar = document.querySelector('.sidebar-top-level-items'); + sidebar = document.querySelector('.nav-sidebar'); if (!sidebar) return; const items = [...sidebar.querySelectorAll('.sidebar-top-level-items > li')]; - sidebar.addEventListener('mouseleave', () => { + sidebar.querySelector('.sidebar-top-level-items').addEventListener('mouseleave', () => { clearTimeout(timeoutId); timeoutId = setTimeout(() => { diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js index 2e81a1b056b..0847e463577 100644 --- a/spec/javascripts/fly_out_nav_spec.js +++ b/spec/javascripts/fly_out_nav_spec.js @@ -1,4 +1,3 @@ -import Cookies from 'js-cookie'; import { calculateTop, showSubLevelItems, @@ -11,6 +10,7 @@ import { getHideSubItemsInterval, documentMouseMove, getHeaderHeight, + setSidebar, } from '~/fly_out_nav'; import bp from '~/breakpoints'; @@ -113,7 +113,6 @@ describe('Fly out sidebar navigation', () => { clientX: el.getBoundingClientRect().left + 20, clientY: el.getBoundingClientRect().top + 10, }); - console.log(el); expect( getHideSubItemsInterval(), @@ -283,7 +282,7 @@ describe('Fly out sidebar navigation', () => { describe('canShowActiveSubItems', () => { afterEach(() => { - Cookies.remove('sidebar_collapsed'); + setSidebar(null); }); it('returns true by default', () => { @@ -292,36 +291,23 @@ describe('Fly out sidebar navigation', () => { ).toBeTruthy(); }); - it('returns false when cookie is false & element is active', () => { - Cookies.set('sidebar_collapsed', 'false'); + it('returns false when active & expanded sidebar', () => { + const sidebar = document.createElement('div'); el.classList.add('active'); - expect( - canShowActiveSubItems(el), - ).toBeFalsy(); - }); - - it('returns true when cookie is false & element is active', () => { - Cookies.set('sidebar_collapsed', 'true'); - el.classList.add('active'); + setSidebar(sidebar); expect( canShowActiveSubItems(el), - ).toBeTruthy(); + ).toBeFalsy(); }); - it('returns true when element is active & breakpoint is sm', () => { - breakpointSize = 'sm'; + it('returns true when active & collapsed sidebar', () => { + const sidebar = document.createElement('div'); + sidebar.classList.add('sidebar-icons-only'); el.classList.add('active'); - expect( - canShowActiveSubItems(el), - ).toBeTruthy(); - }); - - it('returns true when element is active & breakpoint is md', () => { - breakpointSize = 'md'; - el.classList.add('active'); + setSidebar(sidebar); expect( canShowActiveSubItems(el), -- cgit v1.2.1 From c13f712c77e0837760e79293b6fb41c734741e77 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 18 Aug 2017 17:43:23 -0700 Subject: implement Repository#== so that with_repo_branch_commit can properly short-circuit --- app/models/repository.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/models/repository.rb b/app/models/repository.rb index c1e4fcf94a4..1c3080c0efd 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -60,6 +60,12 @@ class Repository @project = project end + def equals(other) + @disk_path == other.disk_path + end + + alias_method :==, :equals + def raw_repository return nil unless full_path -- cgit v1.2.1 From 00b440443808fba04fc55f7e77c61f79e29c21ea Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 21 Aug 2017 15:20:44 -0700 Subject: skip the branch fetch if we already have the sha --- app/models/repository.rb | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 1c3080c0efd..61fab9764e8 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -991,23 +991,27 @@ class Repository end def with_repo_branch_commit(start_repository, start_branch_name) - return yield(nil) if start_repository.empty_repo? + tmp_ref = nil + return yield nil if start_repository.empty_repo? - branch_name_or_sha = + branch_commit = if start_repository == self - start_branch_name + commit(start_branch_name) else - tmp_ref = fetch_ref( - start_repository.path_to_repo, - "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}", - "refs/tmp/#{SecureRandom.hex}/head" - ) - - start_repository.commit(start_branch_name).sha + sha = start_repository.find_branch(start_branch_name).target + commit(sha) || + begin + tmp_ref = fetch_ref( + start_repository.path_to_repo, + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}", + "refs/tmp/#{SecureRandom.hex}/head" + ) + + commit(start_repository.commit(start_branch_name).sha) + end end - yield(commit(branch_name_or_sha)) - + yield branch_commit ensure rugged.references.delete(tmp_ref) if tmp_ref end -- cgit v1.2.1 From 5f811894a8ba0d85298cc695c360f171d30c193c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 23 Aug 2017 20:25:44 +0800 Subject: Remove unwanted refs after importing a project Because we don't need them, and they would slow down the repository, keeping unneeded objects as well. We could also consider doing this in regular housekeeping. --- app/models/project.rb | 2 +- app/services/projects/housekeeping_service.rb | 2 ++ .../projects/import_export/cleanup_service.rb | 33 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 app/services/projects/import_export/cleanup_service.rb diff --git a/app/models/project.rb b/app/models/project.rb index 37f4dd08355..72da4e8eb2e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -372,7 +372,7 @@ class Project < ActiveRecord::Base if Gitlab::ImportSources.importer_names.include?(project.import_type) && project.repo_exists? project.run_after_commit do begin - Projects::HousekeepingService.new(project).execute + Projects::ImportExport::CleanupService.new(project).execute rescue Projects::HousekeepingService::LeaseTaken => e Rails.logger.info("Could not perform housekeeping for project #{project.full_path} (#{project.id}): #{e}") end diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb index d66ef676088..dcef8b66215 100644 --- a/app/services/projects/housekeeping_service.rb +++ b/app/services/projects/housekeeping_service.rb @@ -26,6 +26,8 @@ module Projects lease_uuid = try_obtain_lease raise LeaseTaken unless lease_uuid.present? + yield if block_given? + execute_gitlab_shell_gc(lease_uuid) end diff --git a/app/services/projects/import_export/cleanup_service.rb b/app/services/projects/import_export/cleanup_service.rb new file mode 100644 index 00000000000..54d7fb88a91 --- /dev/null +++ b/app/services/projects/import_export/cleanup_service.rb @@ -0,0 +1,33 @@ +module Projects + module ImportExport + class CleanupService + RESERVED_REFS_REGEXP = + %r{\Arefs/(?:heads|tags|merge\-requests|keep\-around|environments)/} + + attr_reader :project + + def initialize(project) + @project = project + end + + # This could raise Projects::HousekeepingService::LeaseTaken + def execute + Projects::HousekeepingService.new(project).execute do + garbage_refs.each(&rugged.references.method(:delete)) + end + end + + private + + def garbage_refs + @garbage_refs ||= rugged.references.reject do |ref| + ref.name =~ RESERVED_REFS_REGEXP + end + end + + def rugged + @rugged ||= project.repository.rugged + end + end + end +end -- cgit v1.2.1 From 140ac8d2ad81f03f67dddcb565458e9baee79755 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 23 Aug 2017 21:51:21 +0800 Subject: Add changelog and tests --- .../projects/import_export/cleanup_service.rb | 5 +- .../36807-gc-unwanted-refs-after-import.yml | 5 ++ spec/models/project_spec.rb | 13 ++++- .../services/projects/housekeeping_service_spec.rb | 13 +++++ .../projects/import_export/cleanup_service_spec.rb | 55 ++++++++++++++++++++++ 5 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/36807-gc-unwanted-refs-after-import.yml create mode 100644 spec/services/projects/import_export/cleanup_service_spec.rb diff --git a/app/services/projects/import_export/cleanup_service.rb b/app/services/projects/import_export/cleanup_service.rb index 54d7fb88a91..75eaee0cb7b 100644 --- a/app/services/projects/import_export/cleanup_service.rb +++ b/app/services/projects/import_export/cleanup_service.rb @@ -1,8 +1,11 @@ module Projects module ImportExport class CleanupService + RESERVED_REFS_NAMES = + %w[heads tags merge-requests keep-around environments] RESERVED_REFS_REGEXP = - %r{\Arefs/(?:heads|tags|merge\-requests|keep\-around|environments)/} + %r{\Arefs/(?:#{ + RESERVED_REFS_NAMES.map(&Regexp.method(:escape)).join('|')})/}x attr_reader :project diff --git a/changelogs/unreleased/36807-gc-unwanted-refs-after-import.yml b/changelogs/unreleased/36807-gc-unwanted-refs-after-import.yml new file mode 100644 index 00000000000..a37de4325bb --- /dev/null +++ b/changelogs/unreleased/36807-gc-unwanted-refs-after-import.yml @@ -0,0 +1,5 @@ +--- +title: Remove unwanted refs after importing a project +merge_request: 13766 +author: +type: other diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 2e613c44357..130c0739033 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1563,10 +1563,18 @@ describe Project do describe 'project import state transitions' do context 'state transition: [:started] => [:finished]' do - let(:housekeeping_service) { spy } + let(:cleanup_service) { spy(:cleanup_service) } + let(:housekeeping_service) { spy(:housekeeping_service) } before do - allow(Projects::HousekeepingService).to receive(:new) { housekeeping_service } + allow(Projects::ImportExport::CleanupService) + .to receive(:new) { cleanup_service } + + allow(cleanup_service) + .to receive(:execute) { housekeeping_service.execute } + + allow(Projects::HousekeepingService) + .to receive(:new) { housekeeping_service } end it 'resets project import_error' do @@ -1581,6 +1589,7 @@ describe Project do project.import_finish + expect(cleanup_service).to have_received(:execute) expect(housekeeping_service).to have_received(:execute) end diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index 385f56e447f..6e916a523fe 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -23,6 +23,12 @@ describe Projects::HousekeepingService do expect(project.reload.pushes_since_gc).to eq(0) end + it 'yields the block if given' do + expect do |b| + subject.execute(&b) + end.to yield_with_no_args + end + context 'when no lease can be obtained' do before do expect(subject).to receive(:try_obtain_lease).and_return(false) @@ -39,6 +45,13 @@ describe Projects::HousekeepingService do expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken) end.not_to change { project.pushes_since_gc } end + + it 'does not yield' do + expect do |b| + expect { subject.execute(&b) } + .to raise_error(Projects::HousekeepingService::LeaseTaken) + end.not_to yield_with_no_args + end end end diff --git a/spec/services/projects/import_export/cleanup_service_spec.rb b/spec/services/projects/import_export/cleanup_service_spec.rb new file mode 100644 index 00000000000..b46efc40a2f --- /dev/null +++ b/spec/services/projects/import_export/cleanup_service_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Projects::ImportExport::CleanupService do + subject { described_class.new(project) } + + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:sha) { project.commit.sha } + let(:housekeeping_service) { double(:housekeeping_service) } + + describe '#execute' do + before do + allow(Projects::HousekeepingService) + .to receive(:new).with(project).and_return(housekeeping_service) + + allow(housekeeping_service) + .to receive(:execute).and_yield + end + + it 'performs housekeeping' do + subject.execute + + expect(housekeeping_service).to have_received(:execute) + end + + context 'with some refs in refs/pull/**/*' do + before do + repository.write_ref('refs/pull/1/head', sha) + repository.write_ref('refs/pull/1/merge', sha) + + subject.execute + end + + it 'removes refs/pull/**/*' do + expect(repository.rugged.references.map(&:name)) + .not_to include(%r{\Arefs/pull/}) + end + end + + described_class::RESERVED_REFS_NAMES.each do |name| + context "with a ref in refs/#{name}/tmp" do + before do + repository.write_ref("refs/#{name}/tmp", sha) + + subject.execute + end + + it "does not remove refs/#{name}/tmp" do + expect(repository.rugged.references.map(&:name)) + .to include("refs/#{name}/tmp") + end + end + end + end +end -- cgit v1.2.1 From bea29327af6d14bea58f82304115735fd0b13d3f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 24 Aug 2017 17:26:18 +0800 Subject: Just use methods over constants, so that we could take the advantage that if they're not used, we could garbage collect those data, reducing memory footprint. --- .../projects/import_export/cleanup_service.rb | 20 ++++++++++++++------ .../projects/import_export/cleanup_service_spec.rb | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/services/projects/import_export/cleanup_service.rb b/app/services/projects/import_export/cleanup_service.rb index 75eaee0cb7b..2e6bfb99189 100644 --- a/app/services/projects/import_export/cleanup_service.rb +++ b/app/services/projects/import_export/cleanup_service.rb @@ -1,11 +1,15 @@ module Projects module ImportExport class CleanupService - RESERVED_REFS_NAMES = + def self.reserved_refs_names %w[heads tags merge-requests keep-around environments] - RESERVED_REFS_REGEXP = - %r{\Arefs/(?:#{ - RESERVED_REFS_NAMES.map(&Regexp.method(:escape)).join('|')})/}x + end + + def self.reserved_refs_regexp + names = reserved_refs_names.map(&Regexp.method(:escape)).join('|') + + %r{\Arefs/(?:#{names})/} + end attr_reader :project @@ -23,8 +27,12 @@ module Projects private def garbage_refs - @garbage_refs ||= rugged.references.reject do |ref| - ref.name =~ RESERVED_REFS_REGEXP + @garbage_refs ||= begin + reserved_refs_regexp = self.class.reserved_refs_regexp + + rugged.references.reject do |ref| + ref.name =~ reserved_refs_regexp + end end end diff --git a/spec/services/projects/import_export/cleanup_service_spec.rb b/spec/services/projects/import_export/cleanup_service_spec.rb index b46efc40a2f..108d0ea2ecc 100644 --- a/spec/services/projects/import_export/cleanup_service_spec.rb +++ b/spec/services/projects/import_export/cleanup_service_spec.rb @@ -37,7 +37,7 @@ describe Projects::ImportExport::CleanupService do end end - described_class::RESERVED_REFS_NAMES.each do |name| + described_class.reserved_refs_names.each do |name| context "with a ref in refs/#{name}/tmp" do before do repository.write_ref("refs/#{name}/tmp", sha) -- cgit v1.2.1 From cc2c737dea3c90d465e423e102fb634c531bdcc9 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 24 Aug 2017 17:30:54 +0800 Subject: Just use @project directly --- app/services/projects/import_export/cleanup_service.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/services/projects/import_export/cleanup_service.rb b/app/services/projects/import_export/cleanup_service.rb index 2e6bfb99189..25bff3468a9 100644 --- a/app/services/projects/import_export/cleanup_service.rb +++ b/app/services/projects/import_export/cleanup_service.rb @@ -11,15 +11,13 @@ module Projects %r{\Arefs/(?:#{names})/} end - attr_reader :project - def initialize(project) @project = project end # This could raise Projects::HousekeepingService::LeaseTaken def execute - Projects::HousekeepingService.new(project).execute do + Projects::HousekeepingService.new(@project).execute do garbage_refs.each(&rugged.references.method(:delete)) end end @@ -37,7 +35,7 @@ module Projects end def rugged - @rugged ||= project.repository.rugged + @rugged ||= @project.repository.rugged end end end -- cgit v1.2.1 From 5c31c72048d418d88d59abbdb117ec5413eb5f83 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 24 Aug 2017 17:36:16 +0800 Subject: Use block rather than just b --- spec/services/projects/housekeeping_service_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index 6e916a523fe..9386c110385 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -24,8 +24,8 @@ describe Projects::HousekeepingService do end it 'yields the block if given' do - expect do |b| - subject.execute(&b) + expect do |block| + subject.execute(&block) end.to yield_with_no_args end @@ -47,8 +47,8 @@ describe Projects::HousekeepingService do end it 'does not yield' do - expect do |b| - expect { subject.execute(&b) } + expect do |block| + expect { subject.execute(&block) } .to raise_error(Projects::HousekeepingService::LeaseTaken) end.not_to yield_with_no_args end -- cgit v1.2.1 From 5ced2d8d7d9107f031894c5b16908db8bf6b913f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 23 Aug 2017 15:09:20 +0200 Subject: Rename CI/CD job triggering policy class to Policy --- lib/gitlab/ci/config/entry/job.rb | 4 +- lib/gitlab/ci/config/entry/policy.rb | 18 ++++++++ lib/gitlab/ci/config/entry/trigger.rb | 18 -------- spec/lib/gitlab/ci/config/entry/policy_spec.rb | 56 +++++++++++++++++++++++++ spec/lib/gitlab/ci/config/entry/trigger_spec.rb | 56 ------------------------- 5 files changed, 76 insertions(+), 76 deletions(-) create mode 100644 lib/gitlab/ci/config/entry/policy.rb delete mode 100644 lib/gitlab/ci/config/entry/trigger.rb create mode 100644 spec/lib/gitlab/ci/config/entry/policy_spec.rb delete mode 100644 spec/lib/gitlab/ci/config/entry/trigger_spec.rb diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index 32f5c6ab142..91aac6df4b1 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -59,10 +59,10 @@ module Gitlab entry :services, Entry::Services, description: 'Services that will be used to execute this job.' - entry :only, Entry::Trigger, + entry :only, Entry::Policy, description: 'Refs policy this job will be executed for.' - entry :except, Entry::Trigger, + entry :except, Entry::Policy, description: 'Refs policy this job will be executed for.' entry :variables, Entry::Variables, diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb new file mode 100644 index 00000000000..a08ab8a9d14 --- /dev/null +++ b/lib/gitlab/ci/config/entry/policy.rb @@ -0,0 +1,18 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a trigger policy for the job. + # + class Policy < Node + include Validatable + + validations do + validates :config, array_of_strings_or_regexps: true + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/trigger.rb b/lib/gitlab/ci/config/entry/trigger.rb deleted file mode 100644 index 16b234e6c59..00000000000 --- a/lib/gitlab/ci/config/entry/trigger.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Gitlab - module Ci - class Config - module Entry - ## - # Entry that represents a trigger policy for the job. - # - class Trigger < Node - include Validatable - - validations do - validates :config, array_of_strings_or_regexps: true - end - end - end - end - end -end diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb new file mode 100644 index 00000000000..ac57e3ef539 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Entry::Policy do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is valid' do + context 'when config is a branch or tag name' do + let(:config) { %w[master feature/branch] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns key value' do + expect(entry.value).to eq config + end + end + end + + context 'when config is a regexp' do + let(:config) { ['/^issue-.*$/'] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when config is a special keyword' do + let(:config) { %w[tags triggers branches] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + end + + context 'when entry value is not valid' do + let(:config) { [1] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'policy config should be an array of strings or regexps' + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/trigger_spec.rb b/spec/lib/gitlab/ci/config/entry/trigger_spec.rb deleted file mode 100644 index e4ee44f1274..00000000000 --- a/spec/lib/gitlab/ci/config/entry/trigger_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::Config::Entry::Trigger do - let(:entry) { described_class.new(config) } - - describe 'validations' do - context 'when entry config value is valid' do - context 'when config is a branch or tag name' do - let(:config) { %w[master feature/branch] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - - describe '#value' do - it 'returns key value' do - expect(entry.value).to eq config - end - end - end - - context 'when config is a regexp' do - let(:config) { ['/^issue-.*$/'] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when config is a special keyword' do - let(:config) { %w[tags triggers branches] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - end - - context 'when entry value is not valid' do - let(:config) { [1] } - - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include 'trigger config should be an array of strings or regexps' - end - end - end - end -end -- cgit v1.2.1 From 932d32515a72bc80e021474100f677d954f3822e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 24 Aug 2017 18:58:31 +0800 Subject: Move to Projects::HousecleaningService --- app/models/project.rb | 2 +- app/services/projects/housecleaning_service.rb | 40 ++++++++++++++++ .../projects/import_export/cleanup_service.rb | 42 ----------------- spec/models/project_spec.rb | 10 ++-- .../projects/housecleaning_service_spec.rb | 55 ++++++++++++++++++++++ .../projects/import_export/cleanup_service_spec.rb | 55 ---------------------- 6 files changed, 101 insertions(+), 103 deletions(-) create mode 100644 app/services/projects/housecleaning_service.rb delete mode 100644 app/services/projects/import_export/cleanup_service.rb create mode 100644 spec/services/projects/housecleaning_service_spec.rb delete mode 100644 spec/services/projects/import_export/cleanup_service_spec.rb diff --git a/app/models/project.rb b/app/models/project.rb index 72da4e8eb2e..c0060504d74 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -372,7 +372,7 @@ class Project < ActiveRecord::Base if Gitlab::ImportSources.importer_names.include?(project.import_type) && project.repo_exists? project.run_after_commit do begin - Projects::ImportExport::CleanupService.new(project).execute + Projects::HousecleaningService.new(project).execute rescue Projects::HousekeepingService::LeaseTaken => e Rails.logger.info("Could not perform housekeeping for project #{project.full_path} (#{project.id}): #{e}") end diff --git a/app/services/projects/housecleaning_service.rb b/app/services/projects/housecleaning_service.rb new file mode 100644 index 00000000000..d5cf8478e13 --- /dev/null +++ b/app/services/projects/housecleaning_service.rb @@ -0,0 +1,40 @@ +module Projects + class HousecleaningService + def self.reserved_refs_names + %w[heads tags merge-requests keep-around environments] + end + + def self.reserved_refs_regexp + names = reserved_refs_names.map(&Regexp.method(:escape)).join('|') + + %r{\Arefs/(?:#{names})/} + end + + def initialize(project) + @project = project + end + + # This could raise Projects::HousekeepingService::LeaseTaken + def execute + Projects::HousekeepingService.new(@project).execute do + garbage_refs.each(&rugged.references.method(:delete)) + end + end + + private + + def garbage_refs + @garbage_refs ||= begin + reserved_refs_regexp = self.class.reserved_refs_regexp + + rugged.references.reject do |ref| + ref.name =~ reserved_refs_regexp + end + end + end + + def rugged + @rugged ||= @project.repository.rugged + end + end +end diff --git a/app/services/projects/import_export/cleanup_service.rb b/app/services/projects/import_export/cleanup_service.rb deleted file mode 100644 index 25bff3468a9..00000000000 --- a/app/services/projects/import_export/cleanup_service.rb +++ /dev/null @@ -1,42 +0,0 @@ -module Projects - module ImportExport - class CleanupService - def self.reserved_refs_names - %w[heads tags merge-requests keep-around environments] - end - - def self.reserved_refs_regexp - names = reserved_refs_names.map(&Regexp.method(:escape)).join('|') - - %r{\Arefs/(?:#{names})/} - end - - def initialize(project) - @project = project - end - - # This could raise Projects::HousekeepingService::LeaseTaken - def execute - Projects::HousekeepingService.new(@project).execute do - garbage_refs.each(&rugged.references.method(:delete)) - end - end - - private - - def garbage_refs - @garbage_refs ||= begin - reserved_refs_regexp = self.class.reserved_refs_regexp - - rugged.references.reject do |ref| - ref.name =~ reserved_refs_regexp - end - end - end - - def rugged - @rugged ||= @project.repository.rugged - end - end - end -end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 130c0739033..7631207b1d0 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1563,14 +1563,14 @@ describe Project do describe 'project import state transitions' do context 'state transition: [:started] => [:finished]' do - let(:cleanup_service) { spy(:cleanup_service) } + let(:housecleaning_service) { spy(:housecleaning_service) } let(:housekeeping_service) { spy(:housekeeping_service) } before do - allow(Projects::ImportExport::CleanupService) - .to receive(:new) { cleanup_service } + allow(Projects::HousecleaningService) + .to receive(:new) { housecleaning_service } - allow(cleanup_service) + allow(housecleaning_service) .to receive(:execute) { housekeeping_service.execute } allow(Projects::HousekeepingService) @@ -1589,7 +1589,7 @@ describe Project do project.import_finish - expect(cleanup_service).to have_received(:execute) + expect(housecleaning_service).to have_received(:execute) expect(housekeeping_service).to have_received(:execute) end diff --git a/spec/services/projects/housecleaning_service_spec.rb b/spec/services/projects/housecleaning_service_spec.rb new file mode 100644 index 00000000000..3dd7906dc9a --- /dev/null +++ b/spec/services/projects/housecleaning_service_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Projects::HousecleaningService do + subject { described_class.new(project) } + + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:sha) { project.commit.sha } + let(:housekeeping_service) { double(:housekeeping_service) } + + describe '#execute' do + before do + allow(Projects::HousekeepingService) + .to receive(:new).with(project).and_return(housekeeping_service) + + allow(housekeeping_service) + .to receive(:execute).and_yield + end + + it 'performs housekeeping' do + subject.execute + + expect(housekeeping_service).to have_received(:execute) + end + + context 'with some refs in refs/pull/**/*' do + before do + repository.write_ref('refs/pull/1/head', sha) + repository.write_ref('refs/pull/1/merge', sha) + + subject.execute + end + + it 'removes refs/pull/**/*' do + expect(repository.rugged.references.map(&:name)) + .not_to include(%r{\Arefs/pull/}) + end + end + + described_class.reserved_refs_names.each do |name| + context "with a ref in refs/#{name}/tmp" do + before do + repository.write_ref("refs/#{name}/tmp", sha) + + subject.execute + end + + it "does not remove refs/#{name}/tmp" do + expect(repository.rugged.references.map(&:name)) + .to include("refs/#{name}/tmp") + end + end + end + end +end diff --git a/spec/services/projects/import_export/cleanup_service_spec.rb b/spec/services/projects/import_export/cleanup_service_spec.rb deleted file mode 100644 index 108d0ea2ecc..00000000000 --- a/spec/services/projects/import_export/cleanup_service_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'spec_helper' - -describe Projects::ImportExport::CleanupService do - subject { described_class.new(project) } - - let(:project) { create(:project, :repository) } - let(:repository) { project.repository } - let(:sha) { project.commit.sha } - let(:housekeeping_service) { double(:housekeeping_service) } - - describe '#execute' do - before do - allow(Projects::HousekeepingService) - .to receive(:new).with(project).and_return(housekeeping_service) - - allow(housekeeping_service) - .to receive(:execute).and_yield - end - - it 'performs housekeeping' do - subject.execute - - expect(housekeeping_service).to have_received(:execute) - end - - context 'with some refs in refs/pull/**/*' do - before do - repository.write_ref('refs/pull/1/head', sha) - repository.write_ref('refs/pull/1/merge', sha) - - subject.execute - end - - it 'removes refs/pull/**/*' do - expect(repository.rugged.references.map(&:name)) - .not_to include(%r{\Arefs/pull/}) - end - end - - described_class.reserved_refs_names.each do |name| - context "with a ref in refs/#{name}/tmp" do - before do - repository.write_ref("refs/#{name}/tmp", sha) - - subject.execute - end - - it "does not remove refs/#{name}/tmp" do - expect(repository.rugged.references.map(&:name)) - .to include("refs/#{name}/tmp") - end - end - end - end -end -- cgit v1.2.1 From 0d7d7c1057c80e930d56363f2efd0519e1462586 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 24 Aug 2017 14:54:27 +0200 Subject: Use aspect-oriented design in CI/CD config entries --- lib/gitlab/ci/config/entry/configurable.rb | 3 +- lib/gitlab/ci/config/entry/node.rb | 11 ++++--- lib/gitlab/ci/config/entry/validatable.rb | 11 +++++++ lib/gitlab/ci/config/entry/validator.rb | 2 +- .../gitlab/ci/config/entry/configurable_spec.rb | 36 +++++++--------------- .../lib/gitlab/ci/config/entry/validatable_spec.rb | 10 +++--- 6 files changed, 36 insertions(+), 37 deletions(-) diff --git a/lib/gitlab/ci/config/entry/configurable.rb b/lib/gitlab/ci/config/entry/configurable.rb index e05aca9881b..68b6742385a 100644 --- a/lib/gitlab/ci/config/entry/configurable.rb +++ b/lib/gitlab/ci/config/entry/configurable.rb @@ -15,9 +15,10 @@ module Gitlab # module Configurable extend ActiveSupport::Concern - include Validatable included do + include Validatable + validations do validates :config, type: Hash end diff --git a/lib/gitlab/ci/config/entry/node.rb b/lib/gitlab/ci/config/entry/node.rb index a6a914d79c1..2474684e07f 100644 --- a/lib/gitlab/ci/config/entry/node.rb +++ b/lib/gitlab/ci/config/entry/node.rb @@ -16,8 +16,9 @@ module Gitlab @metadata = metadata @entries = {} - @validator = self.class.validator.new(self) - @validator.validate(:new) + self.class.aspects.to_a.each do |aspect| + instance_exec(&aspect) + end end def [](key) @@ -47,7 +48,7 @@ module Gitlab end def errors - @validator.messages + descendants.flat_map(&:errors) + [] end def value @@ -79,8 +80,8 @@ module Gitlab def self.default end - def self.validator - Validator + def self.aspects + @aspects ||= [] end end end diff --git a/lib/gitlab/ci/config/entry/validatable.rb b/lib/gitlab/ci/config/entry/validatable.rb index f7f1b111571..5ced778d311 100644 --- a/lib/gitlab/ci/config/entry/validatable.rb +++ b/lib/gitlab/ci/config/entry/validatable.rb @@ -5,6 +5,17 @@ module Gitlab module Validatable extend ActiveSupport::Concern + def self.included(node) + node.aspects.append -> do + @validator = self.class.validator.new(self) + @validator.validate(:new) + end + end + + def errors + @validator.messages + descendants.flat_map(&:errors) + end + class_methods do def validator @validator ||= Class.new(Entry::Validator).tap do |validator| diff --git a/lib/gitlab/ci/config/entry/validator.rb b/lib/gitlab/ci/config/entry/validator.rb index 55343005fe3..5ab54d7e218 100644 --- a/lib/gitlab/ci/config/entry/validator.rb +++ b/lib/gitlab/ci/config/entry/validator.rb @@ -30,7 +30,7 @@ module Gitlab def key_name if key.blank? - @entry.class.name.demodulize.underscore.humanize + @entry.class.name.to_s.demodulize.underscore.humanize else key end diff --git a/spec/lib/gitlab/ci/config/entry/configurable_spec.rb b/spec/lib/gitlab/ci/config/entry/configurable_spec.rb index ae7e628b5b5..088d4b472da 100644 --- a/spec/lib/gitlab/ci/config/entry/configurable_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/configurable_spec.rb @@ -1,40 +1,26 @@ require 'spec_helper' describe Gitlab::Ci::Config::Entry::Configurable do - let(:entry) { Class.new } - - before do - entry.include(described_class) + let(:entry) do + Class.new(Gitlab::Ci::Config::Entry::Node) do + include Gitlab::Ci::Config::Entry::Configurable + end end describe 'validations' do - let(:validator) { entry.validator.new(instance) } - - before do - entry.class_eval do - attr_reader :config + context 'when entry is a hash' do + let(:instance) { entry.new(key: 'value') } - def initialize(config) - @config = config - end + it 'correctly validates an instance' do + expect(instance).to be_valid end - - validator.validate end - context 'when entry validator is invalid' do + context 'when entry is not a hash' do let(:instance) { entry.new('ls') } - it 'returns invalid validator' do - expect(validator).to be_invalid - end - end - - context 'when entry instance is valid' do - let(:instance) { entry.new(key: 'value') } - - it 'returns valid validator' do - expect(validator).to be_valid + it 'invalidates the instance' do + expect(instance).not_to be_valid end end end diff --git a/spec/lib/gitlab/ci/config/entry/validatable_spec.rb b/spec/lib/gitlab/ci/config/entry/validatable_spec.rb index d1856801827..ae2a7a51ba6 100644 --- a/spec/lib/gitlab/ci/config/entry/validatable_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/validatable_spec.rb @@ -1,10 +1,10 @@ require 'spec_helper' describe Gitlab::Ci::Config::Entry::Validatable do - let(:entry) { Class.new } - - before do - entry.include(described_class) + let(:entry) do + Class.new(Gitlab::Ci::Config::Entry::Node) do + include Gitlab::Ci::Config::Entry::Validatable + end end describe '.validator' do @@ -28,7 +28,7 @@ describe Gitlab::Ci::Config::Entry::Validatable do end context 'when validating entry instance' do - let(:entry_instance) { entry.new } + let(:entry_instance) { entry.new('something') } context 'when attribute is valid' do before do -- cgit v1.2.1 From 8c409fc40ba4bf2e7fe0c8458fd2b59c09bd123a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Aug 2017 09:49:18 +0200 Subject: Make it possible to define CI/CD config strategies --- lib/gitlab/ci/config/entry/policy.rb | 25 ++++++++-- lib/gitlab/ci/config/entry/simplifiable.rb | 31 ++++++++++++ spec/lib/gitlab/ci/config/entry/policy_spec.rb | 67 ++++++++++++++------------ 3 files changed, 88 insertions(+), 35 deletions(-) create mode 100644 lib/gitlab/ci/config/entry/simplifiable.rb diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb index a08ab8a9d14..ac564a6c7d0 100644 --- a/lib/gitlab/ci/config/entry/policy.rb +++ b/lib/gitlab/ci/config/entry/policy.rb @@ -5,11 +5,28 @@ module Gitlab ## # Entry that represents a trigger policy for the job. # - class Policy < Node - include Validatable + class Policy < Simplifiable + strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) } + strategy :ExpressionsPolicy, if: -> (config) { config.is_a?(Hash) } - validations do - validates :config, array_of_strings_or_regexps: true + class RefsPolicy < Entry::Node + include Entry::Validatable + + validations do + validates :config, array_of_strings_or_regexps: true + end + + def value + { refs: @config } + end + end + + class ExpressionsPolicy < Entry::Node + include Entry::Validatable + + validations do + validates :config, type: Hash + end end end end diff --git a/lib/gitlab/ci/config/entry/simplifiable.rb b/lib/gitlab/ci/config/entry/simplifiable.rb new file mode 100644 index 00000000000..5319065b846 --- /dev/null +++ b/lib/gitlab/ci/config/entry/simplifiable.rb @@ -0,0 +1,31 @@ +module Gitlab + module Ci + class Config + module Entry + class Simplifiable < SimpleDelegator + EntryStrategy = Struct.new(:name, :condition) + + def initialize(config, **metadata) + strategy = self.class.strategies.find do |variant| + variant.condition.call(config) + end + + entry = self.class.const_get(strategy.name) + + super(entry.new(config, metadata)) + end + + def self.strategy(name, **opts) + EntryStrategy.new(name, opts.fetch(:if)).tap do |strategy| + (@strategies ||= []).append(strategy) + end + end + + def self.strategies + @strategies || [] + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index ac57e3ef539..fe9a053c46c 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -3,54 +3,59 @@ require 'spec_helper' describe Gitlab::Ci::Config::Entry::Policy do let(:entry) { described_class.new(config) } - describe 'validations' do - context 'when entry config value is valid' do - context 'when config is a branch or tag name' do - let(:config) { %w[master feature/branch] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid + context 'when using simplified policy' do + describe 'validations' do + context 'when entry config value is valid' do + context 'when config is a branch or tag name' do + let(:config) { %w[master feature/branch] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end end - end - describe '#value' do - it 'returns key value' do - expect(entry.value).to eq config + describe '#value' do + it 'returns key value' do + expect(entry.value).to eq(refs: config) + end end end - end - context 'when config is a regexp' do - let(:config) { ['/^issue-.*$/'] } + context 'when config is a regexp' do + let(:config) { ['/^issue-.*$/'] } - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end end end - end - context 'when config is a special keyword' do - let(:config) { %w[tags triggers branches] } + context 'when config is a special keyword' do + let(:config) { %w[tags triggers branches] } - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end end end end - end - context 'when entry value is not valid' do - let(:config) { [1] } + context 'when entry value is not valid' do + let(:config) { [1] } - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include 'policy config should be an array of strings or regexps' + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include /policy config should be an array of strings or regexps/ + end end end end end + + context 'when using complex policy' do + end end -- cgit v1.2.1 From fcb4d1f80945cbcdfbdb73e102d046447f55e5b5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Aug 2017 10:27:00 +0200 Subject: Implement complex only/except policy CI/CD config --- lib/gitlab/ci/config/entry/policy.rb | 20 ++++++++++++++++++-- lib/gitlab/ci/config/entry/simplifiable.rb | 12 ++++++++++-- spec/lib/gitlab/ci/config/entry/policy_spec.rb | 26 ++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb index ac564a6c7d0..decacebee55 100644 --- a/lib/gitlab/ci/config/entry/policy.rb +++ b/lib/gitlab/ci/config/entry/policy.rb @@ -3,7 +3,7 @@ module Gitlab class Config module Entry ## - # Entry that represents a trigger policy for the job. + # Entry that represents an only/except trigger policy for the job. # class Policy < Simplifiable strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) } @@ -23,9 +23,25 @@ module Gitlab class ExpressionsPolicy < Entry::Node include Entry::Validatable + include Entry::Attributable + + attributes :refs, :expressions validations do - validates :config, type: Hash + validates :config, presence: true + validates :config, allowed_keys: %i[refs expressions] + + with_options allow_nil: true do + validates :refs, array_of_strings_or_regexps: true + validates :expressions, type: Array + validates :expressions, presence: true + end + end + end + + class UnknownStrategy < Entry::Node + def errors + ['policy has to be either an array of conditions or a hash'] end end end diff --git a/lib/gitlab/ci/config/entry/simplifiable.rb b/lib/gitlab/ci/config/entry/simplifiable.rb index 5319065b846..c26576fcbcd 100644 --- a/lib/gitlab/ci/config/entry/simplifiable.rb +++ b/lib/gitlab/ci/config/entry/simplifiable.rb @@ -10,7 +10,7 @@ module Gitlab variant.condition.call(config) end - entry = self.class.const_get(strategy.name) + entry = self.class.entry_class(strategy) super(entry.new(config, metadata)) end @@ -22,7 +22,15 @@ module Gitlab end def self.strategies - @strategies || [] + @strategies.to_a + end + + def self.entry_class(strategy) + if strategy.present? + self.const_get(strategy.name) + else + self::UnknownStrategy + end end end end diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index fe9a053c46c..69095144daa 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -57,5 +57,31 @@ describe Gitlab::Ci::Config::Entry::Policy do end context 'when using complex policy' do + context 'when it is an empty hash' do + let(:config) { { } } + + it 'reports an error about configuration not being present' do + expect(entry.errors).to include /can't be blank/ + end + end + + context 'when it contains unknown keys' do + let(:config) { { refs: ['something'], invalid: 'master' } } + + it 'is not valid entry' do + expect(entry).not_to be_valid + expect(entry.errors) + .to include /policy config contains unknown keys: invalid/ + end + end + end + + context 'when policy strategy does not match' do + let(:config) { 'string strategy' } + + it 'returns information about errors' do + expect(entry.errors) + .to include 'policy has to be either an array of conditions or a hash' + end end end -- cgit v1.2.1 From 873460783a81ba7b89ca51c8fb6dfa5f6c2c51cc Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Aug 2017 11:25:37 +0200 Subject: Add specs for a simplifiable CI/CD entry aspect --- .../gitlab/ci/config/entry/simplifiable_spec.rb | 75 ++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 spec/lib/gitlab/ci/config/entry/simplifiable_spec.rb diff --git a/spec/lib/gitlab/ci/config/entry/simplifiable_spec.rb b/spec/lib/gitlab/ci/config/entry/simplifiable_spec.rb new file mode 100644 index 00000000000..5f9a0625b06 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/simplifiable_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Entry::Simplifiable do + describe '.strategy' do + let(:entry) do + Class.new(described_class) do + strategy :Something, if: -> { 'condition' } + strategy :DifferentOne, if: -> { 'condition' } + end + end + + it 'defines entry strategies' do + expect(entry.strategies.size).to eq 2 + expect(entry.strategies.map(&:name)) + .to eq %i[Something DifferentOne] + end + end + + describe 'setting strategy by a condition' do + let(:first) { double('first strategy') } + let(:second) { double('second strategy') } + let(:unknown) { double('unknown strategy') } + + before do + stub_const("#{described_class.name}::Something", first) + stub_const("#{described_class.name}::DifferentOne", second) + stub_const("#{described_class.name}::UnknownStrategy", unknown) + end + + context 'when first strategy should be used' do + let(:entry) do + Class.new(described_class) do + strategy :Something, if: -> (arg) { arg == 'something' } + strategy :DifferentOne, if: -> (*) { false } + end + end + + it 'attemps to load a first strategy' do + expect(first).to receive(:new).with('something', anything) + + entry.new('something') + end + end + + context 'when second strategy should be used' do + let(:entry) do + Class.new(described_class) do + strategy :Something, if: -> (arg) { arg == 'something' } + strategy :DifferentOne, if: -> (arg) { arg == 'test' } + end + end + + it 'attemps to load a second strategy' do + expect(second).to receive(:new).with('test', anything) + + entry.new('test') + end + end + + context 'when neither one is a valid strategy' do + let(:entry) do + Class.new(described_class) do + strategy :Something, if: -> (*) { false } + strategy :DifferentOne, if: -> (*) { false } + end + end + + it 'instantiates an unknown strategy' do + expect(unknown).to receive(:new).with('test', anything) + + entry.new('test') + end + end + end +end -- cgit v1.2.1 From 9e203582b367a1b84035572261a79b62e22bfeaa Mon Sep 17 00:00:00 2001 From: Hiroyuki Sato Date: Wed, 23 Aug 2017 01:51:53 +0900 Subject: Improve AutocompleteController#user.json performance --- app/models/user.rb | 2 +- .../improve-autocomplete-user-performance.yml | 5 ++++ lib/gitlab/sql/pattern.rb | 29 ++++++++++++++++++++ .../issues/filtered_search/dropdown_author_spec.rb | 12 ++++----- spec/lib/gitlab/sql/pattern_spec.rb | 31 ++++++++++++++++++++++ spec/models/user_spec.rb | 17 ++++++++++++ 6 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 changelogs/unreleased/improve-autocomplete-user-performance.yml create mode 100644 lib/gitlab/sql/pattern.rb create mode 100644 spec/lib/gitlab/sql/pattern_spec.rb diff --git a/app/models/user.rb b/app/models/user.rb index fbd08bc4d0a..e5a84ce4080 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -303,7 +303,7 @@ class User < ActiveRecord::Base # Returns an ActiveRecord::Relation. def search(query) table = arel_table - pattern = "%#{query}%" + pattern = Gitlab::SQL::Pattern.new(query).to_sql order = <<~SQL CASE diff --git a/changelogs/unreleased/improve-autocomplete-user-performance.yml b/changelogs/unreleased/improve-autocomplete-user-performance.yml new file mode 100644 index 00000000000..5a7153771ff --- /dev/null +++ b/changelogs/unreleased/improve-autocomplete-user-performance.yml @@ -0,0 +1,5 @@ +--- +title: Improve performance for AutocompleteController#users.json +merge_request: 13754 +author: Hiroyuki Sato +type: changed diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb new file mode 100644 index 00000000000..47ea19994a2 --- /dev/null +++ b/lib/gitlab/sql/pattern.rb @@ -0,0 +1,29 @@ +module Gitlab + module SQL + class Pattern + MIN_CHARS_FOR_PARTIAL_MATCHING = 3 + + attr_reader :query + + def initialize(query) + @query = query + end + + def to_sql + if exact_matching? + query + else + "%#{query}%" + end + end + + def exact_matching? + !partial_matching? + end + + def partial_matching? + @query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING + end + end + end +end diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index 975dc035f2d..3cec59050ab 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -6,7 +6,7 @@ describe 'Dropdown author', js: true do let!(:project) { create(:project) } let!(:user) { create(:user, name: 'administrator', username: 'root') } let!(:user_john) { create(:user, name: 'John', username: 'th0mas') } - let!(:user_jacob) { create(:user, name: 'Jacob', username: 'otter32') } + let!(:user_jacob) { create(:user, name: 'Jacob', username: 'ooter32') } let(:filtered_search) { find('.filtered-search') } let(:js_dropdown_author) { '#js-dropdown-author' } @@ -82,31 +82,31 @@ describe 'Dropdown author', js: true do end it 'filters by name' do - send_keys_to_filtered_search('ja') + send_keys_to_filtered_search('jac') expect(dropdown_author_size).to eq(1) end it 'filters by case insensitive name' do - send_keys_to_filtered_search('Ja') + send_keys_to_filtered_search('Jac') expect(dropdown_author_size).to eq(1) end it 'filters by username with symbol' do - send_keys_to_filtered_search('@ot') + send_keys_to_filtered_search('@oot') expect(dropdown_author_size).to eq(2) end it 'filters by username without symbol' do - send_keys_to_filtered_search('ot') + send_keys_to_filtered_search('oot') expect(dropdown_author_size).to eq(2) end it 'filters by case insensitive username without symbol' do - send_keys_to_filtered_search('OT') + send_keys_to_filtered_search('OOT') expect(dropdown_author_size).to eq(2) end diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb new file mode 100644 index 00000000000..cbafe36de06 --- /dev/null +++ b/spec/lib/gitlab/sql/pattern_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Gitlab::SQL::Pattern do + describe '#to_sql' do + subject(:to_sql) { described_class.new(query).to_sql } + + context 'when a query is shorter than 3 chars' do + let(:query) { '12' } + + it 'returns exact matching pattern' do + expect(to_sql).to eq('12') + end + end + + context 'when a query is equal to 3 chars' do + let(:query) { '123' } + + it 'returns partial matching pattern' do + expect(to_sql).to eq('%123%') + end + end + + context 'when a query is longer than 3 chars' do + let(:query) { '1234' } + + it 'returns partial matching pattern' do + expect(to_sql).to eq('%1234%') + end + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 9a9e255f874..50abd7af429 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -789,6 +789,7 @@ describe User do describe '.search' do let!(:user) { create(:user, name: 'user', username: 'usern', email: 'email@gmail.com') } let!(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@gmail.com') } + let!(:user3) { create(:user, name: 'us', username: 'se', email: 'foo@gmail.com') } describe 'name matching' do it 'returns users with a matching name with exact match first' do @@ -802,6 +803,14 @@ describe User do it 'returns users with a matching name regardless of the casing' do expect(described_class.search(user2.name.upcase)).to eq([user2]) end + + it 'returns users with a exact matching name shorter than 3 chars' do + expect(described_class.search(user3.name)).to eq([user3]) + end + + it 'returns users with a exact matching name shorter than 3 chars regardless of the casing' do + expect(described_class.search(user3.name.upcase)).to eq([user3]) + end end describe 'email matching' do @@ -830,6 +839,14 @@ describe User do it 'returns users with a matching username regardless of the casing' do expect(described_class.search(user2.username.upcase)).to eq([user2]) end + + it 'returns users with a exact matching username shorter than 3 chars' do + expect(described_class.search(user3.username)).to eq([user3]) + end + + it 'returns users with a exact matching username shorter than 3 chars regardless of the casing' do + expect(described_class.search(user3.username.upcase)).to eq([user3]) + end end end -- cgit v1.2.1 From a061a2461a05200ab534d382b67e1b9099128478 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Aug 2017 11:42:40 +0200 Subject: Fix CI/CD trigger policy default value --- lib/gitlab/ci/config/entry/policy.rb | 3 +++ spec/lib/gitlab/ci/config/entry/policy_spec.rb | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb index decacebee55..41a3737b34a 100644 --- a/lib/gitlab/ci/config/entry/policy.rb +++ b/lib/gitlab/ci/config/entry/policy.rb @@ -44,6 +44,9 @@ module Gitlab ['policy has to be either an array of conditions or a hash'] end end + + def self.default + end end end end diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 69095144daa..740b95427d3 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -84,4 +84,10 @@ describe Gitlab::Ci::Config::Entry::Policy do .to include 'policy has to be either an array of conditions or a hash' end end + + describe '.default' do + it 'does not have a default value' do + expect(described_class.default).to be_nil + end + end end -- cgit v1.2.1 From 946e8d3a93eb8c9e30d1f3baa3b5b28e6c06fbc1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Aug 2017 12:01:59 +0200 Subject: Use only/except policy that returns an array --- lib/gitlab/ci/config/entry/policy.rb | 4 ---- spec/lib/gitlab/ci/config/entry/policy_spec.rb | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb index 41a3737b34a..495822e9f5a 100644 --- a/lib/gitlab/ci/config/entry/policy.rb +++ b/lib/gitlab/ci/config/entry/policy.rb @@ -15,10 +15,6 @@ module Gitlab validations do validates :config, array_of_strings_or_regexps: true end - - def value - { refs: @config } - end end class ExpressionsPolicy < Entry::Node diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 740b95427d3..7f842c4b81b 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -17,7 +17,7 @@ describe Gitlab::Ci::Config::Entry::Policy do describe '#value' do it 'returns key value' do - expect(entry.value).to eq(refs: config) + expect(entry.value).to eq config end end end -- cgit v1.2.1 From 99dddac55850fed68ea6f66363c3f083bd4866fa Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Aug 2017 13:00:45 +0200 Subject: Simplify ci config entry validator implementation --- lib/gitlab/ci/config/entry/validator.rb | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/ci/config/entry/validator.rb b/lib/gitlab/ci/config/entry/validator.rb index 5ab54d7e218..83bca0d08bc 100644 --- a/lib/gitlab/ci/config/entry/validator.rb +++ b/lib/gitlab/ci/config/entry/validator.rb @@ -24,16 +24,11 @@ module Gitlab private def location - predecessors = ancestors.map(&:key).compact - predecessors.append(key_name).join(':') + ancestors.map(&:key).compact.append(key_name).join(':') end def key_name - if key.blank? - @entry.class.name.to_s.demodulize.underscore.humanize - else - key - end + key.presence || @entry.class.name.to_s.demodulize.underscore.humanize end end end -- cgit v1.2.1 From 7e6bc4dde29af635c4ec281beea20ca87ccfbe34 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Aug 2017 15:16:09 +0200 Subject: Improve reporting of a CI/CD entry config location --- lib/gitlab/ci/config/entry/attributable.rb | 2 ++ lib/gitlab/ci/config/entry/node.rb | 7 ++++ lib/gitlab/ci/config/entry/policy.rb | 2 +- lib/gitlab/ci/config/entry/validator.rb | 11 ------ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 41 +++++++++++++---------- spec/lib/gitlab/ci/config/entry/policy_spec.rb | 2 +- spec/lib/gitlab/ci/config/entry/validator_spec.rb | 2 +- 7 files changed, 36 insertions(+), 31 deletions(-) diff --git a/lib/gitlab/ci/config/entry/attributable.rb b/lib/gitlab/ci/config/entry/attributable.rb index 1c8b55ee4c4..24ff862a142 100644 --- a/lib/gitlab/ci/config/entry/attributable.rb +++ b/lib/gitlab/ci/config/entry/attributable.rb @@ -8,6 +8,8 @@ module Gitlab class_methods do def attributes(*attributes) attributes.flatten.each do |attribute| + raise ArgumentError if method_defined?(attribute) + define_method(attribute) do return unless config.is_a?(Hash) diff --git a/lib/gitlab/ci/config/entry/node.rb b/lib/gitlab/ci/config/entry/node.rb index 2474684e07f..c868943c42e 100644 --- a/lib/gitlab/ci/config/entry/node.rb +++ b/lib/gitlab/ci/config/entry/node.rb @@ -71,6 +71,13 @@ module Gitlab true end + def location + name = @key.presence || self.class.name.to_s.demodulize + .underscore.humanize.downcase + + ancestors.map(&:key).append(name).compact.join(':') + end + def inspect val = leaf? ? config : descendants unspecified = specified? ? '' : '(unspecified) ' diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb index 495822e9f5a..05602f1b3c6 100644 --- a/lib/gitlab/ci/config/entry/policy.rb +++ b/lib/gitlab/ci/config/entry/policy.rb @@ -37,7 +37,7 @@ module Gitlab class UnknownStrategy < Entry::Node def errors - ['policy has to be either an array of conditions or a hash'] + ["#{location} has to be either an array of conditions or a hash"] end end diff --git a/lib/gitlab/ci/config/entry/validator.rb b/lib/gitlab/ci/config/entry/validator.rb index 83bca0d08bc..2df23a3edcd 100644 --- a/lib/gitlab/ci/config/entry/validator.rb +++ b/lib/gitlab/ci/config/entry/validator.rb @@ -8,7 +8,6 @@ module Gitlab def initialize(entry) super(entry) - @entry = entry end def messages @@ -20,16 +19,6 @@ module Gitlab def self.name 'Validator' end - - private - - def location - ancestors.map(&:key).compact.append(key_name).join(':') - end - - def key_name - key.presence || @entry.class.name.to_s.demodulize.underscore.humanize - end end end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index ee28387cd48..fbfed263e15 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -344,28 +344,32 @@ module Ci let(:config) { { rspec: { script: "rspec", type: "test", only: only } } } let(:processor) { GitlabCiYamlProcessor.new(YAML.dump(config)) } - shared_examples 'raises an error' do - it do - expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'jobs:rspec:only config should be an array of strings or regexps') - end - end context 'when it is integer' do let(:only) { 1 } - it_behaves_like 'raises an error' + it do + expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, + 'jobs:rspec:only has to be either an array of conditions or a hash') + end end context 'when it is an array of integers' do let(:only) { [1, 1] } - it_behaves_like 'raises an error' + it do + expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, + 'jobs:rspec:only config should be an array of strings or regexps') + end end context 'when it is invalid regex' do let(:only) { ["/*invalid/"] } - it_behaves_like 'raises an error' + it do + expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, + 'jobs:rspec:only config should be an array of strings or regexps') + end end end end @@ -518,28 +522,31 @@ module Ci let(:config) { { rspec: { script: "rspec", except: except } } } let(:processor) { GitlabCiYamlProcessor.new(YAML.dump(config)) } - shared_examples 'raises an error' do - it do - expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'jobs:rspec:except config should be an array of strings or regexps') - end - end - context 'when it is integer' do let(:except) { 1 } - it_behaves_like 'raises an error' + it do + expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, + 'jobs:rspec:except has to be either an array of conditions or a hash') + end end context 'when it is an array of integers' do let(:except) { [1, 1] } - it_behaves_like 'raises an error' + it do + expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, + 'jobs:rspec:except config should be an array of strings or regexps') + end end context 'when it is invalid regex' do let(:except) { ["/*invalid/"] } - it_behaves_like 'raises an error' + it do + expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, + 'jobs:rspec:except config should be an array of strings or regexps') + end end end end diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 7f842c4b81b..b3eba1382f2 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -81,7 +81,7 @@ describe Gitlab::Ci::Config::Entry::Policy do it 'returns information about errors' do expect(entry.errors) - .to include 'policy has to be either an array of conditions or a hash' + .to include /has to be either an array of conditions or a hash/ end end diff --git a/spec/lib/gitlab/ci/config/entry/validator_spec.rb b/spec/lib/gitlab/ci/config/entry/validator_spec.rb index ad7e6f07d3c..172b6b47a4f 100644 --- a/spec/lib/gitlab/ci/config/entry/validator_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/validator_spec.rb @@ -48,7 +48,7 @@ describe Gitlab::Ci::Config::Entry::Validator do validator_instance.validate expect(validator_instance.messages) - .to include "node test attribute can't be blank" + .to include /test attribute can't be blank/ end end end -- cgit v1.2.1 From d1054bd3ddb48c15b6a3a53f8c57974208094106 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 25 Aug 2017 22:38:07 +0800 Subject: Resolve feedback on the MR: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13766 * Rename AfterImportService * Use constants * Move Git operations to Gitlab::Git::Repository * Use Regexp.union --- app/models/project.rb | 6 +-- app/services/projects/after_import_service.rb | 32 +++++++++++++ app/services/projects/housecleaning_service.rb | 40 ---------------- lib/gitlab/git/repository.rb | 11 +++++ .../services/projects/after_import_service_spec.rb | 55 ++++++++++++++++++++++ .../projects/housecleaning_service_spec.rb | 55 ---------------------- 6 files changed, 99 insertions(+), 100 deletions(-) create mode 100644 app/services/projects/after_import_service.rb delete mode 100644 app/services/projects/housecleaning_service.rb create mode 100644 spec/services/projects/after_import_service_spec.rb delete mode 100644 spec/services/projects/housecleaning_service_spec.rb diff --git a/app/models/project.rb b/app/models/project.rb index c0060504d74..f86f75fbbdc 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -371,11 +371,7 @@ class Project < ActiveRecord::Base if Gitlab::ImportSources.importer_names.include?(project.import_type) && project.repo_exists? project.run_after_commit do - begin - Projects::HousecleaningService.new(project).execute - rescue Projects::HousekeepingService::LeaseTaken => e - Rails.logger.info("Could not perform housekeeping for project #{project.full_path} (#{project.id}): #{e}") - end + Projects::AfterImportService.new(project).execute end end end diff --git a/app/services/projects/after_import_service.rb b/app/services/projects/after_import_service.rb new file mode 100644 index 00000000000..bbada7e2b1c --- /dev/null +++ b/app/services/projects/after_import_service.rb @@ -0,0 +1,32 @@ +module Projects + class AfterImportService + RESERVED_REFS_NAMES = + %w[heads tags merge-requests keep-around environments].freeze + + RESERVED_REFS_REGEXP = + %r{\Arefs/(?:#{Regexp.union(*RESERVED_REFS_NAMES)})/} + + def initialize(project) + @project = project + end + + def execute + Projects::HousekeepingService.new(@project).execute do + repository.delete_refs(garbage_refs) + end + rescue Projects::HousekeepingService::LeaseTaken => e + Rails.logger.info( + "Could not perform housekeeping for project #{@project.full_path} (#{@project.id}): #{e}") + end + + private + + def garbage_refs + @garbage_refs ||= repository.all_ref_names_except(RESERVED_REFS_REGEXP) + end + + def repository + @repository ||= @project.repository + end + end +end diff --git a/app/services/projects/housecleaning_service.rb b/app/services/projects/housecleaning_service.rb deleted file mode 100644 index d5cf8478e13..00000000000 --- a/app/services/projects/housecleaning_service.rb +++ /dev/null @@ -1,40 +0,0 @@ -module Projects - class HousecleaningService - def self.reserved_refs_names - %w[heads tags merge-requests keep-around environments] - end - - def self.reserved_refs_regexp - names = reserved_refs_names.map(&Regexp.method(:escape)).join('|') - - %r{\Arefs/(?:#{names})/} - end - - def initialize(project) - @project = project - end - - # This could raise Projects::HousekeepingService::LeaseTaken - def execute - Projects::HousekeepingService.new(@project).execute do - garbage_refs.each(&rugged.references.method(:delete)) - end - end - - private - - def garbage_refs - @garbage_refs ||= begin - reserved_refs_regexp = self.class.reserved_refs_regexp - - rugged.references.reject do |ref| - ref.name =~ reserved_refs_regexp - end - end - end - - def rugged - @rugged ||= @project.repository.rugged - end - end -end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index eb3731ba35a..f6d30ad7534 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -232,6 +232,13 @@ module Gitlab branch_names + tag_names end + # Returns an Array of all ref names, except when it's matching pattern + # + # regexp - The pattern for ref names we don't want + def all_ref_names_except(regexp) + rugged.references.reject { |ref| ref.name =~ regexp }.map(&:name) + end + # Discovers the default branch based on the repository's available branches # # - If no branches are present, returns nil @@ -577,6 +584,10 @@ module Gitlab rugged.branches.delete(branch_name) end + def delete_refs(ref_names) + ref_names.each(&rugged.references.method(:delete)) + end + # Create a new branch named **ref+ based on **stat_point+, HEAD by default # # Examples: diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb new file mode 100644 index 00000000000..540d2191b2d --- /dev/null +++ b/spec/services/projects/after_import_service_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Projects::AfterImportService do + subject { described_class.new(project) } + + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:sha) { project.commit.sha } + let(:housekeeping_service) { double(:housekeeping_service) } + + describe '#execute' do + before do + allow(Projects::HousekeepingService) + .to receive(:new).with(project).and_return(housekeeping_service) + + allow(housekeeping_service) + .to receive(:execute).and_yield + end + + it 'performs housekeeping' do + subject.execute + + expect(housekeeping_service).to have_received(:execute) + end + + context 'with some refs in refs/pull/**/*' do + before do + repository.write_ref('refs/pull/1/head', sha) + repository.write_ref('refs/pull/1/merge', sha) + + subject.execute + end + + it 'removes refs/pull/**/*' do + expect(repository.rugged.references.map(&:name)) + .not_to include(%r{\Arefs/pull/}) + end + end + + described_class::RESERVED_REFS_NAMES.each do |name| + context "with a ref in refs/#{name}/tmp" do + before do + repository.write_ref("refs/#{name}/tmp", sha) + + subject.execute + end + + it "does not remove refs/#{name}/tmp" do + expect(repository.rugged.references.map(&:name)) + .to include("refs/#{name}/tmp") + end + end + end + end +end diff --git a/spec/services/projects/housecleaning_service_spec.rb b/spec/services/projects/housecleaning_service_spec.rb deleted file mode 100644 index 3dd7906dc9a..00000000000 --- a/spec/services/projects/housecleaning_service_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'spec_helper' - -describe Projects::HousecleaningService do - subject { described_class.new(project) } - - let(:project) { create(:project, :repository) } - let(:repository) { project.repository } - let(:sha) { project.commit.sha } - let(:housekeeping_service) { double(:housekeeping_service) } - - describe '#execute' do - before do - allow(Projects::HousekeepingService) - .to receive(:new).with(project).and_return(housekeeping_service) - - allow(housekeeping_service) - .to receive(:execute).and_yield - end - - it 'performs housekeeping' do - subject.execute - - expect(housekeeping_service).to have_received(:execute) - end - - context 'with some refs in refs/pull/**/*' do - before do - repository.write_ref('refs/pull/1/head', sha) - repository.write_ref('refs/pull/1/merge', sha) - - subject.execute - end - - it 'removes refs/pull/**/*' do - expect(repository.rugged.references.map(&:name)) - .not_to include(%r{\Arefs/pull/}) - end - end - - described_class.reserved_refs_names.each do |name| - context "with a ref in refs/#{name}/tmp" do - before do - repository.write_ref("refs/#{name}/tmp", sha) - - subject.execute - end - - it "does not remove refs/#{name}/tmp" do - expect(repository.rugged.references.map(&:name)) - .to include("refs/#{name}/tmp") - end - end - end - end -end -- cgit v1.2.1 From 081e2fce8240f86f991b0bd3653ef6756f2cf9aa Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 25 Aug 2017 23:00:06 +0800 Subject: Try to make reserved ref names more obvious So that whenever we want to reserve more, we're aware, and don't mess it up. --- app/models/environment.rb | 2 +- app/models/merge_request.rb | 2 +- app/models/repository.rb | 18 +++++++++++++++--- app/services/projects/after_import_service.rb | 5 +---- spec/services/projects/after_import_service_spec.rb | 2 +- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/models/environment.rb b/app/models/environment.rb index e9ebf0637f3..435eeaf0e2e 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -114,7 +114,7 @@ class Environment < ActiveRecord::Base end def ref_path - "refs/environments/#{Shellwords.shellescape(name)}" + "refs/#{Repository::REF_ENVIRONMENTS}/#{Shellwords.shellescape(name)}" end def formatted_external_url diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index f028d2395c1..8361039f301 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -797,7 +797,7 @@ class MergeRequest < ActiveRecord::Base end def ref_path - "refs/merge-requests/#{iid}/head" + "refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/head" end def ref_fetched? diff --git a/app/models/repository.rb b/app/models/repository.rb index c1e4fcf94a4..c247fb840ce 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1,6 +1,18 @@ require 'securerandom' class Repository + REF_MERGE_REQUEST = 'merge-requests' + REF_KEEP_AROUND = 'keep-around' + REF_ENVIRONMENTS = 'environments' + + RESERVED_REFS_NAMES = %W[ + heads + tags + #{REF_ENVIRONMENTS} + #{REF_KEEP_AROUND} + #{REF_ENVIRONMENTS} + ].freeze + include Gitlab::ShellAdapter include RepositoryMirroring @@ -228,10 +240,10 @@ class Repository begin write_ref(keep_around_ref_name(sha), sha) rescue Rugged::ReferenceError => ex - Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}" + Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}" rescue Rugged::OSError => ex raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/ - Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}" + Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}" end end @@ -1152,7 +1164,7 @@ class Repository end def keep_around_ref_name(sha) - "refs/keep-around/#{sha}" + "refs/#{REF_KEEP_AROUND}/#{sha}" end def repository_event(event, tags = {}) diff --git a/app/services/projects/after_import_service.rb b/app/services/projects/after_import_service.rb index bbada7e2b1c..107856885f3 100644 --- a/app/services/projects/after_import_service.rb +++ b/app/services/projects/after_import_service.rb @@ -1,10 +1,7 @@ module Projects class AfterImportService - RESERVED_REFS_NAMES = - %w[heads tags merge-requests keep-around environments].freeze - RESERVED_REFS_REGEXP = - %r{\Arefs/(?:#{Regexp.union(*RESERVED_REFS_NAMES)})/} + %r{\Arefs/(?:#{Regexp.union(*Repository::RESERVED_REFS_NAMES)})/} def initialize(project) @project = project diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb index 540d2191b2d..c6678fc1f5c 100644 --- a/spec/services/projects/after_import_service_spec.rb +++ b/spec/services/projects/after_import_service_spec.rb @@ -37,7 +37,7 @@ describe Projects::AfterImportService do end end - described_class::RESERVED_REFS_NAMES.each do |name| + Repository::RESERVED_REFS_NAMES.each do |name| context "with a ref in refs/#{name}/tmp" do before do repository.write_ref("refs/#{name}/tmp", sha) -- cgit v1.2.1 From 4509594e206f93cdb54d80ae96fe7ad3d6a8fa4b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Aug 2017 17:54:14 +0200 Subject: Fix Rubocop offense in CI/CD only/except policy class --- spec/lib/gitlab/ci/config/entry/policy_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index b3eba1382f2..ccf885969fb 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -58,7 +58,7 @@ describe Gitlab::Ci::Config::Entry::Policy do context 'when using complex policy' do context 'when it is an empty hash' do - let(:config) { { } } + let(:config) { {} } it 'reports an error about configuration not being present' do expect(entry.errors).to include /can't be blank/ -- cgit v1.2.1 From 04108611716c0f1bb00092077ad5e31785675490 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Aug 2017 18:26:55 +0200 Subject: Add specs for attributable aspect of ci config entry --- lib/gitlab/ci/config/entry/attributable.rb | 4 +++- .../gitlab/ci/config/entry/attributable_spec.rb | 27 ++++++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/ci/config/entry/attributable.rb b/lib/gitlab/ci/config/entry/attributable.rb index 24ff862a142..3e87a09704e 100644 --- a/lib/gitlab/ci/config/entry/attributable.rb +++ b/lib/gitlab/ci/config/entry/attributable.rb @@ -8,7 +8,9 @@ module Gitlab class_methods do def attributes(*attributes) attributes.flatten.each do |attribute| - raise ArgumentError if method_defined?(attribute) + if method_defined?(attribute) + raise ArgumentError, 'Method already defined!' + end define_method(attribute) do return unless config.is_a?(Hash) diff --git a/spec/lib/gitlab/ci/config/entry/attributable_spec.rb b/spec/lib/gitlab/ci/config/entry/attributable_spec.rb index fde03c51e2c..b028b771375 100644 --- a/spec/lib/gitlab/ci/config/entry/attributable_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/attributable_spec.rb @@ -1,18 +1,21 @@ require 'spec_helper' describe Gitlab::Ci::Config::Entry::Attributable do - let(:node) { Class.new } + let(:node) do + Class.new do + include Gitlab::Ci::Config::Entry::Attributable + end + end + let(:instance) { node.new } before do - node.include(described_class) - node.class_eval do attributes :name, :test end end - context 'config is a hash' do + context 'when config is a hash' do before do allow(instance) .to receive(:config) @@ -29,7 +32,7 @@ describe Gitlab::Ci::Config::Entry::Attributable do end end - context 'config is not a hash' do + context 'when config is not a hash' do before do allow(instance) .to receive(:config) @@ -40,4 +43,18 @@ describe Gitlab::Ci::Config::Entry::Attributable do expect(instance.test).to be_nil end end + + context 'when method is already defined in a superclass' do + it 'raises an error' do + expectation = expect do + Class.new(String) do + include Gitlab::Ci::Config::Entry::Attributable + + attributes :length + end + end + + expectation.to raise_error(ArgumentError, 'Method already defined!') + end + end end -- cgit v1.2.1 From 6a5097fd247f5d6fe7fe4efac38d685e5ef190ae Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 26 Aug 2017 10:57:43 +0200 Subject: Fix rubocop offense in YAML processor specs --- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index fbfed263e15..c70a4cb55fe 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -344,7 +344,6 @@ module Ci let(:config) { { rspec: { script: "rspec", type: "test", only: only } } } let(:processor) { GitlabCiYamlProcessor.new(YAML.dump(config)) } - context 'when it is integer' do let(:only) { 1 } -- cgit v1.2.1 From 866aab7f2a92f9929a5c5811d3d3c23c11184b26 Mon Sep 17 00:00:00 2001 From: Hiroyuki Sato Date: Sat, 26 Aug 2017 22:32:55 +0900 Subject: Fix escape characters was not sanitized --- lib/gitlab/sql/pattern.rb | 9 +++++++-- spec/lib/gitlab/sql/pattern_spec.rb | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb index 47ea19994a2..46c973d8a11 100644 --- a/lib/gitlab/sql/pattern.rb +++ b/lib/gitlab/sql/pattern.rb @@ -11,9 +11,9 @@ module Gitlab def to_sql if exact_matching? - query + sanitized_query else - "%#{query}%" + "%#{sanitized_query}%" end end @@ -24,6 +24,11 @@ module Gitlab def partial_matching? @query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING end + + def sanitized_query + # Note: ActiveRecord::Base.sanitize_sql_like is a protected method + ActiveRecord::Base.__send__(:sanitize_sql_like, query) + end end end end diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb index cbafe36de06..d0412f37098 100644 --- a/spec/lib/gitlab/sql/pattern_spec.rb +++ b/spec/lib/gitlab/sql/pattern_spec.rb @@ -12,6 +12,14 @@ describe Gitlab::SQL::Pattern do end end + context 'when a query with a escape character is shorter than 3 chars' do + let(:query) { '_2' } + + it 'returns sanitized exact matching pattern' do + expect(to_sql).to eq('\_2') + end + end + context 'when a query is equal to 3 chars' do let(:query) { '123' } @@ -20,6 +28,14 @@ describe Gitlab::SQL::Pattern do end end + context 'when a query with a escape character is equal to 3 chars' do + let(:query) { '_23' } + + it 'returns partial matching pattern' do + expect(to_sql).to eq('%\_23%') + end + end + context 'when a query is longer than 3 chars' do let(:query) { '1234' } @@ -27,5 +43,13 @@ describe Gitlab::SQL::Pattern do expect(to_sql).to eq('%1234%') end end + + context 'when a query with a escape character is longer than 3 chars' do + let(:query) { '_234' } + + it 'returns sanitized partial matching pattern' do + expect(to_sql).to eq('%\_234%') + end + end end end -- cgit v1.2.1 From 3a4da8ce8b57aa430720de75397a38c2be0fc6c0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 28 Aug 2017 18:51:23 +0800 Subject: Fix tests --- app/models/repository.rb | 6 +++--- spec/models/project_spec.rb | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index c247fb840ce..b917eb4c302 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1,9 +1,9 @@ require 'securerandom' class Repository - REF_MERGE_REQUEST = 'merge-requests' - REF_KEEP_AROUND = 'keep-around' - REF_ENVIRONMENTS = 'environments' + REF_MERGE_REQUEST = 'merge-requests'.freeze + REF_KEEP_AROUND = 'keep-around'.freeze + REF_ENVIRONMENTS = 'environments'.freeze RESERVED_REFS_NAMES = %W[ heads diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 7631207b1d0..11717ba39e8 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1563,14 +1563,14 @@ describe Project do describe 'project import state transitions' do context 'state transition: [:started] => [:finished]' do - let(:housecleaning_service) { spy(:housecleaning_service) } + let(:after_import_service) { spy(:after_import_service) } let(:housekeeping_service) { spy(:housekeeping_service) } before do - allow(Projects::HousecleaningService) - .to receive(:new) { housecleaning_service } + allow(Projects::AfterImportService) + .to receive(:new) { after_import_service } - allow(housecleaning_service) + allow(after_import_service) .to receive(:execute) { housekeeping_service.execute } allow(Projects::HousekeepingService) @@ -1589,7 +1589,7 @@ describe Project do project.import_finish - expect(housecleaning_service).to have_received(:execute) + expect(after_import_service).to have_received(:execute) expect(housekeeping_service).to have_received(:execute) end -- cgit v1.2.1 From ed95119c80af53ae94f71b7221bc1b159f9b3c5b Mon Sep 17 00:00:00 2001 From: winh Date: Thu, 17 Aug 2017 10:58:15 +0200 Subject: Make namespace select dropdowns consistent --- app/assets/stylesheets/framework/dropdowns.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index a45d5a6dca0..b9ffe3d56db 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -792,3 +792,5 @@ margin-top: 2px; } } + +@include new-style-dropdown('.js-namespace-select + '); -- cgit v1.2.1 From 6038d0e76e95c4b6a96202eb482b048e01d0963e Mon Sep 17 00:00:00 2001 From: winh Date: Wed, 16 Aug 2017 12:50:32 +0200 Subject: Make merge request version dropdowns consistent --- app/assets/stylesheets/pages/merge_requests.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index d1678a17aaf..8c69f0405ea 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -610,6 +610,8 @@ } .mr-version-controls { + @include new-style-dropdown; + position: relative; background: $gray-light; color: $gl-text-color; -- cgit v1.2.1 From b7316ffdff561cc16a003c5540f2c6e627818a2e Mon Sep 17 00:00:00 2001 From: Kai Kontio Date: Mon, 28 Aug 2017 11:47:09 +0000 Subject: Update backup_restore.md documentation regarding S3 and IAM profiles. https://gitlab.com/gitlab-org/gitlab-ce/commit/7f8ef19c411eceec207fef5d8459fbeaa40bf464 most likely broke the old configuration format where empty string aws_access_key_id & aws_secret_access_key were still OK. From 9.5 onwards moving backups to S3 using IAM profiles will fail if they are configured. --- doc/raketasks/backup_restore.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 10f5ab3370d..ae69d7f92f2 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -144,9 +144,8 @@ gitlab_rails['backup_upload_connection'] = { 'region' => 'eu-west-1', 'aws_access_key_id' => 'AKIAKIAKI', 'aws_secret_access_key' => 'secret123' - # If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty - # ie. 'aws_access_key_id' => '', - # 'use_iam_profile' => 'true' + # If using an IAM Profile, don't configure aws_access_key_id & aws_secret_access_key + # 'use_iam_profile' => true } gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket' ``` -- cgit v1.2.1 From 87b51c5981db3b1b9831b01ca6e74127d57dc2d9 Mon Sep 17 00:00:00 2001 From: Hiroyuki Sato Date: Tue, 29 Aug 2017 07:14:41 +0900 Subject: Move the logic to a concern --- app/models/user.rb | 3 ++- lib/gitlab/sql/pattern.rb | 39 +++++++++++++++---------------------- spec/lib/gitlab/sql/pattern_spec.rb | 16 +++++++-------- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index e5a84ce4080..70787de4b40 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -5,6 +5,7 @@ class User < ActiveRecord::Base include Gitlab::ConfigHelper include Gitlab::CurrentSettings + include Gitlab::SQL::Pattern include Avatarable include Referable include Sortable @@ -303,7 +304,7 @@ class User < ActiveRecord::Base # Returns an ActiveRecord::Relation. def search(query) table = arel_table - pattern = Gitlab::SQL::Pattern.new(query).to_sql + pattern = User.to_pattern(query) order = <<~SQL CASE diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb index 46c973d8a11..26bfeeeee67 100644 --- a/lib/gitlab/sql/pattern.rb +++ b/lib/gitlab/sql/pattern.rb @@ -1,33 +1,26 @@ module Gitlab module SQL - class Pattern - MIN_CHARS_FOR_PARTIAL_MATCHING = 3 - - attr_reader :query + module Pattern + extend ActiveSupport::Concern - def initialize(query) - @query = query - end + MIN_CHARS_FOR_PARTIAL_MATCHING = 3 - def to_sql - if exact_matching? - sanitized_query - else - "%#{sanitized_query}%" + class_methods do + def to_pattern(query) + if exact_matching?(query) + sanitize_sql_like(query) + else + "%#{sanitize_sql_like(query)}%" + end end - end - def exact_matching? - !partial_matching? - end - - def partial_matching? - @query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING - end + def exact_matching?(query) + query.length < MIN_CHARS_FOR_PARTIAL_MATCHING + end - def sanitized_query - # Note: ActiveRecord::Base.sanitize_sql_like is a protected method - ActiveRecord::Base.__send__(:sanitize_sql_like, query) + def partial_matching?(query) + query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING + end end end end diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb index d0412f37098..224336421be 100644 --- a/spec/lib/gitlab/sql/pattern_spec.rb +++ b/spec/lib/gitlab/sql/pattern_spec.rb @@ -1,14 +1,14 @@ require 'spec_helper' describe Gitlab::SQL::Pattern do - describe '#to_sql' do - subject(:to_sql) { described_class.new(query).to_sql } + describe '#to_pattern' do + subject(:to_pattern) { User.to_pattern(query) } context 'when a query is shorter than 3 chars' do let(:query) { '12' } it 'returns exact matching pattern' do - expect(to_sql).to eq('12') + expect(to_pattern).to eq('12') end end @@ -16,7 +16,7 @@ describe Gitlab::SQL::Pattern do let(:query) { '_2' } it 'returns sanitized exact matching pattern' do - expect(to_sql).to eq('\_2') + expect(to_pattern).to eq('\_2') end end @@ -24,7 +24,7 @@ describe Gitlab::SQL::Pattern do let(:query) { '123' } it 'returns partial matching pattern' do - expect(to_sql).to eq('%123%') + expect(to_pattern).to eq('%123%') end end @@ -32,7 +32,7 @@ describe Gitlab::SQL::Pattern do let(:query) { '_23' } it 'returns partial matching pattern' do - expect(to_sql).to eq('%\_23%') + expect(to_pattern).to eq('%\_23%') end end @@ -40,7 +40,7 @@ describe Gitlab::SQL::Pattern do let(:query) { '1234' } it 'returns partial matching pattern' do - expect(to_sql).to eq('%1234%') + expect(to_pattern).to eq('%1234%') end end @@ -48,7 +48,7 @@ describe Gitlab::SQL::Pattern do let(:query) { '_234' } it 'returns sanitized partial matching pattern' do - expect(to_sql).to eq('%\_234%') + expect(to_pattern).to eq('%\_234%') end end end -- cgit v1.2.1 From 9954dafda58a0d5d856fdbbef01633e674638aa1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 29 Aug 2017 16:23:12 +0800 Subject: Just use a block --- lib/gitlab/git/repository.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index f6d30ad7534..adf15473eb6 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -585,7 +585,7 @@ module Gitlab end def delete_refs(ref_names) - ref_names.each(&rugged.references.method(:delete)) + ref_names.each { |ref| rugged.references.delete(ref) } end # Create a new branch named **ref+ based on **stat_point+, HEAD by default -- cgit v1.2.1 From 12633b46b6884dda4ffd87b14b4b52725acd6ec1 Mon Sep 17 00:00:00 2001 From: Hiroyuki Sato Date: Tue, 29 Aug 2017 18:00:03 +0900 Subject: Refactor --- lib/gitlab/sql/pattern.rb | 10 +++------- spec/lib/gitlab/sql/pattern_spec.rb | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb index 26bfeeeee67..b42bc67ccfc 100644 --- a/lib/gitlab/sql/pattern.rb +++ b/lib/gitlab/sql/pattern.rb @@ -7,17 +7,13 @@ module Gitlab class_methods do def to_pattern(query) - if exact_matching?(query) - sanitize_sql_like(query) - else + if partial_matching?(query) "%#{sanitize_sql_like(query)}%" + else + sanitize_sql_like(query) end end - def exact_matching?(query) - query.length < MIN_CHARS_FOR_PARTIAL_MATCHING - end - def partial_matching?(query) query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING end diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb index 224336421be..9d7b2136dab 100644 --- a/spec/lib/gitlab/sql/pattern_spec.rb +++ b/spec/lib/gitlab/sql/pattern_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::SQL::Pattern do - describe '#to_pattern' do + describe '.to_pattern' do subject(:to_pattern) { User.to_pattern(query) } context 'when a query is shorter than 3 chars' do -- cgit v1.2.1 From 55d3197c8fa4e3e8a16b7d18c9298ea036c5b818 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 29 Aug 2017 11:56:37 +0100 Subject: Increase z-index of dropdowns Closes #36866, #36766 --- 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 a45d5a6dca0..1d2c258265c 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -189,7 +189,7 @@ width: auto; top: 100%; left: 0; - z-index: 9; + z-index: 200; min-width: 240px; max-width: 500px; margin-top: 2px; -- cgit v1.2.1 From 5eab624d3ce7500b3cc19230b5ce86125b88015b Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 18 Aug 2017 14:26:23 +0200 Subject: Improve migrations using triggers This adds a bunch of checks to migrations that may create or drop triggers. Dropping triggers/functions is done using "IF EXISTS" so we don't throw an error if the object in question has already been dropped. We now also raise a custom error (message) when the user does not have TRIGGER privileges. This should prevent the schema from entering an inconsistent state while also providing the user with enough information on how to solve the problem. The recommendation of using SUPERUSER permissions is a bit extreme but we require this anyway (Omnibus also configures users with this permission). Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/36633 --- .../unreleased/check-trigger-permissions.yml | 5 +++ lib/gitlab/database.rb | 8 +++++ lib/gitlab/database/grant.rb | 34 ++++++++++++++++++++ lib/gitlab/database/migration_helpers.rb | 36 +++++++++++++++++++--- spec/lib/gitlab/database/grant_spec.rb | 30 ++++++++++++++++++ spec/lib/gitlab/database/migration_helpers_spec.rb | 32 ++++++++++++++++--- 6 files changed, 137 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/check-trigger-permissions.yml create mode 100644 lib/gitlab/database/grant.rb create mode 100644 spec/lib/gitlab/database/grant_spec.rb diff --git a/changelogs/unreleased/check-trigger-permissions.yml b/changelogs/unreleased/check-trigger-permissions.yml new file mode 100644 index 00000000000..e0809cea9bf --- /dev/null +++ b/changelogs/unreleased/check-trigger-permissions.yml @@ -0,0 +1,5 @@ +--- +title: Improve migrations using triggers +merge_request: +author: +type: fixed diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index e001d25e7b7..a6ec75da385 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -9,6 +9,14 @@ module Gitlab ActiveRecord::Base.configurations[Rails.env] end + def self.username + config['username'] || ENV['USER'] + end + + def self.database_name + config['database'] + end + def self.adapter_name config['adapter'] end diff --git a/lib/gitlab/database/grant.rb b/lib/gitlab/database/grant.rb new file mode 100644 index 00000000000..aee3981e79a --- /dev/null +++ b/lib/gitlab/database/grant.rb @@ -0,0 +1,34 @@ +module Gitlab + module Database + # Model that can be used for querying permissions of a SQL user. + class Grant < ActiveRecord::Base + self.table_name = + if Database.postgresql? + 'information_schema.role_table_grants' + else + 'mysql.user' + end + + def self.scope_to_current_user + if Database.postgresql? + where('grantee = user') + else + where("CONCAT(User, '@', Host) = current_user()") + end + end + + # Returns true if the current user can create and execute triggers on the + # given table. + def self.create_and_execute_trigger?(table) + priv = + if Database.postgresql? + where(privilege_type: 'TRIGGER', table_name: table) + else + where(Trigger_priv: 'Y') + end + + priv.scope_to_current_user.any? + end + end + end +end diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 5e2c6cc5cad..fb14798efe6 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -358,6 +358,8 @@ module Gitlab raise 'rename_column_concurrently can not be run inside a transaction' end + check_trigger_permissions!(table) + old_col = column_for(table, old) new_type = type || old_col.type @@ -430,6 +432,8 @@ module Gitlab def cleanup_concurrent_column_rename(table, old, new) trigger_name = rename_trigger_name(table, old, new) + check_trigger_permissions!(table) + if Database.postgresql? remove_rename_triggers_for_postgresql(table, trigger_name) else @@ -485,14 +489,14 @@ module Gitlab # Removes the triggers used for renaming a PostgreSQL column concurrently. def remove_rename_triggers_for_postgresql(table, trigger) - execute("DROP TRIGGER #{trigger} ON #{table}") - execute("DROP FUNCTION #{trigger}()") + execute("DROP TRIGGER IF EXISTS #{trigger} ON #{table}") + execute("DROP FUNCTION IF EXISTS #{trigger}()") end # Removes the triggers used for renaming a MySQL column concurrently. def remove_rename_triggers_for_mysql(trigger) - execute("DROP TRIGGER #{trigger}_insert") - execute("DROP TRIGGER #{trigger}_update") + execute("DROP TRIGGER IF EXISTS #{trigger}_insert") + execute("DROP TRIGGER IF EXISTS #{trigger}_update") end # Returns the (base) name to use for triggers when renaming columns. @@ -625,6 +629,30 @@ module Gitlab conn.llen("queue:#{queue_name}") end end + + def check_trigger_permissions!(table) + unless Grant.create_and_execute_trigger?(table) + dbname = Database.database_name + user = Database.username + + raise <<-EOF +Your database user is not allowed to create, drop, or execute triggers on the +table #{table}. + +If you are using PostgreSQL you can solve this by logging in to the GitLab +database (#{dbname}) using a super user and running: + + ALTER #{user} WITH SUPERUSER + +For MySQL you instead need to run: + + GRANT ALL PRIVILEGES ON *.* TO #{user}@'%' + +Both queries will grant the user super user permissions, ensuring you don't run +into similar problems in the future (e.g. when new tables are created). + EOF + end + end end end end diff --git a/spec/lib/gitlab/database/grant_spec.rb b/spec/lib/gitlab/database/grant_spec.rb new file mode 100644 index 00000000000..651da3e8476 --- /dev/null +++ b/spec/lib/gitlab/database/grant_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe Gitlab::Database::Grant do + describe '.scope_to_current_user' do + it 'scopes the relation to the current user' do + user = Gitlab::Database.username + column = Gitlab::Database.postgresql? ? :grantee : :User + names = described_class.scope_to_current_user.pluck(column).uniq + + expect(names).to eq([user]) + end + end + + describe '.create_and_execute_trigger' do + it 'returns true when the user can create and execute a trigger' do + # We assume the DB/user is set up correctly so that triggers can be + # created, which is necessary anyway for other tests to work. + expect(described_class.create_and_execute_trigger?('users')).to eq(true) + end + + it 'returns false when the user can not create and/or execute a trigger' do + allow(described_class).to receive(:scope_to_current_user) + .and_return(described_class.none) + + result = described_class.create_and_execute_trigger?('kittens') + + expect(result).to eq(false) + end + end +end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index c25fd459dd7..1bcdc369c44 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -450,6 +450,8 @@ describe Gitlab::Database::MigrationHelpers do it 'renames a column concurrently' do allow(Gitlab::Database).to receive(:postgresql?).and_return(false) + expect(model).to receive(:check_trigger_permissions!).with(:users) + expect(model).to receive(:install_rename_triggers_for_mysql) .with(trigger_name, 'users', 'old', 'new') @@ -477,6 +479,8 @@ describe Gitlab::Database::MigrationHelpers do it 'renames a column concurrently' do allow(Gitlab::Database).to receive(:postgresql?).and_return(true) + expect(model).to receive(:check_trigger_permissions!).with(:users) + expect(model).to receive(:install_rename_triggers_for_postgresql) .with(trigger_name, 'users', 'old', 'new') @@ -506,6 +510,8 @@ describe Gitlab::Database::MigrationHelpers do it 'cleans up the renaming procedure for PostgreSQL' do allow(Gitlab::Database).to receive(:postgresql?).and_return(true) + expect(model).to receive(:check_trigger_permissions!).with(:users) + expect(model).to receive(:remove_rename_triggers_for_postgresql) .with(:users, /trigger_.{12}/) @@ -517,6 +523,8 @@ describe Gitlab::Database::MigrationHelpers do it 'cleans up the renaming procedure for MySQL' do allow(Gitlab::Database).to receive(:postgresql?).and_return(false) + expect(model).to receive(:check_trigger_permissions!).with(:users) + expect(model).to receive(:remove_rename_triggers_for_mysql) .with(/trigger_.{12}/) @@ -573,8 +581,8 @@ describe Gitlab::Database::MigrationHelpers do describe '#remove_rename_triggers_for_postgresql' do it 'removes the function and trigger' do - expect(model).to receive(:execute).with('DROP TRIGGER foo ON bar') - expect(model).to receive(:execute).with('DROP FUNCTION foo()') + expect(model).to receive(:execute).with('DROP TRIGGER IF EXISTS foo ON bar') + expect(model).to receive(:execute).with('DROP FUNCTION IF EXISTS foo()') model.remove_rename_triggers_for_postgresql('bar', 'foo') end @@ -582,8 +590,8 @@ describe Gitlab::Database::MigrationHelpers do describe '#remove_rename_triggers_for_mysql' do it 'removes the triggers' do - expect(model).to receive(:execute).with('DROP TRIGGER foo_insert') - expect(model).to receive(:execute).with('DROP TRIGGER foo_update') + expect(model).to receive(:execute).with('DROP TRIGGER IF EXISTS foo_insert') + expect(model).to receive(:execute).with('DROP TRIGGER IF EXISTS foo_update') model.remove_rename_triggers_for_mysql('foo') end @@ -890,4 +898,20 @@ describe Gitlab::Database::MigrationHelpers do end end end + + describe '#check_trigger_permissions!' do + it 'does nothing when the user has the correct permissions' do + expect { model.check_trigger_permissions!('users') } + .not_to raise_error(RuntimeError) + end + + it 'raises RuntimeError when the user does not have the correct permissions' do + allow(Gitlab::Database::Grant).to receive(:create_and_execute_trigger?) + .with('kittens') + .and_return(false) + + expect { model.check_trigger_permissions!('kittens') } + .to raise_error(RuntimeError, /Your database user is not allowed/) + end + end end -- cgit v1.2.1 From 11da029edb0bf09eb906301bd0692ed1d74d25eb Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 28 Aug 2017 13:50:21 +0200 Subject: Move GPG signed commits docs to new location Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/36804 --- app/views/profiles/gpg_keys/index.html.haml | 2 +- .../projects/commit/_signature_badge.html.haml | 2 +- doc/README.md | 3 +- .../img/profile_settings_gpg_keys_paste_pub.png | Bin 24514 -> 0 bytes .../img/profile_settings_gpg_keys_single_key.png | Bin 4403 -> 0 bytes .../img/project_signed_and_unsigned_commits.png | Bin 41193 -> 0 bytes .../project_signed_commit_unverified_signature.png | Bin 9542 -> 0 bytes .../project_signed_commit_verified_signature.png | Bin 14029 -> 0 bytes doc/user/project/gpg_signed_commits/index.md | 246 +-------------------- .../img/profile_settings_gpg_keys_paste_pub.png | Bin 0 -> 24514 bytes .../img/profile_settings_gpg_keys_single_key.png | Bin 0 -> 4403 bytes .../img/project_signed_and_unsigned_commits.png | Bin 0 -> 41193 bytes .../project_signed_commit_unverified_signature.png | Bin 0 -> 9542 bytes .../project_signed_commit_verified_signature.png | Bin 0 -> 14029 bytes .../project/repository/gpg_signed_commits/index.md | 245 ++++++++++++++++++++ doc/user/project/repository/index.md | 4 +- 16 files changed, 253 insertions(+), 249 deletions(-) delete mode 100644 doc/user/project/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png delete mode 100644 doc/user/project/gpg_signed_commits/img/profile_settings_gpg_keys_single_key.png delete mode 100644 doc/user/project/gpg_signed_commits/img/project_signed_and_unsigned_commits.png delete mode 100644 doc/user/project/gpg_signed_commits/img/project_signed_commit_unverified_signature.png delete mode 100644 doc/user/project/gpg_signed_commits/img/project_signed_commit_verified_signature.png create mode 100644 doc/user/project/repository/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png create mode 100644 doc/user/project/repository/gpg_signed_commits/img/profile_settings_gpg_keys_single_key.png create mode 100644 doc/user/project/repository/gpg_signed_commits/img/project_signed_and_unsigned_commits.png create mode 100644 doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_unverified_signature.png create mode 100644 doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_verified_signature.png create mode 100644 doc/user/project/repository/gpg_signed_commits/index.md diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml index 720a97cddb7..8dbb8aef31b 100644 --- a/app/views/profiles/gpg_keys/index.html.haml +++ b/app/views/profiles/gpg_keys/index.html.haml @@ -12,7 +12,7 @@ Add a GPG key %p.profile-settings-content Before you can add a GPG key you need to - = link_to 'generate it.', help_page_path('user/project/gpg_signed_commits/index.md') + = link_to 'generate it.', help_page_path('user/project/repository/gpg_signed_commits/index.md') = render 'form' %hr %h5 diff --git a/app/views/projects/commit/_signature_badge.html.haml b/app/views/projects/commit/_signature_badge.html.haml index a3783b31b86..d06b29db838 100644 --- a/app/views/projects/commit/_signature_badge.html.haml +++ b/app/views/projects/commit/_signature_badge.html.haml @@ -12,7 +12,7 @@ %span.monospace= signature.gpg_key_primary_keyid - = link_to('Learn more about signing commits', help_page_path('user/project/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link') + = link_to('Learn more about signing commits', help_page_path('user/project/repository/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link') %button{ class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'auto top', title: title, content: content } } = label diff --git a/doc/README.md b/doc/README.md index 76d4c3e51fe..63ba8ff03e9 100644 --- a/doc/README.md +++ b/doc/README.md @@ -77,6 +77,8 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i - [Create a branch](user/project/repository/web_editor.md#create-a-new-branch) - [Protected branches](user/project/protected_branches.md#protected-branches) - [Delete merged branches](user/project/repository/branches/index.md#delete-merged-branches) +- Commits + - [Signing commits](user/project/repository/gpg_signed_commits/index.md): use GPG to sign your commits. ### Issues and Merge Requests (MRs) @@ -98,7 +100,6 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i - [Git](topics/git/index.md): Getting started with Git, branching strategies, Git LFS, advanced use. - [Git cheatsheet](https://gitlab.com/gitlab-com/marketing/raw/master/design/print/git-cheatsheet/print-pdf/git-cheatsheet.pdf): Download a PDF describing the most used Git operations. - [GitLab Flow](workflow/gitlab_flow.md): explore the best of Git with the GitLab Flow strategy. -- [Signing commits](user/project/gpg_signed_commits/index.md): use GPG to sign your commits. ### Migrate and import your projects from other platforms diff --git a/doc/user/project/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png b/doc/user/project/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png deleted file mode 100644 index 8e26d98f1b0..00000000000 Binary files a/doc/user/project/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png and /dev/null differ diff --git a/doc/user/project/gpg_signed_commits/img/profile_settings_gpg_keys_single_key.png b/doc/user/project/gpg_signed_commits/img/profile_settings_gpg_keys_single_key.png deleted file mode 100644 index 5c14df36d73..00000000000 Binary files a/doc/user/project/gpg_signed_commits/img/profile_settings_gpg_keys_single_key.png and /dev/null differ diff --git a/doc/user/project/gpg_signed_commits/img/project_signed_and_unsigned_commits.png b/doc/user/project/gpg_signed_commits/img/project_signed_and_unsigned_commits.png deleted file mode 100644 index 33936a7d6d7..00000000000 Binary files a/doc/user/project/gpg_signed_commits/img/project_signed_and_unsigned_commits.png and /dev/null differ diff --git a/doc/user/project/gpg_signed_commits/img/project_signed_commit_unverified_signature.png b/doc/user/project/gpg_signed_commits/img/project_signed_commit_unverified_signature.png deleted file mode 100644 index 22565cf7c7e..00000000000 Binary files a/doc/user/project/gpg_signed_commits/img/project_signed_commit_unverified_signature.png and /dev/null differ diff --git a/doc/user/project/gpg_signed_commits/img/project_signed_commit_verified_signature.png b/doc/user/project/gpg_signed_commits/img/project_signed_commit_verified_signature.png deleted file mode 100644 index 1778b2ddf2b..00000000000 Binary files a/doc/user/project/gpg_signed_commits/img/project_signed_commit_verified_signature.png and /dev/null differ diff --git a/doc/user/project/gpg_signed_commits/index.md b/doc/user/project/gpg_signed_commits/index.md index 3ea2203c895..261eedeb412 100644 --- a/doc/user/project/gpg_signed_commits/index.md +++ b/doc/user/project/gpg_signed_commits/index.md @@ -1,245 +1 @@ -# Signing commits with GPG - -> [Introduced][ce-9546] in GitLab 9.5. - -GitLab can show whether a commit is verified or not when signed with a GPG key. -All you need to do is upload the public GPG key in your profile settings. - -GPG verified tags are not supported yet. - -## Getting started with GPG - -Here are a few guides to get you started with GPG: - -- [Git Tools - Signing Your Work](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work) -- [Managing OpenPGP Keys](https://riseup.net/en/security/message-security/openpgp/gpg-keys) -- [OpenPGP Best Practices](https://riseup.net/en/security/message-security/openpgp/best-practices) -- [Creating a new GPG key with subkeys](https://www.void.gr/kargig/blog/2013/12/02/creating-a-new-gpg-key-with-subkeys/) (advanced) - -## How GitLab handles GPG - -GitLab uses its own keyring to verify the GPG signature. It does not access any -public key server. - -In order to have a commit verified on GitLab the corresponding public key needs -to be uploaded to GitLab. For a signature to be verified two prerequisites need -to be met: - -1. The public key needs to be added your GitLab account -1. One of the emails in the GPG key matches your **primary** email - -## Generating a GPG key - -If you don't already have a GPG key, the following steps will help you get -started: - -1. [Install GPG](https://www.gnupg.org/download/index.html) for your operating system -1. Generate the private/public key pair with the following command: - - ```sh - gpg --full-gen-key - ``` - - This will spawn a series of questions. - -1. The first question is which algorithm can be used. Select the kind you want - or press Enter to choose the default (RSA and RSA): - - ``` - Please select what kind of key you want: - (1) RSA and RSA (default) - (2) DSA and Elgamal - (3) DSA (sign only) - (4) RSA (sign only) - Your selection? 1 - ``` - -1. The next question is key length. We recommend to choose the highest value - which is `4096`: - - ``` - RSA keys may be between 1024 and 4096 bits long. - What keysize do you want? (2048) 4096 - Requested keysize is 4096 bits - ``` -1. Next, you need to specify the validity period of your key. This is something - subjective, and you can use the default value which is to never expire: - - ``` - Please specify how long the key should be valid. - 0 = key does not expire - = key expires in n days - w = key expires in n weeks - m = key expires in n months - y = key expires in n years - Key is valid for? (0) 0 - Key does not expire at all - ``` - -1. Confirm that the answers you gave were correct by typing `y`: - - ``` - Is this correct? (y/N) y - ``` - -1. Enter you real name, the email address to be associated with this key (should - match the primary email address you use in GitLab) and an optional comment - (press Enter to skip): - - ``` - GnuPG needs to construct a user ID to identify your key. - - Real name: Mr. Robot - Email address: mr@robot.sh - Comment: - You selected this USER-ID: - "Mr. Robot " - - Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O - ``` - -1. Pick a strong password when asked and type it twice to confirm. -1. Use the following command to list the private GPG key you just created: - - ``` - gpg --list-secret-keys mr@robot.sh - ``` - - Replace `mr@robot.sh` with the email address you entered above. - -1. Copy the GPG key ID that starts with `sec`. In the following example, that's - `0x30F2B65B9246B6CA`: - - ``` - sec rsa4096/0x30F2B65B9246B6CA 2017-08-18 [SC] - D5E4F29F3275DC0CDA8FFC8730F2B65B9246B6CA - uid [ultimate] Mr. Robot - ssb rsa4096/0xB7ABC0813E4028C0 2017-08-18 [E] - ``` - -1. Export the public key of that ID (replace your key ID from the previous step): - - ``` - gpg --armor --export 0x30F2B65B9246B6CA - ``` - -1. Finally, copy the public key and [add it in your profile settings](#adding-a-gpg-key-to-your-account) - -## Adding a GPG key to your account - ->**Note:** -Once you add a key, you cannot edit it, only remove it. In case the paste -didn't work, you'll have to remove the offending key and re-add it. - -You can add a GPG key in your profile's settings: - -1. On the upper right corner, click on your avatar and go to your **Settings**. - - ![Settings dropdown](../../profile/img/profile_settings_dropdown.png) - -1. Navigate to the **GPG keys** tab and paste your _public_ key in the 'Key' - box. - - ![Paste GPG public key](img/profile_settings_gpg_keys_paste_pub.png) - -1. Finally, click on **Add key** to add it to GitLab. You will be able to see - its fingerprint, the corresponding email address and creation date. - - ![GPG key single page](img/profile_settings_gpg_keys_single_key.png) - -## Associating your GPG key with Git - -After you have [created your GPG key](#generating-a-gpg-key) and [added it to -your account](#adding-a-gpg-key-to-your-account), it's time to tell Git which -key to use. - -1. Use the following command to list the private GPG key you just created: - - ``` - gpg --list-secret-keys mr@robot.sh - ``` - - Replace `mr@robot.sh` with the email address you entered above. - -1. Copy the GPG key ID that starts with `sec`. In the following example, that's - `0x30F2B65B9246B6CA`: - - ``` - sec rsa4096/0x30F2B65B9246B6CA 2017-08-18 [SC] - D5E4F29F3275DC0CDA8FFC8730F2B65B9246B6CA - uid [ultimate] Mr. Robot - ssb rsa4096/0xB7ABC0813E4028C0 2017-08-18 [E] - ``` - -1. Tell Git to use that key to sign the commits: - - ``` - git config --global user.signingkey 0x30F2B65B9246B6CA - ``` - - Replace `0x30F2B65B9246B6CA` with your GPG key ID. - -## Signing commits - -After you have [created your GPG key](#generating-a-gpg-key) and [added it to -your account](#adding-a-gpg-key-to-your-account), you can start signing your -commits: - -1. Commit like you used to, the only difference is the addition of the `-S` flag: - - ``` - git commit -S -m "My commit msg" - ``` - -1. Enter the passphrase of your GPG key when asked. -1. Push to GitLab and check that your commits [are verified](#verifying-commits). - -If you don't want to type the `-S` flag every time you commit, you can tell Git -to sign your commits automatically: - -``` -git config --global commit.gpgsign true -``` - -## Verifying commits - -1. Within a project or [merge request](../merge_requests/index.md), navigate to - the **Commits** tab. Signed commits will show a badge containing either - "Verified" or "Unverified", depending on the verification status of the GPG - signature. - - ![Signed and unsigned commits](img/project_signed_and_unsigned_commits.png) - -1. By clicking on the GPG badge, details of the signature are displayed. - - ![Signed commit with verified signature](img/project_signed_commit_verified_signature.png) - - ![Signed commit with verified signature](img/project_signed_commit_unverified_signature.png) - -## Revoking a GPG key - -Revoking a key **unverifies** already signed commits. Commits that were -verified by using this key will change to an unverified state. Future commits -will also stay unverified once you revoke this key. This action should be used -in case your key has been compromised. - -To revoke a GPG key: - -1. On the upper right corner, click on your avatar and go to your **Settings**. -1. Navigate to the **GPG keys** tab. -1. Click on **Revoke** besides the GPG key you want to delete. - -## Removing a GPG key - -Removing a key **does not unverify** already signed commits. Commits that were -verified by using this key will stay verified. Only unpushed commits will stay -unverified once you remove this key. To unverify already signed commits, you need -to [revoke the associated GPG key](#revoking-a-gpg-key) from your account. - -To remove a GPG key from your account: - -1. On the upper right corner, click on your avatar and go to your **Settings**. -1. Navigate to the **GPG keys** tab. -1. Click on the trash icon besides the GPG key you want to delete. - -[ce-9546]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9546 +This document was moved to [another location](../repository/gpg_signed_commits/index.md). diff --git a/doc/user/project/repository/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png b/doc/user/project/repository/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png new file mode 100644 index 00000000000..8e26d98f1b0 Binary files /dev/null and b/doc/user/project/repository/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png differ diff --git a/doc/user/project/repository/gpg_signed_commits/img/profile_settings_gpg_keys_single_key.png b/doc/user/project/repository/gpg_signed_commits/img/profile_settings_gpg_keys_single_key.png new file mode 100644 index 00000000000..5c14df36d73 Binary files /dev/null and b/doc/user/project/repository/gpg_signed_commits/img/profile_settings_gpg_keys_single_key.png differ diff --git a/doc/user/project/repository/gpg_signed_commits/img/project_signed_and_unsigned_commits.png b/doc/user/project/repository/gpg_signed_commits/img/project_signed_and_unsigned_commits.png new file mode 100644 index 00000000000..33936a7d6d7 Binary files /dev/null and b/doc/user/project/repository/gpg_signed_commits/img/project_signed_and_unsigned_commits.png differ diff --git a/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_unverified_signature.png b/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_unverified_signature.png new file mode 100644 index 00000000000..22565cf7c7e Binary files /dev/null and b/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_unverified_signature.png differ diff --git a/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_verified_signature.png b/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_verified_signature.png new file mode 100644 index 00000000000..1778b2ddf2b Binary files /dev/null and b/doc/user/project/repository/gpg_signed_commits/img/project_signed_commit_verified_signature.png differ diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md new file mode 100644 index 00000000000..ff419d714f9 --- /dev/null +++ b/doc/user/project/repository/gpg_signed_commits/index.md @@ -0,0 +1,245 @@ +# Signing commits with GPG + +> [Introduced][ce-9546] in GitLab 9.5. + +GitLab can show whether a commit is verified or not when signed with a GPG key. +All you need to do is upload the public GPG key in your profile settings. + +GPG verified tags are not supported yet. + +## Getting started with GPG + +Here are a few guides to get you started with GPG: + +- [Git Tools - Signing Your Work](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work) +- [Managing OpenPGP Keys](https://riseup.net/en/security/message-security/openpgp/gpg-keys) +- [OpenPGP Best Practices](https://riseup.net/en/security/message-security/openpgp/best-practices) +- [Creating a new GPG key with subkeys](https://www.void.gr/kargig/blog/2013/12/02/creating-a-new-gpg-key-with-subkeys/) (advanced) + +## How GitLab handles GPG + +GitLab uses its own keyring to verify the GPG signature. It does not access any +public key server. + +In order to have a commit verified on GitLab the corresponding public key needs +to be uploaded to GitLab. For a signature to be verified two prerequisites need +to be met: + +1. The public key needs to be added your GitLab account +1. One of the emails in the GPG key matches your **primary** email + +## Generating a GPG key + +If you don't already have a GPG key, the following steps will help you get +started: + +1. [Install GPG](https://www.gnupg.org/download/index.html) for your operating system +1. Generate the private/public key pair with the following command: + + ```sh + gpg --full-gen-key + ``` + + This will spawn a series of questions. + +1. The first question is which algorithm can be used. Select the kind you want + or press Enter to choose the default (RSA and RSA): + + ``` + Please select what kind of key you want: + (1) RSA and RSA (default) + (2) DSA and Elgamal + (3) DSA (sign only) + (4) RSA (sign only) + Your selection? 1 + ``` + +1. The next question is key length. We recommend to choose the highest value + which is `4096`: + + ``` + RSA keys may be between 1024 and 4096 bits long. + What keysize do you want? (2048) 4096 + Requested keysize is 4096 bits + ``` +1. Next, you need to specify the validity period of your key. This is something + subjective, and you can use the default value which is to never expire: + + ``` + Please specify how long the key should be valid. + 0 = key does not expire + = key expires in n days + w = key expires in n weeks + m = key expires in n months + y = key expires in n years + Key is valid for? (0) 0 + Key does not expire at all + ``` + +1. Confirm that the answers you gave were correct by typing `y`: + + ``` + Is this correct? (y/N) y + ``` + +1. Enter you real name, the email address to be associated with this key (should + match the primary email address you use in GitLab) and an optional comment + (press Enter to skip): + + ``` + GnuPG needs to construct a user ID to identify your key. + + Real name: Mr. Robot + Email address: mr@robot.sh + Comment: + You selected this USER-ID: + "Mr. Robot " + + Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O + ``` + +1. Pick a strong password when asked and type it twice to confirm. +1. Use the following command to list the private GPG key you just created: + + ``` + gpg --list-secret-keys mr@robot.sh + ``` + + Replace `mr@robot.sh` with the email address you entered above. + +1. Copy the GPG key ID that starts with `sec`. In the following example, that's + `0x30F2B65B9246B6CA`: + + ``` + sec rsa4096/0x30F2B65B9246B6CA 2017-08-18 [SC] + D5E4F29F3275DC0CDA8FFC8730F2B65B9246B6CA + uid [ultimate] Mr. Robot + ssb rsa4096/0xB7ABC0813E4028C0 2017-08-18 [E] + ``` + +1. Export the public key of that ID (replace your key ID from the previous step): + + ``` + gpg --armor --export 0x30F2B65B9246B6CA + ``` + +1. Finally, copy the public key and [add it in your profile settings](#adding-a-gpg-key-to-your-account) + +## Adding a GPG key to your account + +>**Note:** +Once you add a key, you cannot edit it, only remove it. In case the paste +didn't work, you'll have to remove the offending key and re-add it. + +You can add a GPG key in your profile's settings: + +1. On the upper right corner, click on your avatar and go to your **Settings**. + + ![Settings dropdown](../../../profile/img/profile_settings_dropdown.png) + +1. Navigate to the **GPG keys** tab and paste your _public_ key in the 'Key' + box. + + ![Paste GPG public key](img/profile_settings_gpg_keys_paste_pub.png) + +1. Finally, click on **Add key** to add it to GitLab. You will be able to see + its fingerprint, the corresponding email address and creation date. + + ![GPG key single page](img/profile_settings_gpg_keys_single_key.png) + +## Associating your GPG key with Git + +After you have [created your GPG key](#generating-a-gpg-key) and [added it to +your account](#adding-a-gpg-key-to-your-account), it's time to tell Git which +key to use. + +1. Use the following command to list the private GPG key you just created: + + ``` + gpg --list-secret-keys mr@robot.sh + ``` + + Replace `mr@robot.sh` with the email address you entered above. + +1. Copy the GPG key ID that starts with `sec`. In the following example, that's + `0x30F2B65B9246B6CA`: + + ``` + sec rsa4096/0x30F2B65B9246B6CA 2017-08-18 [SC] + D5E4F29F3275DC0CDA8FFC8730F2B65B9246B6CA + uid [ultimate] Mr. Robot + ssb rsa4096/0xB7ABC0813E4028C0 2017-08-18 [E] + ``` + +1. Tell Git to use that key to sign the commits: + + ``` + git config --global user.signingkey 0x30F2B65B9246B6CA + ``` + + Replace `0x30F2B65B9246B6CA` with your GPG key ID. + +## Signing commits + +After you have [created your GPG key](#generating-a-gpg-key) and [added it to +your account](#adding-a-gpg-key-to-your-account), you can start signing your +commits: + +1. Commit like you used to, the only difference is the addition of the `-S` flag: + + ``` + git commit -S -m "My commit msg" + ``` + +1. Enter the passphrase of your GPG key when asked. +1. Push to GitLab and check that your commits [are verified](#verifying-commits). + +If you don't want to type the `-S` flag every time you commit, you can tell Git +to sign your commits automatically: + +``` +git config --global commit.gpgsign true +``` + +## Verifying commits + +1. Within a project or [merge request](../../merge_requests/index.md), navigate to + the **Commits** tab. Signed commits will show a badge containing either + "Verified" or "Unverified", depending on the verification status of the GPG + signature. + + ![Signed and unsigned commits](img/project_signed_and_unsigned_commits.png) + +1. By clicking on the GPG badge, details of the signature are displayed. + + ![Signed commit with verified signature](img/project_signed_commit_verified_signature.png) + + ![Signed commit with verified signature](img/project_signed_commit_unverified_signature.png) + +## Revoking a GPG key + +Revoking a key **unverifies** already signed commits. Commits that were +verified by using this key will change to an unverified state. Future commits +will also stay unverified once you revoke this key. This action should be used +in case your key has been compromised. + +To revoke a GPG key: + +1. On the upper right corner, click on your avatar and go to your **Settings**. +1. Navigate to the **GPG keys** tab. +1. Click on **Revoke** besides the GPG key you want to delete. + +## Removing a GPG key + +Removing a key **does not unverify** already signed commits. Commits that were +verified by using this key will stay verified. Only unpushed commits will stay +unverified once you remove this key. To unverify already signed commits, you need +to [revoke the associated GPG key](#revoking-a-gpg-key) from your account. + +To remove a GPG key from your account: + +1. On the upper right corner, click on your avatar and go to your **Settings**. +1. Navigate to the **GPG keys** tab. +1. Click on the trash icon besides the GPG key you want to delete. + +[ce-9546]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9546 diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index 5e5ae880518..235af83353d 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -22,7 +22,7 @@ you to [connect with GitLab via SSH](../../../ssh/README.md). ## Files -## Create and edit files +### Create and edit files Host your codebase in GitLab repositories by pushing your files to GitLab. You can either use the user interface (UI), or connect your local computer @@ -111,6 +111,8 @@ right from the UI. - **Revert a commit:** Easily [revert a commit](../merge_requests/revert_changes.md#reverting-a-commit) from the UI to a selected branch. +- **Sign a commit:** +Use GPG to [sign your commits](gpg_signed_commits/index.md). ## Repository size -- cgit v1.2.1 From 6d9fb6b0b779d7065f0c36ec15a42d0d459abbeb Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 29 Aug 2017 21:11:38 +0800 Subject: It doesn't seem that rubocop is complaining for me --- app/models/repository.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 0f67395b88b..9fe39172b19 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -60,12 +60,10 @@ class Repository @project = project end - def equals(other) + def ==(other) @disk_path == other.disk_path end - alias_method :==, :equals - def raw_repository return nil unless full_path -- cgit v1.2.1 From 8cbc9c4ed032e7a4f73aeb86a0bec152a1c7b696 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Sat, 19 Aug 2017 12:49:39 -0500 Subject: Add time stats documentation to issue and merge request end points --- doc/api/issues.md | 56 +++++++++++++++++++++++++++++++++++++++++- doc/api/merge_requests.md | 62 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 110 insertions(+), 8 deletions(-) diff --git a/doc/api/issues.md b/doc/api/issues.md index f30ed08d0fa..14635114a31 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -101,6 +101,12 @@ Example response: "user_notes_count": 1, "due_date": "2016-07-22", "web_url": "http://example.com/example/example/issues/6", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, "confidential": false } ] @@ -198,6 +204,12 @@ Example response: "user_notes_count": 1, "due_date": null, "web_url": "http://example.com/example/example/issues/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, "confidential": false } ] @@ -296,6 +308,12 @@ Example response: "user_notes_count": 1, "due_date": "2016-07-22", "web_url": "http://example.com/example/example/issues/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, "confidential": false } ] @@ -372,6 +390,12 @@ Example response: "user_notes_count": 1, "due_date": null, "web_url": "http://example.com/example/example/issues/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, "confidential": false, "_links": { "self": "http://example.com/api/v4/projects/1/issues/2", @@ -440,6 +464,12 @@ Example response: "user_notes_count": 0, "due_date": null, "web_url": "http://example.com/example/example/issues/14", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, "confidential": false, "_links": { "self": "http://example.com/api/v4/projects/1/issues/2", @@ -509,6 +539,12 @@ Example response: "user_notes_count": 0, "due_date": "2016-07-22", "web_url": "http://example.com/example/example/issues/15", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, "confidential": false, "_links": { "self": "http://example.com/api/v4/projects/1/issues/2", @@ -601,6 +637,12 @@ Example response: }, "due_date": null, "web_url": "http://example.com/example/example/issues/11", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, "confidential": false, "_links": { "self": "http://example.com/api/v4/projects/1/issues/2", @@ -672,6 +714,12 @@ Example response: }, "due_date": null, "web_url": "http://example.com/example/example/issues/11", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, "confidential": false, "_links": { "self": "http://example.com/api/v4/projects/1/issues/2", @@ -1001,7 +1049,13 @@ Example response: "user_notes_count": 1, "should_remove_source_branch": null, "force_remove_source_branch": false, - "web_url": "https://gitlab.example.com/gitlab-org/gitlab-test/merge_requests/6432" + "web_url": "https://gitlab.example.com/gitlab-org/gitlab-test/merge_requests/6432", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } } ] ``` diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 802e5362d70..4f67aa4b9d4 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -92,7 +92,13 @@ Parameters: "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, - "web_url": "http://example.com/example/example/merge_requests/1" + "web_url": "http://example.com/example/example/merge_requests/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } } ] ``` @@ -181,7 +187,13 @@ Parameters: "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, - "web_url": "http://example.com/example/example/merge_requests/1" + "web_url": "http://example.com/example/example/merge_requests/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } } ] ``` @@ -250,7 +262,13 @@ Parameters: "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, - "web_url": "http://example.com/example/example/merge_requests/1" + "web_url": "http://example.com/example/example/merge_requests/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } } ``` @@ -356,6 +374,12 @@ Parameters: "should_remove_source_branch": true, "force_remove_source_branch": false, "web_url": "http://example.com/example/example/merge_requests/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } "changes": [ { "old_path": "VERSION", @@ -442,7 +466,13 @@ POST /projects/:id/merge_requests "user_notes_count": 0, "should_remove_source_branch": true, "force_remove_source_branch": false, - "web_url": "http://example.com/example/example/merge_requests/1" + "web_url": "http://example.com/example/example/merge_requests/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } } ``` @@ -519,7 +549,13 @@ Must include at least one non-required attribute from above. "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, - "web_url": "http://example.com/example/example/merge_requests/1" + "web_url": "http://example.com/example/example/merge_requests/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } } ``` @@ -617,7 +653,13 @@ Parameters: "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, - "web_url": "http://example.com/example/example/merge_requests/1" + "web_url": "http://example.com/example/example/merge_requests/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } } ``` @@ -687,7 +729,13 @@ Parameters: "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, - "web_url": "http://example.com/example/example/merge_requests/1" + "web_url": "http://example.com/example/example/merge_requests/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + } } ``` -- cgit v1.2.1 From 6782b406f77d237e58c23a3bce444e3cd4dde849 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Sat, 19 Aug 2017 12:56:17 -0500 Subject: Add time stats to API schema for issue and merge request end points --- spec/fixtures/api/schemas/public_api/v4/issues.json | 8 +++++++- spec/fixtures/api/schemas/public_api/v4/merge_requests.json | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/spec/fixtures/api/schemas/public_api/v4/issues.json b/spec/fixtures/api/schemas/public_api/v4/issues.json index bd6bfc03199..8acd9488215 100644 --- a/spec/fixtures/api/schemas/public_api/v4/issues.json +++ b/spec/fixtures/api/schemas/public_api/v4/issues.json @@ -78,7 +78,13 @@ "downvotes": { "type": "integer" }, "due_date": { "type": ["date", "null"] }, "confidential": { "type": "boolean" }, - "web_url": { "type": "uri" } + "web_url": { "type": "uri" }, + "time_stats": { + "time_estimate": { "type": "integer" }, + "total_time_spent": { "type": "integer" }, + "human_time_estimate": { "type": ["string", "null"] }, + "human_total_time_spent": { "type": ["string", "null"] } + } }, "required": [ "id", "iid", "project_id", "title", "description", diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json index 60aa47c1259..31b3f4ba946 100644 --- a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json +++ b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json @@ -72,7 +72,13 @@ "user_notes_count": { "type": "integer" }, "should_remove_source_branch": { "type": ["boolean", "null"] }, "force_remove_source_branch": { "type": ["boolean", "null"] }, - "web_url": { "type": "uri" } + "web_url": { "type": "uri" }, + "time_stats": { + "time_estimate": { "type": "integer" }, + "total_time_spent": { "type": "integer" }, + "human_time_estimate": { "type": ["string", "null"] }, + "human_total_time_spent": { "type": ["string", "null"] } + } }, "required": [ "id", "iid", "project_id", "title", "description", -- cgit v1.2.1 From 2531fb3be9542c08231a6540a14b12d907b151e5 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Mon, 28 Aug 2017 19:02:52 -0500 Subject: Add missing N+1 test to issues spec --- spec/requests/api/issues_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 7d120e4a234..03b1d14828d 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -509,6 +509,18 @@ describe API::Issues, :mailer do describe "GET /projects/:id/issues" do let(:base_url) { "/projects/#{project.id}" } + it 'avoids N+1 queries' do + control_count = ActiveRecord::QueryRecorder.new do + get api("/projects/#{project.id}/issues", user) + end.count + + create(:issue, author: user, project: project) + + expect do + get api("/projects/#{project.id}/issues", user) + end.not_to exceed_query_limit(control_count) + end + it 'returns 404 when project does not exist' do get api('/projects/1000/issues', non_member) -- cgit v1.2.1 From ce1ce82045f168143ccc143f5200ea9da820d990 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Mon, 21 Aug 2017 17:42:03 -0500 Subject: Resolve new N+1 by adding preloads and metadata to issues end points --- lib/api/entities.rb | 22 ++++++++++++++++++++-- lib/api/issues.rb | 22 +++++++++++++++++++--- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index e8dd61e493f..e18c69b7a3d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -320,7 +320,10 @@ module API end class IssueBasic < ProjectEntity - expose :label_names, as: :labels + expose :labels do |issue, options| + # Avoids an N+1 query since labels are preloaded + issue.labels.map(&:title).sort + end expose :milestone, using: Entities::Milestone expose :assignees, :author, using: Entities::UserBasic @@ -329,7 +332,22 @@ module API end expose :user_notes_count - expose :upvotes, :downvotes + expose :upvotes do |issue, options| + if options[:issuable_metadata] + # Avoids an N+1 query when metadata is included + options[:issuable_metadata][issue.id].upvotes + else + issue.upvotes + end + end + expose :downvotes do |issue, options| + if options[:issuable_metadata] + # Avoids an N+1 query when metadata is included + options[:issuable_metadata][issue.id].downvotes + else + issue.downvotes + end + end expose :due_date expose :confidential diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 4cec1145f3a..64a425eb96e 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -4,6 +4,8 @@ module API before { authenticate! } + helpers ::Gitlab::IssuableMetadata + helpers do def find_issues(args = {}) args = params.merge(args) @@ -13,6 +15,7 @@ module API args[:label_name] = args.delete(:labels) issues = IssuesFinder.new(current_user, args).execute + .preload(:assignees, :labels, :notes) issues.reorder(args[:order_by] => args[:sort]) end @@ -65,7 +68,11 @@ module API get do issues = find_issues - present paginate(issues), with: Entities::IssueBasic, current_user: current_user + options = { with: Entities::IssueBasic, + current_user: current_user } + options[:issuable_metadata] = issuable_meta_data(issues, 'Issue') + + present paginate(issues), options end end @@ -86,7 +93,11 @@ module API issues = find_issues(group_id: group.id) - present paginate(issues), with: Entities::IssueBasic, current_user: current_user + options = { with: Entities::IssueBasic, + current_user: current_user } + options[:issuable_metadata] = issuable_meta_data(issues, 'Issue') + + present paginate(issues), options end end @@ -109,7 +120,12 @@ module API issues = find_issues(project_id: project.id) - present paginate(issues), with: Entities::IssueBasic, current_user: current_user, project: user_project + options = { with: Entities::IssueBasic, + current_user: current_user, + project: user_project } + options[:issuable_metadata] = issuable_meta_data(issues, 'Issue') + + present paginate(issues), options end desc 'Get a single project issue' do -- cgit v1.2.1 From 749c389345cf382b740277db62f7d4b849902d60 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Mon, 28 Aug 2017 19:02:26 -0500 Subject: Add time stats to issue and merge request API end points --- lib/api/entities.rb | 22 +++++++++++++++++++++- lib/api/issues.rb | 28 +++++++++++++++++----------- lib/api/merge_requests.rb | 2 +- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index e18c69b7a3d..803b48dd88a 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -354,6 +354,10 @@ module API expose :web_url do |issue, options| Gitlab::UrlBuilder.build(issue) end + + expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |issue| + issue + end end class Issue < IssueBasic @@ -383,10 +387,22 @@ module API end class IssuableTimeStats < Grape::Entity + format_with(:time_tracking_formatter) do |time_spent| + Gitlab::TimeTrackingFormatter.output(time_spent) + end + expose :time_estimate expose :total_time_spent expose :human_time_estimate - expose :human_total_time_spent + + with_options(format_with: :time_tracking_formatter) do + expose :total_time_spent, as: :human_total_time_spent + end + + def total_time_spent + # Avoids an N+1 query since timelogs are preloaded + object.timelogs.map(&:time_spent).sum + end end class ExternalIssue < Grape::Entity @@ -436,6 +452,10 @@ module API expose :web_url do |merge_request, options| Gitlab::UrlBuilder.build(merge_request) end + + expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |merge_request| + merge_request + end end class MergeRequest < MergeRequestBasic diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 64a425eb96e..c30e430ae85 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -15,7 +15,7 @@ module API args[:label_name] = args.delete(:labels) issues = IssuesFinder.new(current_user, args).execute - .preload(:assignees, :labels, :notes) + .preload(:assignees, :labels, :notes, :timelogs) issues.reorder(args[:order_by] => args[:sort]) end @@ -68,9 +68,11 @@ module API get do issues = find_issues - options = { with: Entities::IssueBasic, - current_user: current_user } - options[:issuable_metadata] = issuable_meta_data(issues, 'Issue') + options = { + with: Entities::IssueBasic, + current_user: current_user, + issuable_metadata: issuable_meta_data(issues, 'Issue') + } present paginate(issues), options end @@ -93,9 +95,11 @@ module API issues = find_issues(group_id: group.id) - options = { with: Entities::IssueBasic, - current_user: current_user } - options[:issuable_metadata] = issuable_meta_data(issues, 'Issue') + options = { + with: Entities::IssueBasic, + current_user: current_user, + issuable_metadata: issuable_meta_data(issues, 'Issue') + } present paginate(issues), options end @@ -120,10 +124,12 @@ module API issues = find_issues(project_id: project.id) - options = { with: Entities::IssueBasic, - current_user: current_user, - project: user_project } - options[:issuable_metadata] = issuable_meta_data(issues, 'Issue') + options = { + with: Entities::IssueBasic, + current_user: current_user, + project: user_project, + issuable_metadata: issuable_meta_data(issues, 'Issue') + } present paginate(issues), options end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 8810d4e441d..a3284afb745 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -21,7 +21,7 @@ module API return merge_requests if args[:view] == 'simple' merge_requests - .preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels) + .preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels, :timelogs) end params :merge_requests_params do -- cgit v1.2.1 From 54b0f57b2e286680b7b2dc2757c31536858478ce Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Sat, 19 Aug 2017 13:03:34 -0500 Subject: Add changelog --- .../28453-add-time-estimate-time-spent-to-api-issue-output.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/28453-add-time-estimate-time-spent-to-api-issue-output.yml diff --git a/changelogs/unreleased/28453-add-time-estimate-time-spent-to-api-issue-output.yml b/changelogs/unreleased/28453-add-time-estimate-time-spent-to-api-issue-output.yml new file mode 100644 index 00000000000..129cf505a3f --- /dev/null +++ b/changelogs/unreleased/28453-add-time-estimate-time-spent-to-api-issue-output.yml @@ -0,0 +1,4 @@ +--- +title: Add time stats to Issue and Merge Request API +merge_request: 13335 +author: @travismiller -- cgit v1.2.1 From e6630d7f7276dc5cec2a02e32dc74a0a060886ab Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 29 Aug 2017 22:42:02 +0800 Subject: Make sure inspect doesn't generate crazy string --- app/models/repository.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/repository.rb b/app/models/repository.rb index 9fe39172b19..04a76c365be 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -79,6 +79,10 @@ class Repository ) end + def inspect + "#<#{self.class.name}:#{@disk_path}>" + end + # # Git repository can contains some hidden refs like: # /refs/notes/* -- cgit v1.2.1 From 9d3ee1ff139650e064a41fd90d34cd3f558c1099 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 29 Aug 2017 22:42:34 +0800 Subject: Further break with_repo_branch_commit into parts So it's more clear what could happen. Also add more tests about the behaviour. --- app/models/repository.rb | 43 +++++++++++++++++++++++------------------- spec/models/repository_spec.rb | 36 ++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 04a76c365be..93017dfedf9 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1000,29 +1000,22 @@ class Repository end def with_repo_branch_commit(start_repository, start_branch_name) - tmp_ref = nil return yield nil if start_repository.empty_repo? - branch_commit = - if start_repository == self - commit(start_branch_name) + if start_repository == self + yield commit(start_branch_name) + else + sha = start_repository.commit(start_branch_name).sha + + if branch_commit = commit(sha) + yield branch_commit else - sha = start_repository.find_branch(start_branch_name).target - commit(sha) || - begin - tmp_ref = fetch_ref( - start_repository.path_to_repo, - "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}", - "refs/tmp/#{SecureRandom.hex}/head" - ) - - commit(start_repository.commit(start_branch_name).sha) - end + with_repo_tmp_commit( + start_repository, start_branch_name, sha) do |tmp_commit| + yield tmp_commit + end end - - yield branch_commit - ensure - rugged.references.delete(tmp_ref) if tmp_ref + end end def add_remote(name, url) @@ -1231,4 +1224,16 @@ class Repository .commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset) .map { |c| commit(c) } end + + def with_repo_tmp_commit(start_repository, start_branch_name, sha) + tmp_ref = fetch_ref( + start_repository.path_to_repo, + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}", + "refs/tmp/#{SecureRandom.hex}/head" + ) + + yield commit(sha) + ensure + rugged.references.delete(tmp_ref) if tmp_ref + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 462e92b8b62..8d9a86d952b 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -923,13 +923,16 @@ describe Repository, models: true do describe '#update_branch_with_hooks' do let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev + let(:updating_ref) { 'refs/heads/feature' } + let(:target_project) { project } + let(:target_repository) { target_project.repository } context 'when pre hooks were successful' do before do service = Gitlab::Git::HooksService.new expect(Gitlab::Git::HooksService).to receive(:new).and_return(service) expect(service).to receive(:execute) - .with(committer, repository, old_rev, new_rev, 'refs/heads/feature') + .with(committer, target_repository, old_rev, new_rev, updating_ref) .and_yield(service).and_return(true) end @@ -960,6 +963,37 @@ describe Repository, models: true do expect(repository.find_branch('feature').dereferenced_target.id).to eq(new_rev) end end + + context 'when target project does not have the commit' do + let(:target_project) { create(:project, :empty_repo) } + let(:old_rev) { Gitlab::Git::BLANK_SHA } + let(:new_rev) { project.commit('feature').sha } + let(:updating_ref) { 'refs/heads/master' } + + it 'fetch_ref and create the branch' do + expect(target_project.repository).to receive(:fetch_ref) + .and_call_original + + GitOperationService.new(committer, target_repository) + .with_branch( + 'master', + start_project: project, + start_branch_name: 'feature') { new_rev } + + expect(target_repository.branch_names).to contain_exactly('master') + end + end + + context 'when target project already has the commit' do + let(:target_project) { create(:project, :repository) } + + it 'does not fetch_ref and just pass the commit' do + expect(target_repository).not_to receive(:fetch_ref) + + GitOperationService.new(committer, target_repository) + .with_branch('feature', start_project: project) { new_rev } + end + end end context 'when temporary ref failed to be created from other project' do -- cgit v1.2.1 From 67c042e4a5603a39494c3c7e407161348d7e85f3 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 29 Aug 2017 16:49:43 +0200 Subject: Respect the default visibility level when creating a group --- lib/api/groups.rb | 4 +++- spec/requests/api/groups_spec.rb | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/api/groups.rb b/lib/api/groups.rb index e56427304a6..15266e9d4e5 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -7,7 +7,9 @@ module API helpers do params :optional_params_ce do optional :description, type: String, desc: 'The description of the group' - optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the group' + optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, + default: Gitlab::VisibilityLevel.string_level(Gitlab::CurrentSettings.current_application_settings.default_group_visibility), + desc: 'The visibility of the group' optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group' optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group' diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index a7557c7fb22..c6bc0c25e99 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -444,6 +444,7 @@ describe API::Groups do expect(json_response["name"]).to eq(group[:name]) expect(json_response["path"]).to eq(group[:path]) expect(json_response["request_access_enabled"]).to eq(group[:request_access_enabled]) + expect(json_response["visibility"]).to eq(Gitlab::VisibilityLevel.string_level(Gitlab::CurrentSettings.current_application_settings.default_group_visibility)) end it "creates a nested group", :nested_groups do -- cgit v1.2.1 From 1488d1f77c83969f42269c9cb0b7c285b4232899 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 29 Aug 2017 17:15:04 +0200 Subject: Add changelog --- .../37198-api-doesn-t-respect-default-group-visibility.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/37198-api-doesn-t-respect-default-group-visibility.yml diff --git a/changelogs/unreleased/37198-api-doesn-t-respect-default-group-visibility.yml b/changelogs/unreleased/37198-api-doesn-t-respect-default-group-visibility.yml new file mode 100644 index 00000000000..ef83dc1d10a --- /dev/null +++ b/changelogs/unreleased/37198-api-doesn-t-respect-default-group-visibility.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Respect default group visibility when creating a group' +merge_request: 13903 +author: Robert Schilling +type: fixed -- cgit v1.2.1 From f41464f878726de7490ea2457060ff0b93dcd138 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 29 Aug 2017 18:19:59 +0100 Subject: Fixes the margin of the top buttons of the pipeline page --- app/assets/javascripts/pipelines/components/pipelines.vue | 4 +++- app/assets/stylesheets/pages/pipelines.scss | 4 ++++ changelogs/unreleased/34990-top-buttons-misaligned.yml | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/34990-top-buttons-misaligned.yml diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue index 5df317a76bf..010063a0240 100644 --- a/app/assets/javascripts/pipelines/components/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -139,7 +139,9 @@ };