Commit b86b8bc0 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-03-15

# Conflicts:
#	Gemfile.lock
#	app/models/ci/build.rb
#	lib/gitlab/slash_commands/command.rb

[ci skip]
parents 603ac55e 770ba383
...@@ -35,15 +35,22 @@ ...@@ -35,15 +35,22 @@
"import/no-commonjs": "error", "import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }], "no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error", "promise/catch-or-return": "error",
"no-underscore-dangle": ["error", { "allow": ["__", "_links"]}], "no-underscore-dangle": ["error", { "allow": ["__", "_links"] }],
"vue/html-self-closing": ["error", { "no-mixed-operators": 0,
"html": { "space-before-function-paren": 0,
"void": "always", "curly": 0,
"normal": "never", "arrow-parens": 0,
"component": "always" "vue/html-self-closing": [
}, "error",
"svg": "always", {
"math": "always" "html": {
}] "void": "always",
"normal": "never",
"component": "always"
},
"svg": "always",
"math": "always"
}
]
} }
} }
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
eslint-report.html eslint-report.html
/.gitlab_shell_secret /.gitlab_shell_secret
.idea .idea
/.vscode/*
/.rbenv-version /.rbenv-version
.rbx/ .rbx/
/.ruby-gemset /.ruby-gemset
......
{
"singleQuote": true,
"trailingComma": "all"
}
...@@ -34,7 +34,7 @@ gem 'omniauth-gitlab', '~> 1.0.2' ...@@ -34,7 +34,7 @@ gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.5.2' gem 'omniauth-google-oauth2', '~> 0.5.2'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2' gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.7.0' gem 'omniauth-saml', '~> 1.10.0'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
...@@ -162,7 +162,7 @@ end ...@@ -162,7 +162,7 @@ end
gem 'state_machines-activerecord', '~> 0.4.0' gem 'state_machines-activerecord', '~> 0.4.0'
# Issue tags # Issue tags
gem 'acts-as-taggable-on', '~> 4.0' gem 'acts-as-taggable-on', '~> 5.0'
# Background jobs # Background jobs
gem 'sidekiq', '~> 5.0' gem 'sidekiq', '~> 5.0'
...@@ -174,7 +174,7 @@ gem 'sidekiq-limit_fetch', '~> 3.4', require: false ...@@ -174,7 +174,7 @@ gem 'sidekiq-limit_fetch', '~> 3.4', require: false
gem 'rufus-scheduler', '~> 3.4' gem 'rufus-scheduler', '~> 3.4'
# HTTP requests # HTTP requests
gem 'httparty', '~> 0.13.3' gem 'httparty', '~> 0.15.6'
# Colored output to console # Colored output to console
gem 'rainbow', '~> 2.2' gem 'rainbow', '~> 2.2'
...@@ -275,7 +275,7 @@ gem 'base32', '~> 0.3.0' ...@@ -275,7 +275,7 @@ gem 'base32', '~> 0.3.0'
gem "gitlab-license", "~> 1.0" gem "gitlab-license", "~> 1.0"
# Sentry integration # Sentry integration
gem 'sentry-raven', '~> 2.5.3' gem 'sentry-raven', '~> 2.7'
gem 'premailer-rails', '~> 1.9.7' gem 'premailer-rails', '~> 1.9.7'
...@@ -392,14 +392,14 @@ group :test do ...@@ -392,14 +392,14 @@ group :test do
gem 'test-prof', '~> 0.2.5' gem 'test-prof', '~> 0.2.5'
end end
gem 'octokit', '~> 4.6.2' gem 'octokit', '~> 4.8'
gem 'mail_room', '~> 0.9.1' gem 'mail_room', '~> 0.9.1'
gem 'email_reply_trimmer', '~> 0.1' gem 'email_reply_trimmer', '~> 0.1'
gem 'html2text' gem 'html2text'
gem 'ruby-prof', '~> 0.16.2' gem 'ruby-prof', '~> 0.17.0'
# OAuth # OAuth
gem 'oauth2', '~> 1.4' gem 'oauth2', '~> 1.4'
...@@ -415,7 +415,7 @@ gem 'sys-filesystem', '~> 1.1.6' ...@@ -415,7 +415,7 @@ gem 'sys-filesystem', '~> 1.1.6'
gem 'net-ntp' gem 'net-ntp'
# SSH host key support # SSH host key support
gem 'net-ssh', '~> 4.1.0' gem 'net-ssh', '~> 4.2.0'
gem 'sshkey', '~> 1.9.0' gem 'sshkey', '~> 1.9.0'
# Required for ED25519 SSH host key support # Required for ED25519 SSH host key support
......
...@@ -40,8 +40,8 @@ GEM ...@@ -40,8 +40,8 @@ GEM
minitest (~> 5.1) minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4) thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1) tzinfo (~> 1.1)
acts-as-taggable-on (4.0.0) acts-as-taggable-on (5.0.0)
activerecord (>= 4.0) activerecord (>= 4.2.8)
adamantium (0.2.0) adamantium (0.2.0)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
memoizable (~> 0.4.0) memoizable (~> 0.4.0)
...@@ -416,7 +416,7 @@ GEM ...@@ -416,7 +416,7 @@ GEM
thor thor
tilt tilt
hashdiff (0.3.4) hashdiff (0.3.4)
hashie (3.5.6) hashie (3.5.7)
hashie-forbidden_attributes (0.1.1) hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0) hashie (>= 3.0)
health_check (2.6.0) health_check (2.6.0)
...@@ -439,11 +439,15 @@ GEM ...@@ -439,11 +439,15 @@ GEM
domain_name (~> 0.5) domain_name (~> 0.5)
http-form_data (1.0.1) http-form_data (1.0.1)
http_parser.rb (0.6.0) http_parser.rb (0.6.0)
httparty (0.13.7) httparty (0.15.7)
json (~> 1.8)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
<<<<<<< HEAD
httpclient (2.8.3) httpclient (2.8.3)
i18n (0.9.1) i18n (0.9.1)
=======
httpclient (2.8.2)
i18n (0.9.5)
>>>>>>> upstream/master
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
ice_nine (0.11.2) ice_nine (0.11.2)
influxdb (0.2.3) influxdb (0.2.3)
...@@ -538,8 +542,12 @@ GEM ...@@ -538,8 +542,12 @@ GEM
mustermann (~> 1.0.0) mustermann (~> 1.0.0)
mysql2 (0.4.10) mysql2 (0.4.10)
net-ldap (0.16.0) net-ldap (0.16.0)
<<<<<<< HEAD
net-ntp (2.1.3) net-ntp (2.1.3)
net-ssh (4.1.0) net-ssh (4.1.0)
=======
net-ssh (4.2.0)
>>>>>>> upstream/master
netrc (0.11.0) netrc (0.11.0)
nokogiri (1.8.2) nokogiri (1.8.2)
mini_portile2 (~> 2.3.0) mini_portile2 (~> 2.3.0)
...@@ -551,12 +559,12 @@ GEM ...@@ -551,12 +559,12 @@ GEM
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (>= 1.2, < 3) rack (>= 1.2, < 3)
octokit (4.6.2) octokit (4.8.0)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.5) oj (2.17.5)
omniauth (1.4.2) omniauth (1.4.3)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (>= 1.0, < 3) rack (>= 1.6.2, < 3)
omniauth-auth0 (1.4.1) omniauth-auth0 (1.4.1)
omniauth-oauth2 (~> 1.1) omniauth-oauth2 (~> 1.1)
omniauth-authentiq (0.3.1) omniauth-authentiq (0.3.1)
...@@ -595,9 +603,9 @@ GEM ...@@ -595,9 +603,9 @@ GEM
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-oauth2-generic (0.2.2) omniauth-oauth2-generic (0.2.2)
omniauth-oauth2 (~> 1.0) omniauth-oauth2 (~> 1.0)
omniauth-saml (1.7.0) omniauth-saml (1.10.0)
omniauth (~> 1.3) omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.4) ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1) omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0) omniauth (>= 1.0.0)
omniauth-twitter (1.2.1) omniauth-twitter (1.2.1)
...@@ -676,7 +684,7 @@ GEM ...@@ -676,7 +684,7 @@ GEM
pry (>= 0.9.10) pry (>= 0.9.10)
public_suffix (3.0.2) public_suffix (3.0.2)
pyu-ruby-sasl (0.0.3.3) pyu-ruby-sasl (0.0.3.3)
rack (1.6.8) rack (1.6.9)
rack-accept (0.4.5) rack-accept (0.4.5)
rack (>= 0.4) rack (>= 0.4)
rack-attack (4.4.1) rack-attack (4.4.1)
...@@ -829,9 +837,9 @@ GEM ...@@ -829,9 +837,9 @@ GEM
i18n i18n
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-prof (0.16.2) ruby-prof (0.17.0)
ruby-progressbar (1.9.0) ruby-progressbar (1.9.0)
ruby-saml (1.4.1) ruby-saml (1.7.2)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
ruby_parser (3.9.0) ruby_parser (3.9.0)
sexp_processor (~> 4.1) sexp_processor (~> 4.1)
...@@ -870,7 +878,7 @@ GEM ...@@ -870,7 +878,7 @@ GEM
selenium-webdriver (3.5.0) selenium-webdriver (3.5.0)
childprocess (~> 0.5) childprocess (~> 0.5)
rubyzip (~> 1.0) rubyzip (~> 1.0)
sentry-raven (2.5.3) sentry-raven (2.7.2)
faraday (>= 0.7.6, < 1.0) faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.9.0) sexp_processor (4.9.0)
...@@ -959,7 +967,7 @@ GEM ...@@ -959,7 +967,7 @@ GEM
truncato (0.7.10) truncato (0.7.10)
htmlentities (~> 4.3.1) htmlentities (~> 4.3.1)
nokogiri (~> 1.8.0, >= 1.7.0) nokogiri (~> 1.8.0, >= 1.7.0)
tzinfo (1.2.4) tzinfo (1.2.5)
thread_safe (~> 0.1) thread_safe (~> 0.1)
u2f (0.2.1) u2f (0.2.1)
uber (0.1.0) uber (0.1.0)
...@@ -1020,7 +1028,7 @@ DEPENDENCIES ...@@ -1020,7 +1028,7 @@ DEPENDENCIES
RedCloth (~> 4.3.2) RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0) ace-rails-ap (~> 4.1.0)
activerecord_sane_schema_dumper (= 0.2) activerecord_sane_schema_dumper (= 0.2)
acts-as-taggable-on (~> 4.0) acts-as-taggable-on (~> 5.0)
addressable (~> 2.5.2) addressable (~> 2.5.2)
akismet (~> 2.0) akismet (~> 2.0)
allocations (~> 1.0) allocations (~> 1.0)
...@@ -1119,7 +1127,7 @@ DEPENDENCIES ...@@ -1119,7 +1127,7 @@ DEPENDENCIES
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
html2text html2text
httparty (~> 0.13.3) httparty (~> 0.15.6)
influxdb (~> 0.2) influxdb (~> 0.2)
jira-ruby (~> 1.4) jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2) jquery-atwho-rails (~> 1.3.2)
...@@ -1139,11 +1147,15 @@ DEPENDENCIES ...@@ -1139,11 +1147,15 @@ DEPENDENCIES
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10) mysql2 (~> 0.4.10)
net-ldap net-ldap
<<<<<<< HEAD
net-ntp net-ntp
net-ssh (~> 4.1.0) net-ssh (~> 4.1.0)
=======
net-ssh (~> 4.2.0)
>>>>>>> upstream/master
nokogiri (~> 1.8.2) nokogiri (~> 1.8.2)
oauth2 (~> 1.4) oauth2 (~> 1.4)
octokit (~> 4.6.2) octokit (~> 4.8)
oj (~> 2.17.4) oj (~> 2.17.4)
omniauth (~> 1.4.2) omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 1.4.1)
...@@ -1156,7 +1168,7 @@ DEPENDENCIES ...@@ -1156,7 +1168,7 @@ DEPENDENCIES
omniauth-google-oauth2 (~> 0.5.2) omniauth-google-oauth2 (~> 0.5.2)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2) omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.7.0) omniauth-saml (~> 1.10.0)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
...@@ -1206,7 +1218,7 @@ DEPENDENCIES ...@@ -1206,7 +1218,7 @@ DEPENDENCIES
rubocop (~> 0.52.1) rubocop (~> 0.52.1)
rubocop-rspec (~> 1.22.1) rubocop-rspec (~> 1.22.1)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2) ruby-prof (~> 0.17.0)
ruby_parser (~> 3.8) ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4) rufus-scheduler (~> 3.4)
rugged (~> 0.26.0) rugged (~> 0.26.0)
...@@ -1216,7 +1228,7 @@ DEPENDENCIES ...@@ -1216,7 +1228,7 @@ DEPENDENCIES
seed-fu (~> 2.3.7) seed-fu (~> 2.3.7)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5) selenium-webdriver (~> 3.5)
sentry-raven (~> 2.5.3) sentry-raven (~> 2.7)
settingslogic (~> 2.0.9) settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6) sham_rack (~> 1.3.6)
shoulda-matchers (~> 3.1.2) shoulda-matchers (~> 3.1.2)
......
import Vue from 'vue';
import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import BlobViewer from '~/blob/viewer/index'; import BlobViewer from '~/blob/viewer/index';
import initBlob from '~/pages/projects/init_blob'; import initBlob from '~/pages/projects/init_blob';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new BlobViewer(); // eslint-disable-line no-new new BlobViewer(); // eslint-disable-line no-new
initBlob(); initBlob();
const CommitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status');
const statusLink = document.querySelector('.commit-actions .ci-status-link');
if (statusLink) {
statusLink.remove();
// eslint-disable-next-line no-new
new Vue({
el: CommitPipelineStatusEl,
components: {
commitPipelineStatus,
},
render(createElement) {
return createElement('commit-pipeline-status', {
props: {
endpoint: CommitPipelineStatusEl.dataset.endpoint,
},
});
},
});
}
}); });
...@@ -576,9 +576,15 @@ module Ci ...@@ -576,9 +576,15 @@ module Ci
def persisted_environment_variables def persisted_environment_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
return variables unless persisted_environment return variables unless persisted_environment
<<<<<<< HEAD
variables.concat(persisted_environment.predefined_variables) variables.concat(persisted_environment.predefined_variables)
=======
variables.concat(persisted_environment.predefined_variables)
>>>>>>> upstream/master
# Here we're passing unexpanded environment_url for runner to expand, # Here we're passing unexpanded environment_url for runner to expand,
# and we need to make sure that CI_ENVIRONMENT_NAME and # and we need to make sure that CI_ENVIRONMENT_NAME and
# CI_ENVIRONMENT_SLUG so on are available for the URL be expanded. # CI_ENVIRONMENT_SLUG so on are available for the URL be expanded.
......
...@@ -7,29 +7,24 @@ module Storage ...@@ -7,29 +7,24 @@ module Storage
raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry') raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry')
end end
expires_full_path_cache parent_was = if parent_changed? && parent_id_was.present?
Namespace.find(parent_id_was) # raise NotFound early if needed
# Move the namespace directory in all storage paths used by member projects end
repository_storage_paths.each do |repository_storage_path|
# Ensure old directory exists before moving it
gitlab_shell.add_namespace(repository_storage_path, full_path_was)
# Ensure new directory exists before moving it (if there's a parent)
gitlab_shell.add_namespace(repository_storage_path, parent.full_path) if parent
unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path) expires_full_path_cache
Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}" move_repositories
# if we cannot move namespace directory we should rollback if parent_changed?
# db changes in order to prevent out of sync between db and fs former_parent_full_path = parent_was&.full_path
raise Gitlab::UpdatePathError.new('namespace directory cannot be moved') parent_full_path = parent&.full_path
end Gitlab::UploadsTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
Gitlab::PagesTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
else
Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
end end
Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
remove_exports! remove_exports!
# If repositories moved successfully we need to # If repositories moved successfully we need to
...@@ -57,6 +52,26 @@ module Storage ...@@ -57,6 +52,26 @@ module Storage
private private
def move_repositories
# Move the namespace directory in all storage paths used by member projects
repository_storage_paths.each do |repository_storage_path|
# Ensure old directory exists before moving it
gitlab_shell.add_namespace(repository_storage_path, full_path_was)
# Ensure new directory exists before moving it (if there's a parent)
gitlab_shell.add_namespace(repository_storage_path, parent.full_path) if parent
unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path)
Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}"
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
end
end
end
def old_repository_storage_paths def old_repository_storage_paths
@old_repository_storage_paths ||= repository_storage_paths @old_repository_storage_paths ||= repository_storage_paths
end end
......
...@@ -605,9 +605,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -605,9 +605,10 @@ class MergeRequest < ActiveRecord::Base
return unless open? return unless open?
old_diff_refs = self.diff_refs old_diff_refs = self.diff_refs
new_diff = create_merge_request_diff
MergeRequests::MergeRequestDiffCacheService.new.execute(self, new_diff)
create_merge_request_diff
MergeRequests::MergeRequestDiffCacheService.new.execute(self)
new_diff_refs = self.diff_refs new_diff_refs = self.diff_refs
update_diff_discussion_positions( update_diff_discussion_positions(
......
module MergeRequests module MergeRequests
class MergeRequestDiffCacheService class MergeRequestDiffCacheService
def execute(merge_request) def execute(merge_request, new_diff)
# Executing the iteration we cache all the highlighted diff information # Executing the iteration we cache all the highlighted diff information
merge_request.diffs.diff_files.to_a merge_request.diffs.diff_files.to_a
# Remove cache for all diffs on this MR. Do not use the association on the
# model, as that will interfere with other actions happening when
# reloading the diff.
MergeRequestDiff.where(merge_request: merge_request).each do |merge_request_diff|
next if merge_request_diff == new_diff
merge_request_diff.diffs.clear_cache!
end
end end
end end
end end
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
%span.runner-state.runner-state-specific %span.runner-state.runner-state-specific
Specific Specific
- add_to_breadcrumbs _("Runners"), admin_runners_path
- breadcrumb_title "##{@runner.id}"
- @no_container = true
- if @runner.shared? - if @runner.shared?
.bs-callout.bs-callout-success .bs-callout.bs-callout-success
%h4 This Runner will process jobs from ALL UNASSIGNED projects %h4 This Runner will process jobs from ALL UNASSIGNED projects
......
---
title: Set breadcrumb for admin/runners/show
merge_request: 17431
author: Takuya Noguchi
type: fixed
---
title: Update ruby-saml to 1.7.2 and omniauth-saml to 1.10.0
merge_request: 17734
author: Takuya Noguchi
type: security
---
title: Stop caching highlighted diffs in Redis unnecessarily
merge_request: 17746
author:
type: performance
---
title: Add slash command for moving issues
merge_request:
author: Adam Pahlevi
type: added
---
title: Fix importing multiple assignees from GitLab export
merge_request: 17718
author:
type: fixed
---
title: Fix relative uri when "#" is in branch name
merge_request:
author: Jan
type: fixed
---
title: Add realtime pipeline status for adding/viewing files
merge_request: 17705
author:
type: other
---
title: Fix missing uploads after group transfer
merge_request: 17658
author:
type: fixed
# Creates a project with labeled issues for an user.
# Run this single seed file using: rake db:seed_fu FILTER=labeled USER_ID=74.
# If an USER_ID is not provided it will use the last created user.
require './spec/support/sidekiq'
class Gitlab::Seeder::LabeledIssues
include ::Gitlab::Utils
def initialize(user)
@user = user
end
def seed!
Sidekiq::Testing.inline! do
group = create_group
create_projects(group)
create_labels(group)
create_issues(group)
end
print '.'
end
private
def create_group
group_name = "group_of_#{@user.username}_#{SecureRandom.hex(4)}"
group_params = {
name: group_name,
path: group_name,
description: FFaker::Lorem.sentence
}
Groups::CreateService.new(@user, group_params).execute
end
def create_projects(group)
5.times do
project_name = "project_#{SecureRandom.hex(6)}"
params = {
namespace_id: group.id,
name: project_name,
description: FFaker::Lorem.sentence,
visibility_level: Gitlab::VisibilityLevel.values.sample
}
Projects::CreateService.new(@user, params).execute
end
end
def create_labels(group)
group.projects.each do |project|
5.times do
label_title = FFaker::Vehicle.model
Labels::CreateService.new(title: label_title, color: "#69D100").execute(project: project)
end
end
10.times do
label_title = FFaker::Product.brand
Labels::CreateService.new(title: label_title).execute(group: group)
end
end
def create_issues(group)
# Get only group labels
group_labels =
LabelsFinder.new(@user, group_id: group.id).execute.where.not(group_id: nil)
group.projects.each do |project|
label_ids = project.labels.pluck(:id).sample(5)
label_ids.push(*group.labels.sample(4))
20.times do
issue_params = {
title: FFaker::Lorem.sentence(6),
description: FFaker::Lorem.sentence,
state: 'opened',
label_ids: label_ids
}
Issues::CreateService.new(project, @user, issue_params).execute if project.project_feature.present?
end
end
end
end
Gitlab::Seeder.quiet do
user_id = ENV['USER_ID']
user =
if user_id.present?
User.find(user_id)
else
User.last
end
Gitlab::Seeder::LabeledIssues.new(user).seed!
end
...@@ -3,13 +3,15 @@ class CreateUserInteractedProjectsTable < ActiveRecord::Migration ...@@ -3,13 +3,15 @@ class CreateUserInteractedProjectsTable < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
disable_ddl_transaction! INDEX_NAME = 'user_interacted_projects_non_unique_index'
def up def up
create_table :user_interacted_projects, id: false do |t| create_table :user_interacted_projects, id: false do |t|
t.references :user, null: false t.references :user, null: false
t.references :project, null: false t.references :project, null: false
end end
add_index :user_interacted_projects, [:project_id, :user_id], name: INDEX_NAME
end end
def down def down
......
require_relative '../migrate/20180223120443_create_user_interacted_projects_table.rb'
# rubocop:disable AddIndex
# rubocop:disable AddConcurrentForeignKey
class BuildUserInteractedProjectsTable < ActiveRecord::Migration class BuildUserInteractedProjectsTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime. # Set this constant to true if this migration requires downtime.
DOWNTIME = false DOWNTIME = false
UNIQUE_INDEX_NAME = 'index_user_interacted_projects_on_project_id_and_user_id'
disable_ddl_transaction! disable_ddl_transaction!
def up def up
...@@ -13,16 +18,8 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration ...@@ -13,16 +18,8 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
MysqlStrategy.new MysqlStrategy.new
end.up end.up
unless index_exists?(:user_interacted_projects, [:project_id, :user_id]) if index_exists_by_name?(:user_interacted_projects, CreateUserInteractedProjectsTable::INDEX_NAME)
add_concurrent_index :user_interacted_projects, [:project_id, :user_id], unique: true remove_concurrent_index_by_name :user_interacted_projects, CreateUserInteractedProjectsTable::INDEX_NAME
end
unless foreign_key_exists?(:user_interacted_projects, :user_id)
add_concurrent_foreign_key :user_interacted_projects, :users, column: :user_id, on_delete: :cascade
end
unless foreign_key_exists?(:user_interacted_projects, :project_id)
add_concurrent_foreign_key :user_interacted_projects, :projects, column: :project_id, on_delete: :cascade
end end
end end
...@@ -37,31 +34,16 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration ...@@ -37,31 +34,16 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
remove_foreign_key :user_interacted_projects, :projects remove_foreign_key :user_interacted_projects, :projects
end end
if index_exists_by_name?(:user_interacted_projects, 'index_user_interacted_projects_on_project_id_and_user_id') if index_exists_by_name?(:user_interacted_projects, UNIQUE_INDEX_NAME)
remove_concurrent_index_by_name :user_interacted_projects, 'index_user_interacted_projects_on_project_id_and_user_id' remove_concurrent_index_by_name :user_interacted_projects, UNIQUE_INDEX_NAME
end end
end
private unless index_exists_by_name?(:user_interacted_projects, CreateUserInteractedProjectsTable::INDEX_NAME)
add_concurrent_index :user_interacted_projects, [:project_id, :user_id], name: CreateUserInteractedProjectsTable::INDEX_NAME
# Rails' index_exists? doesn't work when you only give it a table and index
# name. As such we have to use some extra code to check if an index exists for
# a given name.
def index_exists_by_name?(table, index)
indexes_for_table[table].include?(index)
end
def indexes_for_table
@indexes_for_table ||= Hash.new do |hash, table_name|
hash[table_name] = indexes(table_name).map(&:name)
end end
end end
def foreign_key_exists?(table, column) private
foreign_keys(table).any? do |key|
key.options[:column] == column.to_s
end
end
class PostgresStrategy < ActiveRecord::Migration class PostgresStrategy < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
...@@ -71,33 +53,86 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration ...@@ -71,33 +53,86 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
def up def up
with_index(:events, [:author_id, :project_id], name: 'events_user_interactions_temp', where: 'project_id IS NOT NULL') do with_index(:events, [:author_id, :project_id], name: 'events_user_interactions_temp', where: 'project_id IS NOT NULL') do
iteration = 0 insert_missing_records
records = 0
begin # Do this once without lock to speed up the second invocation
Rails.logger.info "Building user_interacted_projects table, batch ##{iteration}" remove_duplicates
result = execute <<~SQL with_table_lock(:user_interacted_projects) do
remove_duplicates
create_unique_index
end
remove_without_project
with_table_lock(:user_interacted_projects, :projects) do
remove_without_project
create_fk :user_interacted_projects, :projects, :project_id
end
remove_without_user
with_table_lock(:user_interacted_projects, :users) do
remove_without_user
create_fk :user_interacted_projects, :users, :user_id
end
end
execute "ANALYZE user_interacted_projects"
end
private
def insert_missing_records
iteration = 0
records = 0
begin
Rails.logger.info "Building user_interacted_projects table, batch ##{iteration}"
result = execute <<~SQL
INSERT INTO user_interacted_projects (user_id, project_id) INSERT INTO user_interacted_projects (user_id, project_id)
SELECT e.user_id, e.project_id SELECT e.user_id, e.project_id
FROM (SELECT DISTINCT author_id AS user_id, project_id FROM events WHERE project_id IS NOT NULL) AS e FROM (SELECT DISTINCT author_id AS user_id, project_id FROM events WHERE project_id IS NOT NULL) AS e
LEFT JOIN user_interacted_projects ucp USING (user_id, project_id) LEFT JOIN user_interacted_projects ucp USING (user_id, project_id)
WHERE ucp.user_id IS NULL WHERE ucp.user_id IS NULL
LIMIT #{BATCH_SIZE} LIMIT #{BATCH_SIZE}
SQL SQL
iteration += 1 iteration += 1
records += result.cmd_tuples records += result.cmd_tuples
Rails.logger.info "Building user_interacted_projects table, batch ##{iteration} complete, created #{records} overall" Rails.logger.info "Building user_interacted_projects table, batch ##{iteration} complete, created #{records} overall"
Kernel.sleep(SLEEP_TIME) if result.cmd_tuples > 0 Kernel.sleep(SLEEP_TIME) if result.cmd_tuples > 0
rescue ActiveRecord::InvalidForeignKey => e end while result.cmd_tuples > 0
Rails.logger.info "Retry on InvalidForeignKey: #{e}" end
retry
end while result.cmd_tuples > 0
end
execute "ANALYZE user_interacted_projects" def remove_duplicates
execute <<~SQL
WITH numbered AS (select ctid, ROW_NUMBER() OVER (PARTITION BY (user_id, project_id)) as row_number, user_id, project_id from user_interacted_projects)
DELETE FROM user_interacted_projects WHERE ctid IN (SELECT ctid FROM numbered WHERE row_number > 1);
SQL
end
def remove_without_project
execute "DELETE FROM user_interacted_projects WHERE NOT EXISTS (SELECT 1 FROM projects WHERE id = user_interacted_projects.project_id)"
end end
private def remove_without_user
execute "DELETE FROM user_interacted_projects WHERE NOT EXISTS (SELECT 1 FROM users WHERE id = user_interacted_projects.user_id)"
end
def create_fk(table, target, column)
return if foreign_key_exists?(table, column)
add_foreign_key table, target, column: column, on_delete: :cascade
end
def create_unique_index
return if index_exists_by_name?(:user_interacted_projects, UNIQUE_INDEX_NAME)
add_index :user_interacted_projects, [:project_id, :user_id], unique: true, name: UNIQUE_INDEX_NAME
end
# Protect table against concurrent data changes while still allowing reads
def with_table_lock(*tables)
ActiveRecord::Base.connection.transaction do
execute "LOCK TABLE #{tables.join(", ")} IN SHARE MODE"
yield
end
end
def with_index(*args) def with_index(*args)
add_concurrent_index(*args) unless index_exists?(*args) add_concurrent_index(*args) unless index_exists?(*args)
...@@ -118,7 +153,18 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration ...@@ -118,7 +153,18 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
LEFT JOIN user_interacted_projects ucp USING (user_id, project_id) LEFT JOIN user_interacted_projects ucp USING (user_id, project_id)
WHERE ucp.user_id IS NULL WHERE ucp.user_id IS NULL
SQL SQL
unless index_exists?(:user_interacted_projects, [:project_id, :user_id])
add_concurrent_index :user_interacted_projects, [:project_id, :user_id], unique: true, name: UNIQUE_INDEX_NAME
end
unless foreign_key_exists?(:user_interacted_projects, :user_id)
add_concurrent_foreign_key :user_interacted_projects, :users, column: :user_id, on_delete: :cascade
end
unless foreign_key_exists?(:user_interacted_projects, :project_id)
add_concurrent_foreign_key :user_interacted_projects, :projects, column: :project_id, on_delete: :cascade
end
end end
end end
end end
...@@ -966,7 +966,7 @@ tag including only the files that are untracked by Git: ...@@ -966,7 +966,7 @@ tag including only the files that are untracked by Git:
```yaml ```yaml
job: job:
artifacts: artifacts:
name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
untracked: true untracked: true
``` ```
...@@ -975,7 +975,7 @@ To create an archive with a name of the current [stage](#stages) and branch name ...@@ -975,7 +975,7 @@ To create an archive with a name of the current [stage](#stages) and branch name
```yaml ```yaml
job: job:
artifacts: artifacts:
name: "${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}" name: "$CI_JOB_STAGE-$CI_COMMIT_REF_NAME"
untracked: true untracked: true
``` ```
...@@ -987,7 +987,7 @@ If you use **Windows Batch** to run your shell scripts you need to replace ...@@ -987,7 +987,7 @@ If you use **Windows Batch** to run your shell scripts you need to replace
```yaml ```yaml
job: job:
artifacts: artifacts:
name: "%CI_JOB_STAGE%_%CI_COMMIT_REF_NAME%" name: "%CI_JOB_STAGE%-%CI_COMMIT_REF_NAME%"
untracked: true untracked: true
``` ```
...@@ -997,7 +997,7 @@ If you use **Windows PowerShell** to run your shell scripts you need to replace ...@@ -997,7 +997,7 @@ If you use **Windows PowerShell** to run your shell scripts you need to replace
```yaml ```yaml
job: job:
artifacts: artifacts:
name: "$env:CI_JOB_STAGE_$env:CI_COMMIT_REF_NAME" name: "$env:CI_JOB_STAGE-$env:CI_COMMIT_REF_NAME"
untracked: true untracked: true
``` ```
......
...@@ -17,10 +17,13 @@ are very appreciative of the work done by translators and proofreaders! ...@@ -17,10 +17,13 @@ are very appreciative of the work done by translators and proofreaders!
- French - French
- Rémy Coutable - [GitLab](https://gitlab.com/rymai), [Crowdin](https://crowdin.com/profile/rymai) - Rémy Coutable - [GitLab](https://gitlab.com/rymai), [Crowdin](https://crowdin.com/profile/rymai)
- German - German
- Indonesian
- Ahmad Naufal Mukhtar - [GitLab](https://gitlab.com/anaufalm), [Crowdin](https://crowdin.com/profile/anaufalm)
- Italian - Italian
- Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo) - Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo)
- Japanese - Japanese
- Korean - Korean
- Chang-Ho Cha - [GitLab](https://gitlab.com/changho-cha), [Crowdin](https://crowdin.com/profile/zzazang)
- Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve) - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
- Polish - Polish
- Filip Mech - [GitLab](https://gitlab.com/mehenz), [Crowdin](https://crowdin.com/profile/mehenz) - Filip Mech - [GitLab](https://gitlab.com/mehenz), [Crowdin](https://crowdin.com/profile/mehenz)
......
# Principles # Principles
> TODO: Add principles These principles will ensure that your frontend contribution starts off in the right direction.
## Discuss architecture before implementation
Discuss your architecture design in an issue before writing code. This helps decrease the review time and also provides good practice for writing and thinking about system design.
## Be consistent
There are multiple ways of writing code to accomplish the same results. We should be as consistent as possible in how we write code across our codebases. This will make it more easier us to maintain our code across GitLab.
## Enhance progressively
Whenever you see with existing code that does not follow our current style guide, update it proactively. Refrain from changing everything but each merge request should progressively enhance our codebase and reduce technical debt.
## When to use Vue
- Use Vue for feature that make use of heavy DOM manipulation
- Use Vue for reusable components
## When to use jQuery
- Use jQuery to interact with Bootstrap JavaScript components
- Avoid jQuery when a better alternative exists. We are slowly moving away from it [#43559][jquery-future]
## Mixing Vue and jQuery
- Mixing Vue and jQuery is not recommended.
- If you need to use a specific jQuery plugin in Vue, [create a wrapper around it][select2].
- It is acceptable for Vue to listen to existing jQuery events using jQuery event listeners.
- It is not recommended to add new jQuery events for Vue to interact with jQuery.
[jquery-future]: https://gitlab.com/gitlab-org/gitlab-ce/issues/43559
[select2]: https://vuejs.org/v2/examples/select2.html
# HTML style guide # HTML style guide
> TODO: Add content ## Buttons
<a name="button-type"></a><a name="1.1"></a>
- [1.1](#button-type) **Use button type** Button tags requires a `type` attribute according to the [W3C HTML specification][button-type-spec].
```
// bad
<button></button>
// good
<button type="button"></button>
```
<a name="button-role"></a><a name="1.2"></a>
- [1.2](#button-role) **Use button role for non buttons** If an HTML element has an onClick handler but is not a button, it should have `role="button"`. This is more [accessible][button-role-accessible].
```
// bad
<div onClick="doSomething"></div>
// good
<div role="button" onClick="doSomething"></div>
```
## Links
<a name="blank-links"></a><a name="2.1"></a>
- [2.1](#blank-links) **Use rel for target blank** Use `rel="noopener noreferrer"` whenever your links open in a new window i.e. `target="_blank"`. This prevents [the following][jitbit-target-blank] security vulnerability documented by JitBit
```
// bad
<a href="url" target="_blank"></a>
// good
<a href="url" target="_blank" rel="noopener noreferrer"></a>
```
<a name="fake-links"></a><a name="2.2"></a>
- [2.2](#fake-links) **Do not use fake links** Use a button tag if a link only invokes JavaScript click event handlers. This is more semantic.
```
// bad
<a class="js-do-something" href="#"></a>
// good
<button class="js-do-something" type="button"></button>
```
[button-type-spec]: https://www.w3.org/TR/2011/WD-html5-20110525/the-button-element.html#dom-button-type
[button-role-accessible]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role
[jitbit-target-blank]: https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/
...@@ -7,3 +7,9 @@ ...@@ -7,3 +7,9 @@
## [JavaScript style guide](javascript.md) ## [JavaScript style guide](javascript.md)
## [Vue style guide](vue.md) ## [Vue style guide](vue.md)
# Tooling
## [Prettier](prettier.md)
Our code is automatically formatted with [Prettier](https://prettier.io) to follow our guidelines.
# Formatting with Prettier
Our code is automatically formatted with [Prettier](https://prettier.io) to follow our style guides. Prettier is taking care of formatting .js, .vue, and .scss files based on the standard prettier rules. You can find all settings for Prettier in `.prettierrc`.
## Editor
The easiest way to include prettier in your workflow is by setting up your preferred editor (all major editors are supported) accordingly. We suggest setting up prettier to run automatically when each file is saved. Find [here](https://prettier.io/docs/en/editors.html) the best way to set it up in your preferred editor.
Please take care that you only let Prettier format the same file types as the global Yarn script does (.js, .vue, and .scss). In VSCode by example you can easily exclude file formats in your settings file:
```
"prettier.disableLanguages": [
"json",
"markdown"
],
```
## Yarn Script
The following yarn scripts are available to do global formatting:
```
yarn prettier-staged-save
```
Updates all currently staged files (based on `git diff`) with Prettier and saves the needed changes.
```
yarn prettier-staged
```
Checks all currently staged files (based on `git diff`) with Prettier and log which files would need manual updating to the console.
```
yarn prettier-all
```
Checks all files with Prettier and logs which files need manual updating to the console.
```
yarn prettier-all-save
```
Formats all files in the repository with Prettier. (This should only be used to test global rule updates otherwise you would end up with huge MR's).
The source of these Yarn scripts can be found in `/scripts/frontend/prettier.js`.
...@@ -15,9 +15,10 @@ Taking the trigger term as `project-name`, the commands are: ...@@ -15,9 +15,10 @@ Taking the trigger term as `project-name`, the commands are:
| `/project-name issue new <title> <shift+return> <description>` | Creates a new issue with title `<title>` and description `<description>` | | `/project-name issue new <title> <shift+return> <description>` | Creates a new issue with title `<title>` and description `<description>` |
| `/project-name issue show <id>` | Shows the issue with id `<id>` | | `/project-name issue show <id>` | Shows the issue with id `<id>` |
| `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` | | `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` |
| `/project-name issue move <id> to <project>` | Moves issue ID `<id>` to `<project>` |
| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment | | `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
Note that if you are using the [GitLab Slack application](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html) for Note that if you are using the [GitLab Slack application](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html) for
your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html#usage). your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html#usage).
## Issue commands ## Issue commands
......
...@@ -84,7 +84,7 @@ module Banzai ...@@ -84,7 +84,7 @@ module Banzai
relative_url_root, relative_url_root,
project.full_path, project.full_path,
uri_type(file_path), uri_type(file_path),
Addressable::URI.escape(ref), Addressable::URI.escape(ref).gsub('#', '%23'),
Addressable::URI.escape(file_path) Addressable::URI.escape(file_path)
].compact.join('/').squeeze('/').chomp('/') ].compact.join('/').squeeze('/').chomp('/')
......
...@@ -859,6 +859,19 @@ into similar problems in the future (e.g. when new tables are created). ...@@ -859,6 +859,19 @@ into similar problems in the future (e.g. when new tables are created).
BackgroundMigrationWorker.perform_in(delay_interval * index, job_class_name, [start_id, end_id]) BackgroundMigrationWorker.perform_in(delay_interval * index, job_class_name, [start_id, end_id])
end end
end end
def foreign_key_exists?(table, column)
foreign_keys(table).any? do |key|
key.options[:column] == column.to_s
end
end
# Rails' index_exists? doesn't work when you only give it a table and index
# name. As such we have to use some extra code to check if an index exists for
# a given name.
def index_exists_by_name?(table, index)
indexes(table).map(&:name).include?(index)
end
end end
end end
end end
...@@ -29,6 +29,14 @@ module Gitlab ...@@ -29,6 +29,14 @@ module Gitlab
@merge_request_diff.real_size @merge_request_diff.real_size
end end
def clear_cache!
Rails.cache.delete(cache_key)
end
def cache_key
[@merge_request_diff, 'highlighted-diff-files', diff_options]
end
private private
def highlight_diff_file_from_cache!(diff_file, cache_diff_lines) def highlight_diff_file_from_cache!(diff_file, cache_diff_lines)
...@@ -64,16 +72,12 @@ module Gitlab ...@@ -64,16 +72,12 @@ module Gitlab
end end
def store_highlight_cache def store_highlight_cache
Rails.cache.write(cache_key, highlight_cache) if @highlight_cache_was_empty Rails.cache.write(cache_key, highlight_cache, expires_in: 1.week) if @highlight_cache_was_empty
end end
def cacheable?(diff_file) def cacheable?(diff_file)
@merge_request_diff.present? && diff_file.text? && diff_file.diffable? @merge_request_diff.present? && diff_file.text? && diff_file.diffable?
end end
def cache_key
[@merge_request_diff, 'highlighted-diff-files', diff_options]
end
end end
end end
end end
......
...@@ -70,6 +70,7 @@ module Gitlab ...@@ -70,6 +70,7 @@ module Gitlab
update_user_references update_user_references
update_project_references update_project_references
remove_duplicate_assignees
reset_tokens! reset_tokens!
remove_encrypted_attributes! remove_encrypted_attributes!
...@@ -83,6 +84,14 @@ module Gitlab ...@@ -83,6 +84,14 @@ module Gitlab
end end
end end
def remove_duplicate_assignees
return unless @relation_hash['issue_assignees']
# When an assignee did not exist in the members mapper, the importer is
# assigned. We only need to assign each user once.
@relation_hash['issue_assignees'].uniq!(&:user_id)
end
def setup_note def setup_note
set_note_author set_note_author
# attachment is deprecated and note uploads are handled by Markdown uploader # attachment is deprecated and note uploads are handled by Markdown uploader
......
module Gitlab module Gitlab
# This class is used to move local, unhashed files owned by projects to their new location
class ProjectTransfer class ProjectTransfer
def move_project(project_path, namespace_path_was, namespace_path) # nil parent_path (or parent_path_was) represents a root namespace
new_namespace_folder = File.join(root_dir, namespace_path) def move_namespace(path, parent_path_was, parent_path)
FileUtils.mkdir_p(new_namespace_folder) unless Dir.exist?(new_namespace_folder) parent_path_was ||= ''
from = File.join(root_dir, namespace_path_was, project_path) parent_path ||= ''
to = File.join(root_dir, namespace_path, project_path) new_parent_folder = File.join(root_dir, parent_path)
FileUtils.mkdir_p(new_parent_folder)
from = File.join(root_dir, parent_path_was, path)
to = File.join(root_dir, parent_path, path)
move(from, to, "") move(from, to, "")
end end
alias_method :move_project, :move_namespace
def rename_project(path_was, path, namespace_path) def rename_project(path_was, path, namespace_path)
base_dir = File.join(root_dir, namespace_path) base_dir = File.join(root_dir, namespace_path)
move(path_was, path, base_dir) move(path_was, path, base_dir)
......
...@@ -5,8 +5,13 @@ module Gitlab ...@@ -5,8 +5,13 @@ module Gitlab
Gitlab::SlashCommands::IssueShow, Gitlab::SlashCommands::IssueShow,
Gitlab::SlashCommands::IssueNew, Gitlab::SlashCommands::IssueNew,
Gitlab::SlashCommands::IssueSearch, Gitlab::SlashCommands::IssueSearch,
<<<<<<< HEAD
Gitlab::SlashCommands::Deploy, Gitlab::SlashCommands::Deploy,
Gitlab::SlashCommands::Run Gitlab::SlashCommands::Run
=======
Gitlab::SlashCommands::IssueMove,
Gitlab::SlashCommands::Deploy
>>>>>>> upstream/master
].freeze ].freeze
def execute def execute
......
module Gitlab
module SlashCommands
class IssueMove < IssueCommand
def self.match(text)
%r{
\A # the beginning of a string
issue\s+move\s+ # the command
\#?(?<iid>\d+)\s+ # the issue id, may preceded by hash sign
(to\s+)? # aid the command to be much more human-ly
(?<project_path>[^\s]+) # named group for id of dest. project
}x.match(text)
end
def self.help_message
'issue move <issue_id> (to)? <project_path>'
end
def self.allowed?(project, user)
can?(user, :admin_issue, project)
end
def execute(match)
old_issue = find_by_iid(match[:iid])
target_project = Project.find_by_full_path(match[:project_path])
unless current_user.can?(:read_project, target_project) && old_issue
return Gitlab::SlashCommands::Presenters::Access.new.not_found
end
new_issue = Issues::MoveService.new(project, current_user)
.execute(old_issue, target_project)
presenter(new_issue).present(old_issue)
rescue Issues::MoveService::MoveError => e
presenter(old_issue).display_move_error(e.message)
end
private
def presenter(issue)
Gitlab::SlashCommands::Presenters::IssueMove.new(issue)
end
end
end
end
# coding: utf-8
module Gitlab
module SlashCommands
module Presenters
class IssueMove < Presenters::Base
include Presenters::IssueBase
def present(old_issue)
in_channel_response(moved_issue(old_issue))
end
def display_move_error(error)
message = header_with_list("The action was not successful, because:", [error])
ephemeral_response(text: message)
end
private
def moved_issue(old_issue)
{
attachments: [
{
title: "#{@resource.title} · #{@resource.to_reference}",
title_link: resource_url,
author_name: author.name,
author_icon: author.avatar_url,
fallback: "Issue #{@resource.to_reference}: #{@resource.title}",
pretext: pretext(old_issue),
color: color(@resource),
fields: fields,
mrkdwn_in: [
:title,
:pretext,
:text,
:fields
]
}
]
}
end
def pretext(old_issue)
"Moved issue *#{issue_link(old_issue)}* to *#{issue_link(@resource)}*"
end
def issue_link(issue)
"[#{issue.to_reference}](#{project_issue_url(issue.project, issue)})"
end
end
end
end
end
...@@ -8,6 +8,10 @@ ...@@ -8,6 +8,10 @@
"karma": "karma start config/karma.config.js --single-run", "karma": "karma start config/karma.config.js --single-run",
"karma-coverage": "BABEL_ENV=coverage karma start config/karma.config.js --single-run", "karma-coverage": "BABEL_ENV=coverage karma start config/karma.config.js --single-run",
"karma-start": "karma start config/karma.config.js", "karma-start": "karma start config/karma.config.js",
"prettier-staged": "node ./scripts/frontend/prettier.js",
"prettier-staged-save": "node ./scripts/frontend/prettier.js save",
"prettier-all": "node ./scripts/frontend/prettier.js check-all",
"prettier-all-save": "node ./scripts/frontend/prettier.js save-all",
"webpack": "webpack --config config/webpack.config.js", "webpack": "webpack --config config/webpack.config.js",
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js" "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
}, },
...@@ -116,7 +120,7 @@ ...@@ -116,7 +120,7 @@
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "2.0.7", "karma-webpack": "2.0.7",
"nodemon": "^1.15.1", "nodemon": "^1.15.1",
"prettier": "1.9.2", "prettier": "1.11.1",
"webpack-dev-server": "^2.11.2" "webpack-dev-server": "^2.11.2"
} }
} }
/* eslint import/no-commonjs: "off" */
const execFileSync = require('child_process').execFileSync;
const exec = (command, args) => {
const options = {
cwd: process.cwd(),
env: process.env,
encoding: 'utf-8',
};
return execFileSync(command, args, options);
};
const execGitCmd = args =>
exec('git', args)
.trim()
.toString()
.split('\n');
module.exports = {
getStagedFiles: fileExtensionFilter => {
const gitOptions = [
'diff',
'--name-only',
'--cached',
'--diff-filter=ACMRTUB',
];
if (fileExtensionFilter) gitOptions.push(...fileExtensionFilter);
return execGitCmd(gitOptions);
},
};
/* eslint import/no-commonjs: "off", import/no-extraneous-dependencies: "off", no-console: "off" */
const glob = require('glob');
const prettier = require('prettier');
const fs = require('fs');
const getStagedFiles = require('./frontend_script_utils').getStagedFiles;
const mode = process.argv[2] || 'check';
const shouldSave = mode === 'save' || mode === 'save-all';
const allFiles = mode === 'check-all' || mode === 'save-all';
const config = {
patterns: ['**/*.js', '**/*.vue', '**/*.scss'],
ignore: ['**/node_modules/**', '**/vendor/**', '**/public/**'],
parsers: {
js: 'babylon',
vue: 'vue',
scss: 'css',
},
};
const availableExtensions = Object.keys(config.parsers);
console.log(`Loading ${allFiles ? 'All' : 'Staged'} Files ...`);
const stagedFiles = allFiles
? null
: getStagedFiles(availableExtensions.map(ext => `*.${ext}`));
if (stagedFiles) {
if (!stagedFiles.length || (stagedFiles.length === 1 && !stagedFiles[0])) {
console.log('No matching staged files.');
return;
}
console.log(`Matching staged Files : ${stagedFiles.length}`);
}
let didWarn = false;
let didError = false;
let files;
if (allFiles) {
const ignore = config.ignore;
const patterns = config.patterns;
const globPattern =
patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`;
files = glob
.sync(globPattern, { ignore })
.filter(f => allFiles || stagedFiles.includes(f));
} else {
files = stagedFiles.filter(f =>
availableExtensions.includes(f.split('.').pop()),
);
}
if (!files.length) {
console.log('No Files found to process with Prettier');
return;
}
console.log(`${shouldSave ? 'Updating' : 'Checking'} ${files.length} file(s)`);
prettier
.resolveConfig('.')
.then(options => {
console.log('Found options : ', options);
files.forEach(file => {
try {
const fileExtension = file.split('.').pop();
Object.assign(options, {
parser: config.parsers[fileExtension],
});
const input = fs.readFileSync(file, 'utf8');
if (shouldSave) {
const output = prettier.format(input, options);
if (output !== input) {
fs.writeFileSync(file, output, 'utf8');
console.log(`Prettified : ${file}`);
}
} else if (!prettier.check(input, options)) {
if (!didWarn) {
console.log(
'\n===============================\nGitLab uses Prettier to format all JavaScript code.\nPlease format each file listed below or run "yarn prettier-staged-save"\n===============================\n',
);
didWarn = true;
}
console.log(`Prettify Manually : ${file}`);
}
} catch (error) {
didError = true;
console.log(`\n\nError with ${file}: ${error.message}`);
}
});
if (didWarn || didError) {
process.exit(1);
}
})
.catch(e => {
console.log(`Error on loading the Config File: ${e.message}`);
process.exit(1);
});
...@@ -39,6 +39,7 @@ describe 'CI Lint', :js do ...@@ -39,6 +39,7 @@ describe 'CI Lint', :js do
it 'displays information about an error' do it 'displays information about an error' do
expect(page).to have_content('Status: syntax is incorrect') expect(page).to have_content('Status: syntax is incorrect')
expect(page).to have_selector('.ace_content', text: yaml_content)
end end
end end
......
...@@ -527,4 +527,29 @@ feature 'File blob', :js do ...@@ -527,4 +527,29 @@ feature 'File blob', :js do
end end
end end
end end
context 'realtime pipelines' do
before do
Files::CreateService.new(
project,
project.creator,
start_branch: 'feature',
branch_name: 'feature',
commit_message: "Add ruby file",
file_path: 'files/ruby/test.rb',
file_content: "# Awesome content"
).execute
create(:ci_pipeline, status: 'running', project: project, ref: 'feature', sha: project.commit('feature').sha)
visit_blob('files/ruby/test.rb', ref: 'feature')
end
it 'should show the realtime pipeline status' do
page.within('.commit-actions') do
expect(page).to have_css('.ci-status-icon')
expect(page).to have_css('.ci-status-icon-running')
expect(page).to have_css('.js-ci-status-icon-running')
end
end
end
end end
...@@ -217,6 +217,23 @@ describe Banzai::Filter::RelativeLinkFilter do ...@@ -217,6 +217,23 @@ describe Banzai::Filter::RelativeLinkFilter do
end end
end end
context 'when ref name contains special chars' do
let(:ref) {'mark#\'@],+;-._/#@!$&()+down'}
it 'correctly escapes the ref' do
# Adressable won't escape the '#', so we do this manually
ref_escaped = 'mark%23\'@%5D,+;-._/%23@!$&()+down'
# Stub this method so the branch doesn't actually need to be in the repo
allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw)
doc = filter(link('files/images/logo-black.png'))
expect(doc.at_css('a')['href'])
.to eq "/#{project_path}/raw/#{ref_escaped}/files/images/logo-black.png"
end
end
context 'when requested path is a directory with space in the repo' do context 'when requested path is a directory with space in the repo' do
let(:ref) { 'master' } let(:ref) { 'master' }
let(:commit) { project.commit('38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e') } let(:commit) { project.commit('38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e') }
......
...@@ -12,7 +12,7 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do ...@@ -12,7 +12,7 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do
diff_files diff_files
end end
it 'does not files marked as undiffable in .gitattributes' do it 'does not highlight files marked as undiffable in .gitattributes' do
allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(false) allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(false)
expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines) expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines)
......
...@@ -43,7 +43,6 @@ ...@@ -43,7 +43,6 @@
{ {
"id": 40, "id": 40,
"title": "Voluptatem", "title": "Voluptatem",
"assignee_id": 1,
"author_id": 22, "author_id": 22,
"project_id": 5, "project_id": 5,
"created_at": "2016-06-14T15:02:08.340Z", "created_at": "2016-06-14T15:02:08.340Z",
...@@ -61,7 +60,23 @@ ...@@ -61,7 +60,23 @@
"issue_assignees": [ "issue_assignees": [
{ {
"user_id": 1, "user_id": 1,
"issue_id": 1 "issue_id": 40
},
{
"user_id": 15,
"issue_id": 40
},
{
"user_id": 16,
"issue_id": 40
},
{
"user_id": 16,
"issue_id": 40
},
{
"user_id": 6,
"issue_id": 40
} }
], ],
"milestone": { "milestone": {
...@@ -319,8 +334,7 @@ ...@@ -319,8 +334,7 @@
}, },
{ {
"id": 39, "id": 39,
"title": "Delectus veniam ratione in eos culpa et natus molestiae earum aut.", "title": "Issue without assignees",
"assignee_id": 20,
"author_id": 22, "author_id": 22,
"project_id": 5, "project_id": 5,
"created_at": "2016-06-14T15:02:08.233Z", "created_at": "2016-06-14T15:02:08.233Z",
...@@ -334,6 +348,7 @@ ...@@ -334,6 +348,7 @@
"confidential": false, "confidential": false,
"due_date": null, "due_date": null,
"moved_to_id": null, "moved_to_id": null,
"issue_assignees": [],
"milestone": { "milestone": {
"id": 1, "id": 1,
"title": "test milestone", "title": "test milestone",
...@@ -539,7 +554,6 @@ ...@@ -539,7 +554,6 @@
{ {
"id": 38, "id": 38,
"title": "Quasi adipisci non cupiditate dolorem quo qui earum sed.", "title": "Quasi adipisci non cupiditate dolorem quo qui earum sed.",
"assignee_id": 1,
"author_id": 6, "author_id": 6,
"project_id": 5, "project_id": 5,
"created_at": "2016-06-14T15:02:08.154Z", "created_at": "2016-06-14T15:02:08.154Z",
...@@ -756,7 +770,6 @@ ...@@ -756,7 +770,6 @@
{ {
"id": 37, "id": 37,
"title": "Cupiditate quo aut ducimus minima molestiae vero numquam possimus.", "title": "Cupiditate quo aut ducimus minima molestiae vero numquam possimus.",
"assignee_id": 15,
"author_id": 20, "author_id": 20,
"project_id": 5, "project_id": 5,
"created_at": "2016-06-14T15:02:08.051Z", "created_at": "2016-06-14T15:02:08.051Z",
...@@ -952,7 +965,6 @@ ...@@ -952,7 +965,6 @@
{ {
"id": 36, "id": 36,
"title": "Necessitatibus dolor est enim quia rem suscipit quidem voluptas ullam.", "title": "Necessitatibus dolor est enim quia rem suscipit quidem voluptas ullam.",
"assignee_id": 20,
"author_id": 16, "author_id": 16,
"project_id": 5, "project_id": 5,
"created_at": "2016-06-14T15:02:07.958Z", "created_at": "2016-06-14T15:02:07.958Z",
...@@ -1148,7 +1160,6 @@ ...@@ -1148,7 +1160,6 @@
{ {
"id": 35, "id": 35,
"title": "Repellat praesentium deserunt maxime incidunt harum porro qui.", "title": "Repellat praesentium deserunt maxime incidunt harum porro qui.",
"assignee_id": 6,
"author_id": 20, "author_id": 20,
"project_id": 5, "project_id": 5,
"created_at": "2016-06-14T15:02:07.832Z", "created_at": "2016-06-14T15:02:07.832Z",
...@@ -1344,7 +1355,6 @@ ...@@ -1344,7 +1355,6 @@
{ {
"id": 34, "id": 34,
"title": "Ullam expedita deserunt libero consequatur quia dolor harum perferendis facere quidem.", "title": "Ullam expedita deserunt libero consequatur quia dolor harum perferendis facere quidem.",
"assignee_id": 20,
"author_id": 1, "author_id": 1,
"project_id": 5, "project_id": 5,
"created_at": "2016-06-14T15:02:07.717Z", "created_at": "2016-06-14T15:02:07.717Z",
...@@ -1540,7 +1550,6 @@ ...@@ -1540,7 +1550,6 @@
{ {
"id": 33, "id": 33,
"title": "Numquam accusamus eos iste exercitationem magni non inventore.", "title": "Numquam accusamus eos iste exercitationem magni non inventore.",
"assignee_id": 15,
"author_id": 26, "author_id": 26,
"project_id": 5, "project_id": 5,
"created_at": "2016-06-14T15:02:07.611Z", "created_at": "2016-06-14T15:02:07.611Z",
...@@ -1736,7 +1745,6 @@ ...@@ -1736,7 +1745,6 @@
{ {
"id": 32, "id": 32,
"title": "Necessitatibus magnam qui at velit consequatur perspiciatis.", "title": "Necessitatibus magnam qui at velit consequatur perspiciatis.",
"assignee_id": 22,
"author_id": 15, "author_id": 15,
"project_id": 5, "project_id": 5,
"created_at": "2016-06-14T15:02:07.431Z", "created_at": "2016-06-14T15:02:07.431Z",
...@@ -1932,7 +1940,6 @@ ...@@ -1932,7 +1940,6 @@
{ {
"id": 31, "id": 31,
"title": "Libero nam magnam incidunt eaque placeat error et.", "title": "Libero nam magnam incidunt eaque placeat error et.",
"assignee_id": 1,
"author_id": 16, "author_id": 16,
"project_id": 5, "project_id": 5,
"created_at": "2016-06-14T15:02:07.280Z", "created_at": "2016-06-14T15:02:07.280Z",
......
...@@ -4,7 +4,12 @@ include ImportExport::CommonUtil ...@@ -4,7 +4,12 @@ include ImportExport::CommonUtil
describe Gitlab::ImportExport::ProjectTreeRestorer do describe Gitlab::ImportExport::ProjectTreeRestorer do
describe 'restore project tree' do describe 'restore project tree' do
before(:context) do before(:context) do
@user = create(:user) # Using an admin for import, so we can check assignment of existing members
@user = create(:admin)
@existing_members = [
create(:user, username: 'bernard_willms'),
create(:user, username: 'saul_will')
]
RSpec::Mocks.with_temporary_scope do RSpec::Mocks.with_temporary_scope do
@project = create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') @project = create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project')
...@@ -63,8 +68,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -63,8 +68,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC') expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC')
end end
it 'has issue assignees' do it 'has multiple issue assignees' do
expect(Issue.where(title: 'Voluptatem').first.issue_assignees).not_to be_empty expect(Issue.find_by(title: 'Voluptatem').assignees).to contain_exactly(@user, *@existing_members)
expect(Issue.find_by(title: 'Issue without assignees').assignees).to be_empty
end end
it 'contains the merge access levels on a protected branch' do it 'contains the merge access levels on a protected branch' do
......
...@@ -21,30 +21,77 @@ describe Gitlab::ProjectTransfer do ...@@ -21,30 +21,77 @@ describe Gitlab::ProjectTransfer do
describe '#move_project' do describe '#move_project' do
it "moves project upload to another namespace" do it "moves project upload to another namespace" do
FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path)) path_to_be_moved = File.join(@root_dir, @namespace_path_was, @project_path)
expected_path = File.join(@root_dir, @namespace_path, @project_path)
FileUtils.mkdir_p(path_to_be_moved)
@project_transfer.move_project(@project_path, @namespace_path_was, @namespace_path) @project_transfer.move_project(@project_path, @namespace_path_was, @namespace_path)
expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy expect(Dir.exist?(expected_path)).to be_truthy
end end
end end
describe '#move_namespace' do
context 'when moving namespace from root into another namespace' do
it "moves namespace projects' upload" do
child_namespace = 'test_child_namespace'
path_to_be_moved = File.join(@root_dir, child_namespace, @project_path)
expected_path = File.join(@root_dir, @namespace_path, child_namespace, @project_path)
FileUtils.mkdir_p(path_to_be_moved)
@project_transfer.move_namespace(child_namespace, nil, @namespace_path)
expect(Dir.exist?(expected_path)).to be_truthy
end
end
context 'when moving namespace from one parent to another' do
it "moves namespace projects' upload" do
child_namespace = 'test_child_namespace'
path_to_be_moved = File.join(@root_dir, @namespace_path_was, child_namespace, @project_path)
expected_path = File.join(@root_dir, @namespace_path, child_namespace, @project_path)
FileUtils.mkdir_p(path_to_be_moved)
@project_transfer.move_namespace(child_namespace, @namespace_path_was, @namespace_path)
expect(Dir.exist?(expected_path)).to be_truthy
end
end
context 'when moving namespace from having a parent to root' do
it "moves namespace projects' upload" do
child_namespace = 'test_child_namespace'
path_to_be_moved = File.join(@root_dir, @namespace_path_was, child_namespace, @project_path)
expected_path = File.join(@root_dir, child_namespace, @project_path)
FileUtils.mkdir_p(path_to_be_moved)
@project_transfer.move_namespace(child_namespace, @namespace_path_was, nil)
expect(Dir.exist?(expected_path)).to be_truthy
end
end
end
describe '#rename_project' do describe '#rename_project' do
it "renames project" do it "renames project" do
FileUtils.mkdir_p(File.join(@root_dir, @namespace_path, @project_path_was)) path_to_be_moved = File.join(@root_dir, @namespace_path, @project_path_was)
expected_path = File.join(@root_dir, @namespace_path, @project_path)
FileUtils.mkdir_p(path_to_be_moved)
@project_transfer.rename_project(@project_path_was, @project_path, @namespace_path) @project_transfer.rename_project(@project_path_was, @project_path, @namespace_path)
expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy expect(Dir.exist?(expected_path)).to be_truthy
end end
end end
describe '#rename_namespace' do describe '#rename_namespace' do
it "renames namespace" do it "renames namespace" do
FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path)) path_to_be_moved = File.join(@root_dir, @namespace_path_was, @project_path)
expected_path = File.join(@root_dir, @namespace_path, @project_path)
FileUtils.mkdir_p(path_to_be_moved)
@project_transfer.rename_namespace(@namespace_path_was, @namespace_path) @project_transfer.rename_namespace(@namespace_path_was, @namespace_path)
expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy expect(Dir.exist?(expected_path)).to be_truthy
end end
end end
......
...@@ -108,5 +108,10 @@ describe Gitlab::SlashCommands::Command do ...@@ -108,5 +108,10 @@ describe Gitlab::SlashCommands::Command do
it { is_expected.to eq(Gitlab::SlashCommands::IssueSearch) } it { is_expected.to eq(Gitlab::SlashCommands::IssueSearch) }
end end
context 'IssueMove is triggered' do
let(:params) { { text: 'issue move #78291 to gitlab/gitlab-ci' } }
it { is_expected.to eq(Gitlab::SlashCommands::IssueMove) }
end
end end
end end
require 'spec_helper'
describe Gitlab::SlashCommands::IssueMove, service: true do
describe '#match' do
shared_examples_for 'move command' do |text_command|
it 'can be parsed to extract the needed fields' do
match_data = described_class.match(text_command)
expect(match_data['iid']).to eq('123456')
expect(match_data['project_path']).to eq('gitlab/gitlab-ci')
end
end
it_behaves_like 'move command', 'issue move #123456 to gitlab/gitlab-ci'
it_behaves_like 'move command', 'issue move #123456 gitlab/gitlab-ci'
it_behaves_like 'move command', 'issue move #123456 gitlab/gitlab-ci '
it_behaves_like 'move command', 'issue move 123456 to gitlab/gitlab-ci'
it_behaves_like 'move command', 'issue move 123456 gitlab/gitlab-ci'
it_behaves_like 'move command', 'issue move 123456 gitlab/gitlab-ci '
end
describe '#execute' do
set(:user) { create(:user) }
set(:issue) { create(:issue) }
set(:chat_name) { create(:chat_name, user: user) }
set(:project) { issue.project }
set(:other_project) { create(:project, namespace: project.namespace) }
before do
[project, other_project].each { |prj| prj.add_master(user) }
end
subject { described_class.new(project, chat_name) }
def process_message(message)
subject.execute(described_class.match(message))
end
context 'when the user can move the issue' do
context 'when the move fails' do
it 'returns the error message' do
message = "issue move #{issue.iid} #{project.full_path}"
expect(process_message(message)).to include(response_type: :ephemeral,
text: a_string_matching('Cannot move issue'))
end
end
context 'when the move succeeds' do
let(:message) { "issue move #{issue.iid} #{other_project.full_path}" }
it 'moves the issue to the new destination' do
expect { process_message(message) }.to change { Issue.count }.by(1)
new_issue = issue.reload.moved_to
expect(new_issue.state).to eq('opened')
expect(new_issue.project_id).to eq(other_project.id)
expect(new_issue.author_id).to eq(issue.author_id)
expect(issue.state).to eq('closed')
expect(issue.project_id).to eq(project.id)
end
it 'returns the new issue' do
expect(process_message(message))
.to include(response_type: :in_channel,
attachments: [a_hash_including(title_link: a_string_including(other_project.full_path))])
end
it 'mentions the old issue' do
expect(process_message(message))
.to include(attachments: [a_hash_including(pretext: a_string_including(project.full_path))])
end
end
end
context 'when the issue does not exist' do
it 'returns not found' do
message = "issue move #{issue.iid.succ} #{other_project.full_path}"
expect(process_message(message)).to include(response_type: :ephemeral,
text: a_string_matching('not found'))
end
end
context 'when the target project does not exist' do
it 'returns not found' do
message = "issue move #{issue.iid} #{other_project.full_path}/foo"
expect(process_message(message)).to include(response_type: :ephemeral,
text: a_string_matching('not found'))
end
end
context 'when the user cannot see the target project' do
it 'returns not found' do
message = "issue move #{issue.iid} #{other_project.full_path}"
other_project.team.truncate
expect(process_message(message)).to include(response_type: :ephemeral,
text: a_string_matching('not found'))
end
end
context 'when the user does not have the required permissions on the target project' do
it 'returns the error message' do
message = "issue move #{issue.iid} #{other_project.full_path}"
other_project.team.truncate
other_project.team.add_guest(user)
expect(process_message(message)).to include(response_type: :ephemeral,
text: a_string_matching('Cannot move issue'))
end
end
end
end
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::IssueMove do
set(:admin) { create(:admin) }
set(:project) { create(:project) }
set(:other_project) { create(:project) }
set(:old_issue) { create(:issue, project: project) }
set(:new_issue) { Issues::MoveService.new(project, admin).execute(old_issue, other_project) }
let(:attachment) { subject[:attachments].first }
subject { described_class.new(new_issue).present(old_issue) }
it { is_expected.to be_a(Hash) }
it 'shows the new issue' do
expect(subject[:response_type]).to be(:in_channel)
expect(subject).to have_key(:attachments)
expect(attachment[:title]).to start_with(new_issue.title)
expect(attachment[:title_link]).to include(other_project.full_path)
end
it 'mentions the old issue and the new issue in the pretext' do
expect(attachment[:pretext]).to include(project.full_path)
expect(attachment[:pretext]).to include(other_project.full_path)
end
end
...@@ -698,21 +698,21 @@ describe Ci::Build do ...@@ -698,21 +698,21 @@ describe Ci::Build do
describe '#erase' do describe '#erase' do
before do before do
build.erase(erased_by: user) build.erase(erased_by: erased_by)
end end
context 'erased by user' do context 'erased by user' do
let!(:user) { create(:user, username: 'eraser') } let!(:erased_by) { create(:user, username: 'eraser') }
include_examples 'erasable' include_examples 'erasable'
it 'records user who erased a build' do it 'records user who erased a build' do
expect(build.erased_by).to eq user expect(build.erased_by).to eq erased_by
end end
end end
context 'erased by system' do context 'erased by system' do
let(:user) { nil } let(:erased_by) { nil }
include_examples 'erasable' include_examples 'erasable'
...@@ -767,21 +767,21 @@ describe Ci::Build do ...@@ -767,21 +767,21 @@ describe Ci::Build do
describe '#erase' do describe '#erase' do
before do before do
build.erase(erased_by: user) build.erase(erased_by: erased_by)
end end
context 'erased by user' do context 'erased by user' do
let!(:user) { create(:user, username: 'eraser') } let!(:erased_by) { create(:user, username: 'eraser') }
include_examples 'erasable' include_examples 'erasable'
it 'records user who erased a build' do it 'records user who erased a build' do
expect(build.erased_by).to eq user expect(build.erased_by).to eq erased_by
end end
end end
context 'erased by system' do context 'erased by system' do
let(:user) { nil } let(:erased_by) { nil }
include_examples 'erasable' include_examples 'erasable'
...@@ -1978,7 +1978,7 @@ describe Ci::Build do ...@@ -1978,7 +1978,7 @@ describe Ci::Build do
context 'when depended job has not been completed yet' do context 'when depended job has not been completed yet' do
let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) } let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
it { expect { job.run! }.not_to raise_error(Ci::Build::MissingDependenciesError) } it { expect { job.run! }.not_to raise_error }
end end
context 'when artifacts of depended job has been expired' do context 'when artifacts of depended job has been expired' do
......
...@@ -1780,7 +1780,7 @@ describe MergeRequest do ...@@ -1780,7 +1780,7 @@ describe MergeRequest do
end end
it "executes diff cache service" do it "executes diff cache service" do
expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject) expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject, an_instance_of(MergeRequestDiff))
subject.reload_diff subject.reload_diff
end end
......
...@@ -204,43 +204,67 @@ describe Namespace do ...@@ -204,43 +204,67 @@ describe Namespace do
expect(gitlab_shell.exists?(project.repository_storage_path, "#{namespace.path}/#{project.path}.git")).to be_truthy expect(gitlab_shell.exists?(project.repository_storage_path, "#{namespace.path}/#{project.path}.git")).to be_truthy
end end
context 'with subgroups' do context 'with subgroups', :nested_groups do
let(:parent) { create(:group, name: 'parent', path: 'parent') } let(:parent) { create(:group, name: 'parent', path: 'parent') }
let(:new_parent) { create(:group, name: 'new_parent', path: 'new_parent') }
let(:child) { create(:group, name: 'child', path: 'child', parent: parent) } let(:child) { create(:group, name: 'child', path: 'child', parent: parent) }
let!(:project) { create(:project_empty_repo, :legacy_storage, path: 'the-project', namespace: child, skip_disk_validation: true) } let!(:project) { create(:project_empty_repo, :legacy_storage, path: 'the-project', namespace: child, skip_disk_validation: true) }
let(:uploads_dir) { FileUploader.root } let(:uploads_dir) { FileUploader.root }
let(:pages_dir) { File.join(TestEnv.pages_path) } let(:pages_dir) { File.join(TestEnv.pages_path) }
def expect_project_directories_at(namespace_path)
expected_repository_path = File.join(TestEnv.repos_path, namespace_path, 'the-project.git')
expected_upload_path = File.join(uploads_dir, namespace_path, 'the-project')
expected_pages_path = File.join(pages_dir, namespace_path, 'the-project')
expect(File.directory?(expected_repository_path)).to be_truthy
expect(File.directory?(expected_upload_path)).to be_truthy
expect(File.directory?(expected_pages_path)).to be_truthy
end
before do before do
FileUtils.mkdir_p(File.join(TestEnv.repos_path, "#{project.full_path}.git"))
FileUtils.mkdir_p(File.join(uploads_dir, project.full_path)) FileUtils.mkdir_p(File.join(uploads_dir, project.full_path))
FileUtils.mkdir_p(File.join(pages_dir, project.full_path)) FileUtils.mkdir_p(File.join(pages_dir, project.full_path))
end end
context 'renaming child' do context 'renaming child' do
it 'correctly moves the repository, uploads and pages' do it 'correctly moves the repository, uploads and pages' do
expected_repository_path = File.join(TestEnv.repos_path, 'parent', 'renamed', 'the-project.git') child.update!(path: 'renamed')
expected_upload_path = File.join(uploads_dir, 'parent', 'renamed', 'the-project')
expected_pages_path = File.join(pages_dir, 'parent', 'renamed', 'the-project')
child.update_attributes!(path: 'renamed') expect_project_directories_at('parent/renamed')
expect(File.directory?(expected_repository_path)).to be(true)
expect(File.directory?(expected_upload_path)).to be(true)
expect(File.directory?(expected_pages_path)).to be(true)
end end
end end
context 'renaming parent' do context 'renaming parent' do
it 'correctly moves the repository, uploads and pages' do it 'correctly moves the repository, uploads and pages' do
expected_repository_path = File.join(TestEnv.repos_path, 'renamed', 'child', 'the-project.git') parent.update!(path: 'renamed')
expected_upload_path = File.join(uploads_dir, 'renamed', 'child', 'the-project')
expected_pages_path = File.join(pages_dir, 'renamed', 'child', 'the-project') expect_project_directories_at('renamed/child')
end
end
parent.update_attributes!(path: 'renamed') context 'moving from one parent to another' do
it 'correctly moves the repository, uploads and pages' do
child.update!(parent: new_parent)
expect(File.directory?(expected_repository_path)).to be(true) expect_project_directories_at('new_parent/child')
expect(File.directory?(expected_upload_path)).to be(true) end
expect(File.directory?(expected_pages_path)).to be(true) end
context 'moving from having a parent to root' do
it 'correctly moves the repository, uploads and pages' do
child.update!(parent: nil)
expect_project_directories_at('child')
end
end
context 'moving from root to having a parent' do
it 'correctly moves the repository, uploads and pages' do
parent.update!(parent: new_parent)
expect_project_directories_at('new_parent/parent/child')
end end
end end
end end
...@@ -537,7 +561,6 @@ describe Namespace do ...@@ -537,7 +561,6 @@ describe Namespace do
end end
end end
# Note: Group transfers are not yet implemented
context 'when a group is transferred into a root group' do context 'when a group is transferred into a root group' do
context 'when the root group "Share with group lock" is enabled' do context 'when the root group "Share with group lock" is enabled' do
let(:root_group) { create(:group, share_with_group_lock: true) } let(:root_group) { create(:group, share_with_group_lock: true) }
......
require 'spec_helper' require 'spec_helper'
describe MergeRequests::MergeRequestDiffCacheService do describe MergeRequests::MergeRequestDiffCacheService, :use_clean_rails_memory_store_caching do
let(:subject) { described_class.new } let(:subject) { described_class.new }
let(:merge_request) { create(:merge_request) }
describe '#execute' do describe '#execute' do
it 'retrieves the diff files to cache the highlighted result' do before do
merge_request = create(:merge_request)
cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequestDiff.default_options]
expect(Rails.cache).to receive(:read).with(cache_key).and_return({})
expect(Rails.cache).to receive(:write).with(cache_key, anything)
allow_any_instance_of(Gitlab::Diff::File).to receive(:text?).and_return(true) allow_any_instance_of(Gitlab::Diff::File).to receive(:text?).and_return(true)
allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(true) allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(true)
end
it 'retrieves the diff files to cache the highlighted result' do
new_diff = merge_request.merge_request_diff
cache_key = new_diff.diffs.cache_key
expect(Rails.cache).to receive(:read).with(cache_key).and_call_original
expect(Rails.cache).to receive(:write).with(cache_key, anything, anything).and_call_original
subject.execute(merge_request, new_diff)
end
it 'clears the cache for older diffs on the merge request' do
old_diff = merge_request.merge_request_diff
old_cache_key = old_diff.diffs.cache_key
subject.execute(merge_request, old_diff)
new_diff = merge_request.create_merge_request_diff
new_cache_key = new_diff.diffs.cache_key
expect(Rails.cache).to receive(:delete).with(old_cache_key).and_call_original
expect(Rails.cache).to receive(:read).with(new_cache_key).and_call_original
expect(Rails.cache).to receive(:write).with(new_cache_key, anything, anything).and_call_original
subject.execute(merge_request) subject.execute(merge_request, new_diff)
end end
end end
end end
...@@ -6685,9 +6685,9 @@ preserve@^0.2.0: ...@@ -6685,9 +6685,9 @@ preserve@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@1.9.2: prettier@1.11.1:
version "1.9.2" version "1.11.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.2.tgz#96bc2132f7a32338e6078aeb29727178c6335827" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75"
prettier@^1.7.0: prettier@^1.7.0:
version "1.8.2" version "1.8.2"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment