Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b3e0658cb1
commit
6cd5b7dbfa
|
@ -350,7 +350,9 @@ quick-rspec geo pg-10 ee:
|
|||
- $CI_COMMIT_REF_NAME =~ /(^geo[\/-].*|.*-geo$)/
|
||||
|
||||
rspec quarantine pg ee:
|
||||
extends: rspec quarantine pg
|
||||
extends:
|
||||
- rspec quarantine pg
|
||||
- .only-ee
|
||||
script:
|
||||
- export NO_KNAPSACK=1 CACHE_CLASSES=true
|
||||
- scripts/gitaly-test-spawn
|
||||
|
|
|
@ -22,7 +22,6 @@ import Board from 'ee_else_ce/boards/components/board';
|
|||
import BoardSidebar from 'ee_else_ce/boards/components/board_sidebar';
|
||||
import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown';
|
||||
import BoardAddIssuesModal from '~/boards/components/modal/index.vue';
|
||||
import '~/vue_shared/vue_resource_interceptor';
|
||||
import {
|
||||
NavigationType,
|
||||
convertObjectPropsToCamelCase,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import Vue from 'vue';
|
||||
import '../vue_shared/vue_resource_interceptor';
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Vue.config.productionTip = false;
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import Vue from 'vue';
|
||||
import Flash from '../../flash';
|
||||
import '../../vue_shared/vue_resource_interceptor';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
window.gl = window.gl || {};
|
||||
|
|
|
@ -2,7 +2,6 @@ import Vue from 'vue';
|
|||
import { initSidebarTracking } from 'ee_else_ce/event_tracking/issue_sidebar';
|
||||
import issuableApp from './components/app.vue';
|
||||
import { parseIssuableData } from './utils/parse_data';
|
||||
import '../vue_shared/vue_resource_interceptor';
|
||||
|
||||
export default function initIssueableApp() {
|
||||
return new Vue({
|
||||
|
|
|
@ -8,19 +8,19 @@ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
|||
// Maintain a global counter for active requests
|
||||
// see: spec/support/wait_for_requests.rb
|
||||
axios.interceptors.request.use(config => {
|
||||
window.activeVueResources = window.activeVueResources || 0;
|
||||
window.activeVueResources += 1;
|
||||
window.pendingRequests = window.pendingRequests || 0;
|
||||
window.pendingRequests += 1;
|
||||
return config;
|
||||
});
|
||||
|
||||
// Remove the global counter
|
||||
axios.interceptors.response.use(
|
||||
response => {
|
||||
window.activeVueResources -= 1;
|
||||
window.pendingRequests -= 1;
|
||||
return response;
|
||||
},
|
||||
err => {
|
||||
window.activeVueResources -= 1;
|
||||
window.pendingRequests -= 1;
|
||||
return Promise.reject(err);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -27,8 +27,7 @@ export default {
|
|||
|
||||
/**
|
||||
pageInfo will come from the headers of the API call
|
||||
in the `.then` clause of the VueResource API call
|
||||
there should be a function that contructs the pageInfo for this component
|
||||
there should be a function that constructs the pageInfo for this component
|
||||
|
||||
This is an example:
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
import csrf from '../lib/utils/csrf';
|
||||
|
||||
Vue.use(VueResource);
|
||||
|
||||
// Maintain a global counter for active requests
|
||||
// see: spec/support/wait_for_requests.rb
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
window.activeVueResources = window.activeVueResources || 0;
|
||||
window.activeVueResources += 1;
|
||||
|
||||
next(() => {
|
||||
window.activeVueResources -= 1;
|
||||
});
|
||||
});
|
||||
|
||||
// Inject CSRF token and parse headers.
|
||||
// New Vue Resource version uses Headers, we are expecting a plain object to render pagination
|
||||
// and polling.
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
request.headers.set(csrf.headerKey, csrf.token);
|
||||
|
||||
next(response => {
|
||||
// Headers object has a `forEach` property that iterates through all values.
|
||||
const headers = {};
|
||||
|
||||
response.headers.forEach((value, key) => {
|
||||
headers[key] = value;
|
||||
});
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
response.headers = headers;
|
||||
});
|
||||
});
|
|
@ -141,7 +141,7 @@
|
|||
}
|
||||
|
||||
.sidebar-top-level-items > li > a {
|
||||
min-height: 44px;
|
||||
min-height: 45px;
|
||||
}
|
||||
|
||||
.fly-out-top-item {
|
||||
|
|
|
@ -19,14 +19,12 @@ module Projects
|
|||
end
|
||||
|
||||
def destroy
|
||||
if tag.delete
|
||||
respond_to do |format|
|
||||
format.json { head :no_content }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.json { head :bad_request }
|
||||
end
|
||||
result = Projects::ContainerRepository::DeleteTagsService
|
||||
.new(image.project, current_user, tags: [params[:id]])
|
||||
.execute(image)
|
||||
|
||||
respond_to do |format|
|
||||
format.json { head(result[:status] == :success ? :ok : bad_request) }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -42,21 +40,12 @@ module Projects
|
|||
return
|
||||
end
|
||||
|
||||
@tags = tag_names.map { |tag_name| image.tag(tag_name) }
|
||||
unless @tags.all? { |tag| tag.valid_name? }
|
||||
head :bad_request
|
||||
return
|
||||
end
|
||||
|
||||
success_count = 0
|
||||
@tags.each do |tag|
|
||||
if tag.delete
|
||||
success_count += 1
|
||||
end
|
||||
end
|
||||
result = Projects::ContainerRepository::DeleteTagsService
|
||||
.new(image.project, current_user, tags: tag_names)
|
||||
.execute(image)
|
||||
|
||||
respond_to do |format|
|
||||
format.json { head(success_count == @tags.size ? :no_content : :bad_request) }
|
||||
format.json { head(result[:status] == :success ? :no_content : :bad_request) }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -70,10 +59,6 @@ module Projects
|
|||
@image ||= project.container_repositories
|
||||
.find(params[:repository_id])
|
||||
end
|
||||
|
||||
def tag
|
||||
@tag ||= image.tag(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,7 +40,7 @@ module Projects
|
|||
return unless tags.count == other_tags.count
|
||||
|
||||
# delete all tags
|
||||
tags.map(&:delete)
|
||||
tags.map(&:unsafe_delete)
|
||||
end
|
||||
|
||||
def group_by_digest(tags)
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
module ContainerRepository
|
||||
class DeleteTagsService < BaseService
|
||||
def execute(container_repository)
|
||||
return error('access denied') unless can?(current_user, :destroy_container_image, project)
|
||||
|
||||
tag_names = params[:tags]
|
||||
return error('not tags specified') if tag_names.blank?
|
||||
|
||||
if can_use?
|
||||
smart_delete(container_repository, tag_names)
|
||||
else
|
||||
unsafe_delete(container_repository, tag_names)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unsafe_delete(container_repository, tag_names)
|
||||
deleted_tags = tag_names.select do |tag_name|
|
||||
container_repository.tag(tag_name).unsafe_delete
|
||||
end
|
||||
|
||||
return error('could not delete tags') if deleted_tags.empty?
|
||||
|
||||
success(deleted: deleted_tags)
|
||||
end
|
||||
|
||||
# Replace a tag on the registry with a dummy tag.
|
||||
# This is a hack as the registry doesn't support deleting individual
|
||||
# tags. This code effectively pushes a dummy image and assigns the tag to it.
|
||||
# This way when the tag is deleted only the dummy image is affected.
|
||||
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/21405 for a discussion
|
||||
def smart_delete(container_repository, tag_names)
|
||||
# generates the blobs for the dummy image
|
||||
dummy_manifest = container_repository.client.generate_empty_manifest(container_repository.path)
|
||||
|
||||
# update the manifests of the tags with the new dummy image
|
||||
tag_digests = tag_names.map do |name|
|
||||
container_repository.client.put_tag(container_repository.path, name, dummy_manifest)
|
||||
end
|
||||
|
||||
# make sure the digests are the same (it should always be)
|
||||
tag_digests.uniq!
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
Gitlab::Sentry.track_exception(ArgumentError.new('multiple tag digests')) if tag_digests.many?
|
||||
|
||||
# deletes the dummy image
|
||||
# all created tag digests are the same since they all have the same dummy image.
|
||||
# a single delete is sufficient to remove all tags with it
|
||||
if container_repository.client.delete_repository_tag(container_repository.path, tag_digests.first)
|
||||
success(deleted: tag_names)
|
||||
else
|
||||
error('could not delete tags')
|
||||
end
|
||||
end
|
||||
|
||||
def can_use?
|
||||
Feature.enabled?(:container_registry_smart_delete, project, default_enabled: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'Adds the ability to delete single tags from the docker registry. Fix the issue that caused all related tags and image to be deleted at the same time.'
|
||||
merge_request: 16886
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Stop sidebar icons from jumping when expanded & collapsed
|
||||
merge_request: 16971
|
||||
author:
|
||||
type: fixed
|
|
@ -252,8 +252,8 @@ This action does not delete blobs. In order to delete them and recycle disk spac
|
|||
[run the garbage collection](https://docs.gitlab.com/omnibus/maintenance/README.html#removing-unused-layers-not-referenced-by-manifests).
|
||||
|
||||
NOTE: **Note:**
|
||||
Due to a [Docker Distribution deficiency](https://gitlab.com/gitlab-org/gitlab-foss/issues/21405),
|
||||
it doesn't remove tags whose manifest is shared by multiple tags.
|
||||
Since GitLab 12.4, individual tags are deleted.
|
||||
For more details, see the [discussion](https://gitlab.com/gitlab-org/gitlab/issues/15737).
|
||||
|
||||
Examples:
|
||||
|
||||
|
|
|
@ -253,13 +253,13 @@ table_display_block: true
|
|||
---
|
||||
```
|
||||
|
||||
## Emphasis
|
||||
### Emphasis
|
||||
|
||||
- Use double asterisks (`**`) to mark a word or text in bold (`**bold**`).
|
||||
- Use underscore (`_`) for text in italics (`_italic_`).
|
||||
- Use greater than (`>`) for blockquotes.
|
||||
|
||||
## Punctuation
|
||||
### Punctuation
|
||||
|
||||
Check the general punctuation rules for the GitLab documentation on the table below.
|
||||
Check specific punctuation rules for [lists](#lists) below.
|
||||
|
@ -274,6 +274,20 @@ Check specific punctuation rules for [lists](#lists) below.
|
|||
| Always add a space before and after dashes when using it in a sentence (for replacing a comma, for example). | _You should try this - or not._ |
|
||||
| Always use lowercase after a colon. | _Related Issues: a way to create a relationship between issues._ |
|
||||
|
||||
### Placeholder text
|
||||
|
||||
Often in examples, a writer will provide a command or configuration that is complete apart from
|
||||
a value specific to the reader.
|
||||
|
||||
In these cases, use [`<` and `>`](https://en.wikipedia.org/wiki/Usage_message#Pattern) to call out
|
||||
where a reader must replace text with their own value.
|
||||
|
||||
For example:
|
||||
|
||||
```sh
|
||||
cp <your_source_directory> <your_destination_directory>
|
||||
```
|
||||
|
||||
## Lists
|
||||
|
||||
- Always start list items with a capital letter, unless they are parameters or commands
|
||||
|
|
|
@ -106,9 +106,15 @@ module API
|
|||
authorize_destroy_container_image!
|
||||
validate_tag!
|
||||
|
||||
tag.delete
|
||||
result = ::Projects::ContainerRepository::DeleteTagsService
|
||||
.new(repository.project, current_user, tags: [declared_params[:tag_name]])
|
||||
.execute(repository)
|
||||
|
||||
status :ok
|
||||
if result[:status] == :success
|
||||
status :ok
|
||||
else
|
||||
status :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require 'faraday'
|
||||
require 'faraday_middleware'
|
||||
require 'digest'
|
||||
|
||||
module ContainerRegistry
|
||||
class Client
|
||||
|
@ -9,6 +10,8 @@ module ContainerRegistry
|
|||
|
||||
DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE = 'application/vnd.docker.distribution.manifest.v2+json'
|
||||
OCI_MANIFEST_V1_TYPE = 'application/vnd.oci.image.manifest.v1+json'
|
||||
CONTAINER_IMAGE_V1_TYPE = 'application/vnd.docker.container.image.v1+json'
|
||||
|
||||
ACCEPTED_TYPES = [DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE, OCI_MANIFEST_V1_TYPE].freeze
|
||||
|
||||
# Taken from: FaradayMiddleware::FollowRedirects
|
||||
|
@ -36,6 +39,45 @@ module ContainerRegistry
|
|||
faraday.delete("/v2/#{name}/manifests/#{reference}").success?
|
||||
end
|
||||
|
||||
def upload_raw_blob(path, blob)
|
||||
digest = "sha256:#{Digest::SHA256.hexdigest(blob)}"
|
||||
|
||||
if upload_blob(path, blob, digest).success?
|
||||
[blob, digest]
|
||||
end
|
||||
end
|
||||
|
||||
def upload_blob(name, content, digest)
|
||||
upload = faraday.post("/v2/#{name}/blobs/uploads/")
|
||||
return unless upload.success?
|
||||
|
||||
location = URI(upload.headers['location'])
|
||||
|
||||
faraday.put("#{location.path}?#{location.query}") do |req|
|
||||
req.params['digest'] = digest
|
||||
req.headers['Content-Type'] = 'application/octet-stream'
|
||||
req.body = content
|
||||
end
|
||||
end
|
||||
|
||||
def generate_empty_manifest(path)
|
||||
image = {
|
||||
config: {}
|
||||
}
|
||||
image, image_digest = upload_raw_blob(path, JSON.pretty_generate(image))
|
||||
return unless image
|
||||
|
||||
{
|
||||
schemaVersion: 2,
|
||||
mediaType: DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE,
|
||||
config: {
|
||||
mediaType: CONTAINER_IMAGE_V1_TYPE,
|
||||
size: image.size,
|
||||
digest: image_digest
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def blob(name, digest, type = nil)
|
||||
type ||= 'application/octet-stream'
|
||||
response_body faraday_blob.get("/v2/#{name}/blobs/#{digest}", nil, 'Accept' => type), allow_redirect: true
|
||||
|
@ -45,6 +87,15 @@ module ContainerRegistry
|
|||
faraday.delete("/v2/#{name}/blobs/#{digest}").success?
|
||||
end
|
||||
|
||||
def put_tag(name, reference, manifest)
|
||||
response = faraday.put("/v2/#{name}/manifests/#{reference}") do |req|
|
||||
req.headers['Content-Type'] = DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE
|
||||
req.body = JSON.pretty_generate(manifest)
|
||||
end
|
||||
|
||||
response.headers['docker-content-digest'] if response.success?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize_connection(conn, options)
|
||||
|
|
|
@ -98,6 +98,10 @@ module ContainerRegistry
|
|||
end
|
||||
end
|
||||
|
||||
def put(digests)
|
||||
repository.client.put_tag(repository.path, name, digests)
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def total_size
|
||||
return unless layers
|
||||
|
@ -106,7 +110,10 @@ module ContainerRegistry
|
|||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def delete
|
||||
# Deletes the image associated with this tag
|
||||
# Note this will delete the image and all tags associated with it.
|
||||
# Consider using DeleteTagsService instead.
|
||||
def unsafe_delete
|
||||
return unless digest
|
||||
|
||||
client.delete_repository_tag(repository.path, digest)
|
||||
|
|
|
@ -1,444 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop: disable Rails/Output
|
||||
module Gitlab
|
||||
# Checks if a set of migrations requires downtime or not.
|
||||
class EeCompatCheck
|
||||
CANONICAL_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-foss'
|
||||
CANONICAL_EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab.git'
|
||||
CHECK_DIR = Rails.root.join('ee_compat_check')
|
||||
IGNORED_FILES_REGEX = /VERSION|CHANGELOG\.md|doc\/.+/i.freeze
|
||||
PLEASE_READ_THIS_BANNER = %Q{
|
||||
============================================================
|
||||
===================== PLEASE READ THIS =====================
|
||||
============================================================
|
||||
}.freeze
|
||||
STAY_STRONG_LINK_TO_DOCS = %Q{
|
||||
Stay 💪! For more information, see
|
||||
https://docs.gitlab.com/ce/development/automatic_ce_ee_merge.html
|
||||
}.freeze
|
||||
THANKS_FOR_READING_BANNER = %Q{
|
||||
============================================================
|
||||
==================== THANKS FOR READING ====================
|
||||
============================================================\n
|
||||
}.freeze
|
||||
|
||||
attr_reader :ee_repo_dir, :patches_dir
|
||||
attr_reader :ce_project_url, :ee_repo_url
|
||||
attr_reader :ce_branch, :ee_remote_with_branch, :ee_branch_found
|
||||
attr_reader :job_id, :failed_files
|
||||
|
||||
def initialize(branch:, ce_project_url: CANONICAL_CE_PROJECT_URL, job_id: nil)
|
||||
@ee_repo_dir = CHECK_DIR.join('ee-repo')
|
||||
@patches_dir = CHECK_DIR.join('patches')
|
||||
@ce_branch = branch
|
||||
@ce_project_url = ce_project_url
|
||||
@ee_repo_url = ce_public_repo_url.sub('gitlab-ce', 'gitlab-ee')
|
||||
@job_id = job_id
|
||||
end
|
||||
|
||||
def check
|
||||
ensure_patches_dir
|
||||
# We're generating the patch against the canonical-ce remote since forks'
|
||||
# master branch are not necessarily up-to-date.
|
||||
add_remote('canonical-ce', "#{CANONICAL_CE_PROJECT_URL}.git")
|
||||
generate_patch(branch: ce_branch, patch_path: ce_patch_full_path, branch_remote: 'origin', master_remote: 'canonical-ce')
|
||||
|
||||
ensure_ee_repo
|
||||
Dir.chdir(ee_repo_dir) do
|
||||
step("In the #{ee_repo_dir} directory")
|
||||
|
||||
ee_remotes.each do |key, url|
|
||||
add_remote(key, url)
|
||||
end
|
||||
fetch(branch: 'master', depth: 20, remote: 'canonical-ee')
|
||||
|
||||
status = catch(:halt_check) do
|
||||
ce_branch_compat_check!
|
||||
delete_ee_branches_locally!
|
||||
ee_branch_presence_check!
|
||||
|
||||
step("Checking out #{ee_remote_with_branch}/#{ee_branch_found}", %W[git checkout -b #{ee_branch_found} #{ee_remote_with_branch}/#{ee_branch_found}])
|
||||
generate_patch(branch: ee_branch_found, patch_path: ee_patch_full_path, branch_remote: ee_remote_with_branch, master_remote: 'canonical-ee')
|
||||
ee_branch_compat_check!
|
||||
end
|
||||
|
||||
delete_ee_branches_locally!
|
||||
|
||||
status.nil?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fork?
|
||||
ce_project_url != CANONICAL_CE_PROJECT_URL
|
||||
end
|
||||
|
||||
def ee_remotes
|
||||
return @ee_remotes if defined?(@ee_remotes)
|
||||
|
||||
remotes =
|
||||
{
|
||||
'ee' => ee_repo_url,
|
||||
'canonical-ee' => CANONICAL_EE_REPO_URL
|
||||
}
|
||||
remotes.delete('ee') unless fork?
|
||||
|
||||
@ee_remotes = remotes
|
||||
end
|
||||
|
||||
def add_remote(name, url)
|
||||
step(
|
||||
"Adding the #{name} remote (#{url})",
|
||||
%W[git remote add #{name} #{url}]
|
||||
)
|
||||
end
|
||||
|
||||
def ensure_ee_repo
|
||||
unless clone_repo(ee_repo_url, ee_repo_dir)
|
||||
# Fallback to using the canonical EE if there is no forked EE
|
||||
clone_repo(CANONICAL_EE_REPO_URL, ee_repo_dir)
|
||||
end
|
||||
end
|
||||
|
||||
def clone_repo(url, dir)
|
||||
_, status = step(
|
||||
"Cloning #{url} into #{dir}",
|
||||
%W[git clone --branch master --single-branch --depth=200 #{url} #{dir}]
|
||||
)
|
||||
status.zero?
|
||||
end
|
||||
|
||||
def ensure_patches_dir
|
||||
FileUtils.mkdir_p(patches_dir)
|
||||
end
|
||||
|
||||
def generate_patch(branch:, patch_path:, branch_remote:, master_remote:)
|
||||
FileUtils.rm(patch_path, force: true)
|
||||
|
||||
find_merge_base_with_master(branch: branch, branch_remote: branch_remote, master_remote: master_remote)
|
||||
|
||||
step(
|
||||
"Generating the patch against #{master_remote}/master in #{patch_path}",
|
||||
%W[git diff --binary #{master_remote}/master...#{branch_remote}/#{branch}]
|
||||
) do |output, status|
|
||||
throw(:halt_check, :ko) unless status.zero?
|
||||
|
||||
File.write(patch_path, output)
|
||||
|
||||
throw(:halt_check, :ko) unless File.exist?(patch_path)
|
||||
end
|
||||
end
|
||||
|
||||
def ce_branch_compat_check!
|
||||
if check_patch(ce_patch_full_path).zero?
|
||||
puts applies_cleanly_msg(ce_branch)
|
||||
throw(:halt_check)
|
||||
end
|
||||
end
|
||||
|
||||
def ee_branch_presence_check!
|
||||
ee_remotes.keys.each do |remote|
|
||||
output, _ = step(
|
||||
"Searching #{remote}",
|
||||
%W[git ls-remote #{remote} *#{minimal_ee_branch_name}*])
|
||||
|
||||
branches =
|
||||
output.scan(%r{(?<=refs/heads/|refs/tags/).+}).sort_by(&:size)
|
||||
|
||||
next if branches.empty?
|
||||
|
||||
branch = branches.first
|
||||
|
||||
step("Fetching #{remote}/#{branch}", %W[git fetch #{remote} #{branch}])
|
||||
|
||||
@ee_remote_with_branch = remote
|
||||
@ee_branch_found = branch
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
puts
|
||||
puts ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg
|
||||
|
||||
throw(:halt_check, :ko)
|
||||
end
|
||||
|
||||
def ee_branch_compat_check!
|
||||
unless check_patch(ee_patch_full_path).zero?
|
||||
puts
|
||||
puts ee_branch_doesnt_apply_cleanly_msg
|
||||
|
||||
throw(:halt_check, :ko)
|
||||
end
|
||||
|
||||
puts
|
||||
puts applies_cleanly_msg(ee_branch_found)
|
||||
end
|
||||
|
||||
def check_patch(patch_path)
|
||||
step("Checking out master", %w[git checkout master])
|
||||
step("Resetting to latest master", %w[git reset --hard canonical-ee/master])
|
||||
step(
|
||||
"Checking if #{patch_path} applies cleanly to EE/master",
|
||||
# Don't use --check here because it can result in a 0-exit status even
|
||||
# though the patch doesn't apply cleanly, e.g.:
|
||||
# > git apply --check --3way foo.patch
|
||||
# error: patch failed: lib/gitlab/ee_compat_check.rb:74
|
||||
# Falling back to three-way merge...
|
||||
# Applied patch to 'lib/gitlab/ee_compat_check.rb' with conflicts.
|
||||
# > echo $?
|
||||
# 0
|
||||
%W[git apply --3way #{patch_path}]
|
||||
) do |output, status|
|
||||
puts output
|
||||
|
||||
unless status.zero?
|
||||
@failed_files = output.lines.reduce([]) do |memo, line|
|
||||
if line.start_with?('error: patch failed:')
|
||||
file = line.sub(/\Aerror: patch failed: /, '')
|
||||
memo << file unless file =~ IGNORED_FILES_REGEX
|
||||
end
|
||||
|
||||
memo
|
||||
end
|
||||
|
||||
status = 0 if failed_files.empty?
|
||||
end
|
||||
|
||||
command(%w[git reset --hard])
|
||||
status
|
||||
end
|
||||
end
|
||||
|
||||
def delete_ee_branches_locally!
|
||||
command(%w[git checkout master])
|
||||
command(%W[git branch --delete --force #{ee_branch_prefix}])
|
||||
command(%W[git branch --delete --force #{ee_branch_suffix}])
|
||||
end
|
||||
|
||||
def merge_base_found?(branch:, branch_remote:, master_remote:)
|
||||
step(
|
||||
"Finding merge base with #{master_remote}/master",
|
||||
%W[git merge-base #{master_remote}/master #{branch_remote}/#{branch}]
|
||||
) do |output, status|
|
||||
if status.zero?
|
||||
puts "Merge base was found: #{output}"
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def find_merge_base_with_master(branch:, branch_remote:, master_remote:)
|
||||
# Start with (Math.exp(3).to_i = 20) until (Math.exp(6).to_i = 403)
|
||||
# In total we go (20 + 54 + 148 + 403 = 625) commits deeper
|
||||
depth = 20
|
||||
success =
|
||||
(3..6).any? do |factor|
|
||||
depth += Math.exp(factor).to_i
|
||||
# Repository is initially cloned with a depth of 20 so we need to fetch
|
||||
# deeper in the case the branch has more than 20 commits on top of master
|
||||
fetch(branch: branch, depth: depth, remote: branch_remote)
|
||||
fetch(branch: 'master', depth: depth, remote: master_remote)
|
||||
|
||||
merge_base_found?(branch: branch, branch_remote: branch_remote, master_remote: master_remote)
|
||||
end
|
||||
|
||||
raise "\n#{branch} is too far behind #{master_remote}/master, please rebase it!\n" unless success
|
||||
end
|
||||
|
||||
def fetch(branch:, depth:, remote: 'origin')
|
||||
step(
|
||||
"Fetching deeper...",
|
||||
%W[git fetch --depth=#{depth} --prune #{remote} +refs/heads/#{branch}:refs/remotes/#{remote}/#{branch}]
|
||||
) do |output, status|
|
||||
raise "Fetch failed: #{output}" unless status.zero?
|
||||
end
|
||||
end
|
||||
|
||||
def ce_patch_name
|
||||
@ce_patch_name ||= patch_name_from_branch(ce_branch)
|
||||
end
|
||||
|
||||
def ce_patch_full_path
|
||||
@ce_patch_full_path ||= patches_dir.join(ce_patch_name)
|
||||
end
|
||||
|
||||
def ee_branch_suffix
|
||||
@ee_branch_suffix ||= "#{ce_branch}-ee"
|
||||
end
|
||||
|
||||
def ee_branch_prefix
|
||||
@ee_branch_prefix ||= "ee-#{ce_branch}"
|
||||
end
|
||||
|
||||
def ee_patch_name
|
||||
@ee_patch_name ||= patch_name_from_branch(ee_branch_found)
|
||||
end
|
||||
|
||||
def ee_patch_full_path
|
||||
@ee_patch_full_path ||= patches_dir.join(ee_patch_name)
|
||||
end
|
||||
|
||||
def minimal_ee_branch_name
|
||||
@minimal_ee_branch_name ||= ce_branch.sub(/(\Ace\-|\-ce\z)/, '')
|
||||
end
|
||||
|
||||
def patch_name_from_branch(branch_name)
|
||||
"#{branch_name.parameterize}.patch"
|
||||
end
|
||||
|
||||
def patch_url
|
||||
"#{ce_project_url}/-/jobs/#{job_id}/artifacts/raw/ee_compat_check/patches/#{ce_patch_name}"
|
||||
end
|
||||
|
||||
def step(desc, cmd = nil)
|
||||
puts "\n=> #{desc}\n"
|
||||
|
||||
if cmd
|
||||
start = Time.now
|
||||
puts "\n$ #{cmd.join(' ')}"
|
||||
|
||||
output, status = command(cmd)
|
||||
puts "\n==> Finished in #{Time.now - start} seconds"
|
||||
|
||||
if block_given?
|
||||
yield(output, status)
|
||||
else
|
||||
[output, status]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def command(cmd)
|
||||
Gitlab::Popen.popen(cmd)
|
||||
end
|
||||
|
||||
# We're "re-creating" the repo URL because ENV['CI_REPOSITORY_URL'] contains
|
||||
# redacted credentials (e.g. "***:****") which are useless in instructions
|
||||
# the job gives.
|
||||
def ce_public_repo_url
|
||||
"#{ce_project_url}.git"
|
||||
end
|
||||
|
||||
def applies_cleanly_msg(branch)
|
||||
%Q{
|
||||
#{PLEASE_READ_THIS_BANNER}
|
||||
🎉 Congratulations!! 🎉
|
||||
|
||||
The `#{branch}` branch applies cleanly to EE/master!
|
||||
|
||||
Much ❤️! For more information, see
|
||||
https://docs.gitlab.com/ce/development/automatic_ce_ee_merge.html
|
||||
#{THANKS_FOR_READING_BANNER}
|
||||
}
|
||||
end
|
||||
|
||||
def ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg
|
||||
ee_repos = ee_remotes.values.uniq
|
||||
|
||||
%Q{
|
||||
#{PLEASE_READ_THIS_BANNER}
|
||||
💥 Oh no! 💥
|
||||
|
||||
The `#{ce_branch}` branch does not apply cleanly to the current
|
||||
EE/master, and no `#{ee_branch_prefix}` or `#{ee_branch_suffix}` branch
|
||||
was found in #{ee_repos.join(' nor in ')}.
|
||||
|
||||
If you're a community contributor, don't worry, someone from
|
||||
GitLab Inc. will take care of this, and you don't have to do anything.
|
||||
If you're willing to help, and are ok to contribute to EE as well,
|
||||
you're welcome to help. You could follow the instructions below.
|
||||
|
||||
#{conflicting_files_msg}
|
||||
|
||||
We advise you to create a `#{ee_branch_prefix}` or `#{ee_branch_suffix}`
|
||||
branch that includes changes from `#{ce_branch}` but also specific changes
|
||||
than can be applied cleanly to EE/master. In some cases, the conflicts
|
||||
are trivial and you can ignore the warning from this job. As always,
|
||||
use your best judgement!
|
||||
|
||||
There are different ways to create such branch:
|
||||
|
||||
1. Create a new branch from master and cherry-pick your CE commits
|
||||
|
||||
# In the EE repo
|
||||
$ git fetch #{CANONICAL_EE_REPO_URL} master
|
||||
$ git checkout -b #{ee_branch_prefix} FETCH_HEAD
|
||||
$ git fetch #{ce_public_repo_url} #{ce_branch}
|
||||
$ git cherry-pick SHA # Repeat for all the commits you want to pick
|
||||
|
||||
Note: You can squash the `#{ce_branch}` commits into a single "Port of #{ce_branch} to EE" commit.
|
||||
|
||||
2. Apply your branch's patch to EE
|
||||
|
||||
# In the EE repo
|
||||
$ git fetch #{CANONICAL_EE_REPO_URL} master
|
||||
$ git checkout -b #{ee_branch_prefix} FETCH_HEAD
|
||||
$ wget #{patch_url} && git apply --3way #{ce_patch_name}
|
||||
|
||||
At this point you might have conflicts such as:
|
||||
|
||||
error: patch failed: lib/gitlab/ee_compat_check.rb:5
|
||||
Falling back to three-way merge...
|
||||
Applied patch to 'lib/gitlab/ee_compat_check.rb' with conflicts.
|
||||
U lib/gitlab/ee_compat_check.rb
|
||||
|
||||
Resolve them, stage the changes and commit them.
|
||||
|
||||
If the patch couldn't be applied cleanly, use the following command:
|
||||
|
||||
# In the EE repo
|
||||
$ git apply --reject #{ce_patch_name}
|
||||
|
||||
This option makes git apply the parts of the patch that are applicable,
|
||||
and leave the rejected hunks in corresponding `.rej` files.
|
||||
You can then resolve the conflicts highlighted in `.rej` by
|
||||
manually applying the correct diff from the `.rej` file to the file with conflicts.
|
||||
When finished, you can delete the `.rej` files and commit your changes.
|
||||
|
||||
⚠️ Don't forget to push your branch to gitlab-ee:
|
||||
|
||||
# In the EE repo
|
||||
$ git push origin #{ee_branch_prefix}
|
||||
|
||||
⚠️ Also, don't forget to create a new merge request on gitlab-ee and
|
||||
cross-link it with the CE merge request.
|
||||
|
||||
Once this is done, you can retry this failed job, and it should pass.
|
||||
|
||||
#{STAY_STRONG_LINK_TO_DOCS}
|
||||
#{THANKS_FOR_READING_BANNER}
|
||||
}
|
||||
end
|
||||
|
||||
def ee_branch_doesnt_apply_cleanly_msg
|
||||
%Q{
|
||||
#{PLEASE_READ_THIS_BANNER}
|
||||
💥 Oh no! 💥
|
||||
|
||||
The `#{ce_branch}` does not apply cleanly to the current EE/master, and
|
||||
even though a `#{ee_branch_found}` branch
|
||||
exists in #{ee_repo_url}, it does not apply cleanly either to
|
||||
EE/master!
|
||||
|
||||
#{conflicting_files_msg}
|
||||
|
||||
Please update the `#{ee_branch_found}`, push it again to gitlab-ee, and
|
||||
retry this job.
|
||||
|
||||
#{STAY_STRONG_LINK_TO_DOCS}
|
||||
#{THANKS_FOR_READING_BANNER}
|
||||
}
|
||||
end
|
||||
|
||||
def conflicting_files_msg
|
||||
header = "The conflicts detected were as follows:\n"
|
||||
separator = "\n - "
|
||||
failed_items = failed_files.join(separator)
|
||||
|
||||
"#{header}#{separator}#{failed_items}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +0,0 @@
|
|||
desc 'Checks if the branch would apply cleanly to EE'
|
||||
task ee_compat_check: :environment do
|
||||
Rake::Task['gitlab:dev:ee_compat_check'].invoke
|
||||
end
|
|
@ -1,31 +0,0 @@
|
|||
namespace :gitlab do
|
||||
namespace :dev do
|
||||
desc 'Checks if the branch would apply cleanly to EE'
|
||||
task :ee_compat_check, [:branch] => :environment do |_, args|
|
||||
opts =
|
||||
if ENV['CI']
|
||||
{
|
||||
ce_project_url: ENV['CI_PROJECT_URL'],
|
||||
branch: ENV['CI_COMMIT_REF_NAME'],
|
||||
job_id: ENV['CI_JOB_ID']
|
||||
}
|
||||
else
|
||||
unless args[:branch]
|
||||
puts "Must specify a branch as an argument".color(:red)
|
||||
exit 1
|
||||
end
|
||||
|
||||
args
|
||||
end
|
||||
|
||||
if File.basename(Rails.root) == 'gitlab'
|
||||
puts "Skipping EE projects"
|
||||
exit 0
|
||||
elsif Gitlab::EeCompatCheck.new(opts || {}).check
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -28,14 +28,23 @@ unless Rails.env.production?
|
|||
task :all do
|
||||
status = 0
|
||||
|
||||
%w[
|
||||
tasks = %w[
|
||||
config_lint
|
||||
lint:haml
|
||||
scss_lint
|
||||
gettext:lint
|
||||
gettext:updated_check
|
||||
lint:static_verification
|
||||
].each do |task|
|
||||
]
|
||||
|
||||
if Gitlab.ee?
|
||||
# This task will fail on CE installations (e.g. gitlab-org/gitlab-foss)
|
||||
# since it will detect strings in the locale files that do not exist in
|
||||
# the source files. To work around this we will only enable this task on
|
||||
# EE installations.
|
||||
tasks << 'gettext:updated_check'
|
||||
end
|
||||
|
||||
tasks.each do |task|
|
||||
pid = Process.fork do
|
||||
puts "*** Running rake task: #{task} ***"
|
||||
|
||||
|
|
|
@ -133,7 +133,6 @@
|
|||
"vue": "^2.6.10",
|
||||
"vue-apollo": "^3.0.0-beta.28",
|
||||
"vue-loader": "^15.7.0",
|
||||
"vue-resource": "^1.5.1",
|
||||
"vue-router": "^3.0.2",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"vue-virtual-scroll-list": "^1.3.1",
|
||||
|
|
|
@ -1,470 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
|
||||
module EESpecificCheck
|
||||
WHITELIST = [
|
||||
'CHANGELOG-EE.md',
|
||||
'scripts/**/*',
|
||||
'vendor/assets/javascripts/jasmine-jquery.js',
|
||||
'.gitlab-ci.yml',
|
||||
'.gitlab/ci/rails.gitlab-ci.yml',
|
||||
'db/schema.rb',
|
||||
'locale/gitlab.pot'
|
||||
].freeze
|
||||
|
||||
CompareBase = Struct.new(:ce_base, :ee_base, :ce_head, :ee_head)
|
||||
GitStatus = Struct.new(:porcelain, :head)
|
||||
|
||||
module_function
|
||||
|
||||
def git_version
|
||||
say run_git_command('--version')
|
||||
end
|
||||
|
||||
def say(message)
|
||||
warn "\n#{message}", "\n" # puts would eat trailing newline
|
||||
end
|
||||
|
||||
def find_compare_base
|
||||
git_clean
|
||||
|
||||
ce_fetch_head = fetch_remote_ce_branch
|
||||
ee_fetch_head = head_commit_sha
|
||||
ce_fetch_base = find_merge_base('canonical-ce/master', ce_fetch_head)
|
||||
ee_fetch_base = find_merge_base('canonical-ee/master', 'HEAD')
|
||||
ce_merge_base = find_merge_base(ce_fetch_head, ee_fetch_head)
|
||||
|
||||
ce_updated_head =
|
||||
find_ce_compare_head(ce_fetch_head, ce_fetch_base, ce_merge_base)
|
||||
|
||||
CompareBase.new(
|
||||
ce_merge_base, ee_fetch_base, ce_updated_head, ee_fetch_head)
|
||||
end
|
||||
|
||||
def setup_canonical_remotes
|
||||
run_git_command(
|
||||
"remote add canonical-ee https://gitlab.com/gitlab-org/gitlab.git",
|
||||
"remote add canonical-ce https://gitlab.com/gitlab-org/gitlab-foss.git",
|
||||
"fetch canonical-ee master --quiet --depth=9999",
|
||||
"fetch canonical-ce master --quiet --depth=9999")
|
||||
end
|
||||
|
||||
def fetch_remote_ce_branch
|
||||
setup_canonical_remotes
|
||||
|
||||
remote_to_fetch, branch_to_fetch = find_remote_ce_branch
|
||||
|
||||
run_git_command("fetch #{remote_to_fetch} #{branch_to_fetch} --quiet --depth=9999")
|
||||
|
||||
"#{remote_to_fetch}/#{branch_to_fetch}"
|
||||
end
|
||||
|
||||
def find_merge_base(left, right)
|
||||
merge_base = run_git_command("merge-base #{left} #{right}")
|
||||
|
||||
return merge_base unless merge_base.empty?
|
||||
|
||||
say <<~MESSAGE
|
||||
💥 Unfortunately we cannot find the merge-base for #{left} and #{right},
|
||||
💥 and we'll try to fix that in:
|
||||
https://gitlab.com/gitlab-org/gitlab/issues/9120
|
||||
|
||||
💥 Before that, please run this job locally as a workaround:
|
||||
|
||||
./scripts/ee-specific-lines-check
|
||||
|
||||
💥 And paste the result as a discussion to show it to the maintainer.
|
||||
💥 If you have any questions, please ping @godfat to investigate and
|
||||
💥 clarify.
|
||||
MESSAGE
|
||||
|
||||
exit(253)
|
||||
end
|
||||
|
||||
def find_ce_compare_head(ce_fetch_head, ce_fetch_base, ce_merge_base)
|
||||
if git_ancestor?(ce_merge_base, ce_fetch_base)
|
||||
say("CE is ahead of EE, finding backward CE head")
|
||||
find_backward_ce_head(ce_fetch_head, ce_fetch_base, ce_merge_base)
|
||||
else
|
||||
say("CE is behind of EE, finding forward CE head")
|
||||
find_forward_ce_head(ce_merge_base, ce_fetch_head)
|
||||
end
|
||||
end
|
||||
|
||||
def git_ancestor?(ancestor, descendant)
|
||||
run_git_command(
|
||||
"merge-base --is-ancestor #{ancestor} #{descendant} && echo y") == 'y'
|
||||
end
|
||||
|
||||
def find_backward_ce_head(ce_fetch_head, ce_fetch_base, ce_merge_base)
|
||||
if ce_fetch_head.start_with?('canonical-ce') # No specific CE branch
|
||||
say("No CE branch found, using merge base directly")
|
||||
ce_merge_base
|
||||
elsif ce_fetch_base == ce_merge_base # Up-to-date, no rebase needed
|
||||
say("EE is up-to-date with CE, using #{ce_fetch_head} directly")
|
||||
ce_fetch_head
|
||||
else
|
||||
say("Performing rebase to remove commits in CE haven't merged into EE")
|
||||
checkout_and_rebase(ce_merge_base, ce_fetch_base, ce_fetch_head)
|
||||
end
|
||||
end
|
||||
|
||||
def find_forward_ce_head(ce_merge_base, ce_fetch_head)
|
||||
say("Performing merge with CE master for CE branch #{ce_fetch_head}")
|
||||
with_detached_head(ce_fetch_head) do
|
||||
run_git_command("merge #{ce_merge_base} -s recursive -X patience -m 'ee-specific-auto-merge'")
|
||||
|
||||
status = git_status
|
||||
|
||||
if status.porcelain == ''
|
||||
status.head
|
||||
else
|
||||
diff = run_git_command("diff")
|
||||
run_git_command("merge --abort")
|
||||
|
||||
say <<~MESSAGE
|
||||
💥 Git status not clean! This means there's a conflict in
|
||||
💥 #{ce_fetch_head} with canonical-ce/master. Please resolve
|
||||
💥 the conflict from CE master and retry this job.
|
||||
|
||||
⚠️ Git diff:
|
||||
|
||||
#{diff}
|
||||
MESSAGE
|
||||
|
||||
exit(254)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# We rebase onto the commit which is the latest commit presented in both
|
||||
# CE and EE, i.e. ce_merge_base, cutting off commits aren't merged into
|
||||
# EE yet. Here's an example:
|
||||
#
|
||||
# * o: Relevant commits
|
||||
# * x: Irrelevant commits
|
||||
# * !: Commits we want to cut off from CE branch
|
||||
#
|
||||
# ^-> o CE branch (ce_fetch_head)
|
||||
# / (ce_fetch_base)
|
||||
# o -> o -> ! -> x CE master
|
||||
# v (ce_merge_base)
|
||||
# o -> o -> o -> x EE master
|
||||
# \ (ee_fetch_base)
|
||||
# v-> o EE branch
|
||||
#
|
||||
# We want to rebase above into this: (we only change the connection)
|
||||
#
|
||||
# -> - -> o CE branch (ce_fetch_head)
|
||||
# / (ce_fetch_base)
|
||||
# o -> o -> ! -> x CE master
|
||||
# v (ce_merge_base)
|
||||
# o -> o -> o -> x EE master
|
||||
# \ (ee_fetch_base)
|
||||
# v-> o EE branch
|
||||
#
|
||||
# Therefore we rebase onto ce_merge_base, which is based off CE master,
|
||||
# for the CE branch (ce_fetch_head), effective remove the commit marked
|
||||
# as ! in the graph for CE branch. We need to remove it because it's not
|
||||
# merged into EE yet, therefore won't be available in the EE branch.
|
||||
#
|
||||
# After rebase is done, then we could compare against
|
||||
# ce_merge_base..ee_fetch_base along with ce_fetch_head..HEAD (EE branch)
|
||||
# where ce_merge_base..ee_fetch_base is the update-to-date
|
||||
# CE/EE difference and ce_fetch_head..HEAD is the changes we made in
|
||||
# CE and EE branches.
|
||||
def checkout_and_rebase(new_base, old_base, target_head)
|
||||
with_detached_head(target_head) do
|
||||
run_git_command("rebase --onto #{new_base} #{old_base} #{target_head}")
|
||||
|
||||
status = git_status
|
||||
|
||||
if status.porcelain == ''
|
||||
status.head
|
||||
else
|
||||
diff = run_git_command("diff")
|
||||
run_git_command("rebase --abort")
|
||||
|
||||
say <<~MESSAGE
|
||||
💥 Git status is not clean! This means the CE branch has or had a
|
||||
💥 conflict with CE master, and we cannot resolve this in an
|
||||
💥 automatic way.
|
||||
💥
|
||||
💥 Please rebase #{target_head} with CE master.
|
||||
💥
|
||||
💥 For more details, please read:
|
||||
💥 https://gitlab.com/gitlab-org/gitlab/issues/6038#note_86862115
|
||||
💥
|
||||
💥 Git diff:
|
||||
|
||||
#{diff}
|
||||
MESSAGE
|
||||
|
||||
exit(255)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def with_detached_head(target_head)
|
||||
# So that we could switch back. CI sometimes doesn't have the branch,
|
||||
# so we don't use current_branch here
|
||||
head = current_head
|
||||
|
||||
# Use detached HEAD so that we don't update HEAD
|
||||
run_git_command("checkout -f #{target_head}")
|
||||
git_clean
|
||||
|
||||
yield
|
||||
ensure # ensure would still run if we call exit, don't worry
|
||||
# Make sure to switch back
|
||||
run_git_command("checkout -f #{head}")
|
||||
git_clean
|
||||
end
|
||||
|
||||
def head_commit_sha
|
||||
run_git_command("rev-parse HEAD")
|
||||
end
|
||||
|
||||
def git_status
|
||||
GitStatus.new(
|
||||
run_git_command("status --porcelain"),
|
||||
head_commit_sha
|
||||
)
|
||||
end
|
||||
|
||||
def git_clean
|
||||
# We're still seeing errors not ignoring knapsack/ and rspec_flaky/
|
||||
# Instead of waiting that populate over all the branches, we could
|
||||
# just remove untracked files anyway, only on CI of course in case
|
||||
# we're wiping people's data!
|
||||
# See https://gitlab.com/gitlab-org/gitlab/issues/5912
|
||||
# Also see https://gitlab.com/gitlab-org/gitlab/-/jobs/68194333
|
||||
run_git_command('clean -fd') if ENV['CI']
|
||||
end
|
||||
|
||||
def remove_remotes
|
||||
run_git_command(
|
||||
"remote remove canonical-ee",
|
||||
"remote remove canonical-ce",
|
||||
"remote remove target-ce")
|
||||
end
|
||||
|
||||
def updated_diff_numstat(from, to)
|
||||
scan_diff_numstat(
|
||||
run_git_command("diff #{from}..#{to} --numstat -- . ':!ee' ':!qa/qa/ee' ':!qa/qa/ee.rb' ':!qa/qa/specs/features/ee'"))
|
||||
end
|
||||
|
||||
def find_remote_ce_branch
|
||||
branch_to_fetch = matching_ce_refs.first
|
||||
|
||||
if branch_to_fetch
|
||||
say "💪 We found the branch '#{branch_to_fetch}' in the #{ce_repo_url} repository. We will fetch it."
|
||||
|
||||
run_git_command("remote add target-ce #{ce_repo_url}")
|
||||
|
||||
['target-ce', branch_to_fetch]
|
||||
else
|
||||
say <<~MESSAGE
|
||||
⚠️ We did not find a branch that would match the current '#{current_branch}' branch in the #{ce_repo_url} repository. We will fetch 'master' instead.
|
||||
ℹ️ If you have a CE branch for the current branch, make sure that its name includes '#{minimal_ce_branch_name}'.
|
||||
MESSAGE
|
||||
|
||||
%w[canonical-ce master]
|
||||
end
|
||||
end
|
||||
|
||||
def ce_repo_url
|
||||
@ce_repo_url ||=
|
||||
begin
|
||||
repo_url = ENV.fetch('CI_REPOSITORY_URL', 'https://gitlab.com/gitlab-org/gitlab-foss.git')
|
||||
# This workaround can be removed once we rename the dev CE project
|
||||
# https://gitlab.com/gitlab-org/gitlab-foss/issues/59107
|
||||
project_name = repo_url =~ /dev\.gitlab\.org/ ? 'gitlabhq' : 'gitlab-ce'
|
||||
|
||||
repo_url.sub('gitlab-ee', project_name)
|
||||
end
|
||||
end
|
||||
|
||||
def current_head
|
||||
@current_head ||= ENV.fetch('CI_COMMIT_SHA', current_branch)
|
||||
end
|
||||
|
||||
def current_branch
|
||||
@current_branch ||= ENV.fetch('CI_COMMIT_REF_NAME', `git rev-parse --abbrev-ref HEAD`).strip
|
||||
end
|
||||
|
||||
def minimal_ce_branch_name
|
||||
@minimal_ce_branch_name ||= current_branch.sub(/(\Aee\-|\-ee\z)/, '')
|
||||
end
|
||||
|
||||
def matching_ce_refs
|
||||
@matching_ce_refs ||=
|
||||
run_git_command("ls-remote #{ce_repo_url} \"*#{minimal_ce_branch_name}*\"")
|
||||
.scan(%r{(?<=refs/heads/|refs/tags/).+})
|
||||
.select { |branch| branch.match?(/\b#{minimal_ce_branch_name}\b/i) }
|
||||
.sort_by(&:size)
|
||||
end
|
||||
|
||||
def scan_diff_numstat(numstat)
|
||||
numstat.scan(/(\d+)\s+(\d+)\s+(.+)/)
|
||||
.each_with_object(Hash.new(0)) do |(added, deleted, file), result|
|
||||
result[file] = added.to_i + deleted.to_i
|
||||
end
|
||||
end
|
||||
|
||||
def run_git_command(*commands)
|
||||
cmds = commands.map { |cmd| "git #{cmd}" }
|
||||
|
||||
output = run_command(*cmds)
|
||||
|
||||
if commands.size == 1
|
||||
output.first
|
||||
else
|
||||
output
|
||||
end
|
||||
end
|
||||
|
||||
def run_command(*commands)
|
||||
commands.map do |cmd|
|
||||
warn "=> Running `#{cmd}`"
|
||||
|
||||
`#{cmd}`.strip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if $0 == __FILE__
|
||||
require 'rspec/autorun'
|
||||
|
||||
RSpec.describe EESpecificCheck do
|
||||
subject { Class.new { include EESpecificCheck }.new }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:warn)
|
||||
|
||||
EESpecificCheck.private_instance_methods.each do |name|
|
||||
subject.class.__send__(:public, name) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
end
|
||||
|
||||
describe '.run_git_command' do
|
||||
it 'returns the single output when there is a single command' do
|
||||
output = subject.run_git_command('status')
|
||||
|
||||
expect(output).to be_kind_of(String)
|
||||
expect(subject).to have_received(:warn).with(/git status/)
|
||||
end
|
||||
|
||||
it 'returns an array of output for more commands' do
|
||||
output = subject.run_git_command('status', 'help')
|
||||
|
||||
expect(output).to all(be_a(String))
|
||||
expect(subject).to have_received(:warn).with(/git status/)
|
||||
expect(subject).to have_received(:warn).with(/git help/)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.find_merge_base' do
|
||||
context 'when it cannot find the merge base' do
|
||||
before do
|
||||
allow(subject).to receive(:say)
|
||||
allow(subject).to receive(:exit)
|
||||
|
||||
expect(subject).to receive(:run_git_command).and_return('')
|
||||
end
|
||||
|
||||
it 'calls exit(253) to fail the job and ask run it locally' do
|
||||
subject.find_merge_base('master', 'HEAD')
|
||||
|
||||
expect(subject).to have_received(:say)
|
||||
.with(Regexp.union('./scripts/ee-specific-lines-check'))
|
||||
expect(subject).to have_received(:exit)
|
||||
.with(253)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it found the merge base' do
|
||||
before do
|
||||
expect(subject).to receive(:run_git_command).and_return('deadbeef')
|
||||
end
|
||||
|
||||
it 'returns the found merge base' do
|
||||
output = subject.find_merge_base('master', 'HEAD')
|
||||
|
||||
expect(output).to eq('deadbeef')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.matching_ce_refs' do
|
||||
before do
|
||||
expect(subject).to receive(:current_branch).and_return(ee_branch)
|
||||
expect(subject).to receive(:run_git_command)
|
||||
.and_return(ls_remote_output)
|
||||
end
|
||||
|
||||
describe 'simple cases' do
|
||||
let(:ls_remote_output) do
|
||||
<<~OUTPUT
|
||||
d6602ec5194c87b0fc87103ca4d67251c76f233a\trefs/tags/v9
|
||||
f25a265a342aed6041ab0cc484224d9ca54b6f41\trefs/tags/v9.12
|
||||
c5db5456ae3b0873fc659c19fafdde22313cc441\trefs/tags/v9.123
|
||||
0918385dbd9656cab0d1d81ba7453d49bbc16250\trefs/heads/v9.x
|
||||
28862662b749fe981386814e2dba87b0e72c1eab\trefs/remotes/remote_mirror_3059/v9-to-fix-http-case-problems
|
||||
5e3496802098c86050c5b463507f3a68a83a9f02\trefs/remotes/remote_mirror_3059/29036-use-slack-service-v9
|
||||
OUTPUT
|
||||
end
|
||||
|
||||
context 'with a ee- prefix' do
|
||||
let(:ee_branch) { 'ee-v9' }
|
||||
|
||||
it 'sorts by matching size' do
|
||||
expect(subject.matching_ce_refs).to eq(%w[v9 v9.x v9.12 v9.123])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a -ee suffix' do
|
||||
let(:ee_branch) { 'v9-ee' }
|
||||
|
||||
it 'sorts by matching size' do
|
||||
expect(subject.matching_ce_refs).to eq(%w[v9 v9.x v9.12 v9.123])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with ambiguous branch name' do
|
||||
let(:ls_remote_output) do
|
||||
<<~OUTPUT
|
||||
954d7119384c9f2a3c862bac97beb641eb8755d6\trefs/heads/feature/sm/35954-expand-kubernetesservice-to-use-username-password
|
||||
954d7119384c9f2a3c862bac97beb641eb8755d6\trefs/heads/ce-to-ee-231
|
||||
954d7119384c9f2a3c862bac97beb641eb8755d6\trefs/heads/ce-to-ee-2
|
||||
954d7119384c9f2a3c862bac97beb641eb8755d6\trefs/heads/ce-to-1
|
||||
954d7119384c9f2a3c862bac97beb641eb8755d6\trefs/heads/ee-to-ce-123
|
||||
954d7119384c9f2a3c862bac97beb641eb8755d6\trefs/heads/ee-to-ce-12
|
||||
954d7119384c9f2a3c862bac97beb641eb8755d6\trefs/heads/to-ce-1
|
||||
28862662b749fe981386814e2dba87b0e72c1eab\trefs/remotes/remote_mirror_3059/27056-upgrade-vue-resource-to-1-0-3-to-fix-http-case-problems
|
||||
5e3496802098c86050c5b463507f3a68a83a9f02\trefs/remotes/remote_mirror_3059/29036-use-slack-service-to-notify-of-failed-pipelines
|
||||
OUTPUT
|
||||
end
|
||||
|
||||
context 'with a ee- prefix' do
|
||||
let(:ee_branch) { 'ee-to-ce' }
|
||||
let(:minimal_ce_branch) { 'to-ce' }
|
||||
|
||||
it 'sorts by matching size' do
|
||||
expect(subject.matching_ce_refs).to eq(%w[to-ce-1 ee-to-ce-12 ee-to-ce-123])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a -ee suffix' do
|
||||
let(:ee_branch) { 'ce-to-ee' }
|
||||
let(:minimal_ce_branch) { 'ce-to' }
|
||||
|
||||
it 'sorts by matching size' do
|
||||
expect(subject.matching_ce_refs).to eq(%w[ce-to-1 ce-to-ee-2 ce-to-ee-231])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -37,10 +37,6 @@ tasks = [
|
|||
%w[scripts/lint-rugged]
|
||||
]
|
||||
|
||||
if Gitlab.ee?
|
||||
tasks.unshift(%w[ruby -rbundler/setup scripts/ee_specific_check/ee_specific_check.rb])
|
||||
end
|
||||
|
||||
static_analysis = Gitlab::Popen::Runner.new
|
||||
|
||||
static_analysis.run(tasks) do |cmd, &run|
|
||||
|
|
|
@ -10,6 +10,8 @@ describe Projects::Registry::TagsController do
|
|||
create(:container_repository, name: 'image', project: project)
|
||||
end
|
||||
|
||||
let(:service) { double('service') }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
stub_container_registry_config(enabled: true)
|
||||
|
@ -84,17 +86,17 @@ describe Projects::Registry::TagsController do
|
|||
|
||||
context 'when there is matching tag present' do
|
||||
before do
|
||||
stub_container_registry_tags(repository: repository.path, tags: %w[rc1 test.])
|
||||
stub_container_registry_tags(repository: repository.path, tags: %w[rc1], with_manifest: true)
|
||||
end
|
||||
|
||||
it 'makes it possible to delete regular tag' do
|
||||
expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete)
|
||||
expect_delete_tags(%w[rc1])
|
||||
|
||||
destroy_tag('rc1')
|
||||
end
|
||||
|
||||
it 'makes it possible to delete a tag that ends with a dot' do
|
||||
expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete)
|
||||
expect_delete_tags(%w[test.])
|
||||
|
||||
destroy_tag('test.')
|
||||
end
|
||||
|
@ -125,11 +127,12 @@ describe Projects::Registry::TagsController do
|
|||
stub_container_registry_tags(repository: repository.path, tags: %w[rc1 test.])
|
||||
end
|
||||
|
||||
it 'makes it possible to delete tags in bulk' do
|
||||
allow_any_instance_of(ContainerRegistry::Tag).to receive(:delete) { |*args| ContainerRegistry::Tag.delete(*args) }
|
||||
expect(ContainerRegistry::Tag).to receive(:delete).exactly(2).times
|
||||
let(:tags) { %w[tc1 test.] }
|
||||
|
||||
bulk_destroy_tags(['rc1', 'test.'])
|
||||
it 'makes it possible to delete tags in bulk' do
|
||||
expect_delete_tags(tags)
|
||||
|
||||
bulk_destroy_tags(tags)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -146,4 +149,9 @@ describe Projects::Registry::TagsController do
|
|||
format: :json
|
||||
end
|
||||
end
|
||||
|
||||
def expect_delete_tags(tags, status = :success)
|
||||
expect(service).to receive(:execute).with(repository) { { status: status } }
|
||||
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(repository.project, user, tags: tags) { service }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -53,7 +53,9 @@ describe 'Container Registry', :js do
|
|||
find('.js-toggle-repo').click
|
||||
wait_for_requests
|
||||
|
||||
expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete).and_return(true)
|
||||
service = double('service')
|
||||
expect(service).to receive(:execute).with(container_repository) { { status: :success } }
|
||||
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(container_repository.project, user, tags: ['latest']) { service }
|
||||
|
||||
click_on(class: 'js-delete-registry-row', visible: false)
|
||||
expect(find('.modal .modal-title')).to have_content 'Remove image'
|
||||
|
|
|
@ -470,9 +470,6 @@ describe 'Issues' do
|
|||
expect(page).to have_content 'None'
|
||||
end
|
||||
|
||||
# wait_for_requests does not work with vue-resource at the moment
|
||||
sleep 1
|
||||
|
||||
expect(issue.reload.assignees).to be_empty
|
||||
end
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const headersInterceptor = (request, next) => {
|
||||
next(response => {
|
||||
const headers = {};
|
||||
response.headers.forEach((value, key) => {
|
||||
headers[key] = value;
|
||||
});
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
response.headers = headers;
|
||||
});
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const headersInterceptor = (request, next) => {
|
||||
next(response => {
|
||||
const headers = {};
|
||||
response.headers.forEach((value, key) => {
|
||||
headers[key] = value;
|
||||
});
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
response.headers = headers;
|
||||
});
|
||||
};
|
|
@ -7,7 +7,6 @@ import 'core-js/features/set-immediate';
|
|||
import 'vendor/jasmine-jquery';
|
||||
import '~/commons';
|
||||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
import jasmineDiff from 'jasmine-diff';
|
||||
import { config as testUtilsConfig } from '@vue/test-utils';
|
||||
|
@ -46,7 +45,6 @@ Vue.config.errorHandler = function(err) {
|
|||
fail(err);
|
||||
};
|
||||
|
||||
Vue.use(VueResource);
|
||||
Vue.use(Translate);
|
||||
|
||||
// enable test fixtures
|
||||
|
@ -102,13 +100,6 @@ afterEach(__rewire_reset_all__); // eslint-disable-line
|
|||
// to run our unit tests.
|
||||
beforeEach(done => done());
|
||||
|
||||
const builtinVueHttpInterceptors = Vue.http.interceptors.slice();
|
||||
|
||||
beforeEach(() => {
|
||||
// restore interceptors so we have no remaining ones from previous tests
|
||||
Vue.http.interceptors = builtinVueHttpInterceptors.slice();
|
||||
});
|
||||
|
||||
let longRunningTestTimeoutHandle;
|
||||
|
||||
beforeEach(done => {
|
||||
|
|
|
@ -73,4 +73,69 @@ describe ContainerRegistry::Client do
|
|||
expect(response).to eq('Successfully redirected')
|
||||
end
|
||||
end
|
||||
|
||||
def stub_upload(path, content, digest, status = 200)
|
||||
stub_request(:post, "http://container-registry/v2/#{path}/blobs/uploads/")
|
||||
.to_return(status: status, body: "", headers: { 'location' => 'http://container-registry/next_upload?id=someid' })
|
||||
|
||||
stub_request(:put, "http://container-registry/next_upload?digest=#{digest}&id=someid")
|
||||
.with(body: content)
|
||||
.to_return(status: status, body: "", headers: {})
|
||||
end
|
||||
|
||||
describe '#upload_blob' do
|
||||
subject { client.upload_blob('path', 'content', 'sha256:123') }
|
||||
|
||||
context 'with successful uploads' do
|
||||
it 'starts the upload and posts the blob' do
|
||||
stub_upload('path', 'content', 'sha256:123')
|
||||
|
||||
expect(subject).to be_success
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a failed upload' do
|
||||
before do
|
||||
stub_upload('path', 'content', 'sha256:123', 400)
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
expect(subject).to be nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#generate_empty_manifest' do
|
||||
subject { client.generate_empty_manifest('path') }
|
||||
|
||||
let(:result_manifest) do
|
||||
{
|
||||
schemaVersion: 2,
|
||||
mediaType: 'application/vnd.docker.distribution.manifest.v2+json',
|
||||
config: {
|
||||
mediaType: 'application/vnd.docker.container.image.v1+json',
|
||||
size: 21,
|
||||
digest: 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'uploads a random image and returns the manifest' do
|
||||
stub_upload('path', "{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
|
||||
|
||||
expect(subject).to eq(result_manifest)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#put_tag' do
|
||||
subject { client.put_tag('path', 'tagA', { foo: :bar }) }
|
||||
|
||||
it 'uploads the manifest and returns the digest' do
|
||||
stub_request(:put, "http://container-registry/v2/path/manifests/tagA")
|
||||
.with(body: "{\n \"foo\": \"bar\"\n}")
|
||||
.to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:123' })
|
||||
|
||||
expect(subject).to eq 'sha256:123'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -179,7 +179,7 @@ describe ContainerRegistry::Tag do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#delete' do
|
||||
describe '#unsafe_delete' do
|
||||
before do
|
||||
stub_request(:delete, 'http://registry.gitlab/v2/group/test/manifests/sha256:digest')
|
||||
.with(headers: headers)
|
||||
|
@ -187,7 +187,7 @@ describe ContainerRegistry::Tag do
|
|||
end
|
||||
|
||||
it 'correctly deletes the tag' do
|
||||
expect(tag.delete).to be_truthy
|
||||
expect(tag.unsafe_delete).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -150,7 +150,7 @@ describe API::ProjectContainerRepositories do
|
|||
expect(response).to have_gitlab_http_status(:accepted)
|
||||
end
|
||||
|
||||
context 'called multiple times in one hour' do
|
||||
context 'called multiple times in one hour', :clean_gitlab_redis_shared_state do
|
||||
it 'returns 400 with an error message' do
|
||||
stub_exclusive_lease_taken(lease_key, timeout: 1.hour)
|
||||
subject
|
||||
|
@ -202,6 +202,8 @@ describe API::ProjectContainerRepositories do
|
|||
end
|
||||
|
||||
describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do
|
||||
let(:service) { double('service') }
|
||||
|
||||
subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) }
|
||||
|
||||
it_behaves_like 'rejected container repository access', :reporter, :forbidden
|
||||
|
@ -210,18 +212,34 @@ describe API::ProjectContainerRepositories do
|
|||
context 'for developer' do
|
||||
let(:api_user) { developer }
|
||||
|
||||
before do
|
||||
stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true)
|
||||
context 'when there are multiple tags' do
|
||||
before do
|
||||
stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA rootB), with_manifest: true)
|
||||
end
|
||||
|
||||
it 'properly removes tag' do
|
||||
expect(service).to receive(:execute).with(root_repository) { { status: :success } }
|
||||
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(root_repository.project, api_user, tags: %w[rootA]) { service }
|
||||
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
it 'properly removes tag' do
|
||||
expect_any_instance_of(ContainerRegistry::Client)
|
||||
.to receive(:delete_repository_tag).with(root_repository.path,
|
||||
'sha256:4c8e63ca4cb663ce6c688cb06f1c372b088dac5b6d7ad7d49cd620d85cf72a15')
|
||||
context 'when there\'s only one tag' do
|
||||
before do
|
||||
stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true)
|
||||
end
|
||||
|
||||
subject
|
||||
it 'properly removes tag' do
|
||||
expect(service).to receive(:execute).with(root_repository) { { status: :success } }
|
||||
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(root_repository.project, api_user, tags: %w[rootA]) { service }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::ContainerRepository::DeleteTagsService do
|
||||
set(:user) { create(:user) }
|
||||
set(:project) { create(:project, :private) }
|
||||
set(:repository) { create(:container_repository, :root, project: project) }
|
||||
|
||||
let(:params) { { tags: tags } }
|
||||
let(:service) { described_class.new(project, user, params) }
|
||||
|
||||
before do
|
||||
stub_container_registry_config(enabled: true,
|
||||
api_url: 'http://registry.gitlab',
|
||||
host_port: 'registry.gitlab')
|
||||
|
||||
stub_container_registry_tags(
|
||||
repository: repository.path,
|
||||
tags: %w(latest A Ba Bb C D E))
|
||||
|
||||
stub_tag_digest('latest', 'sha256:configA')
|
||||
stub_tag_digest('A', 'sha256:configA')
|
||||
stub_tag_digest('Ba', 'sha256:configB')
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
let(:tags) { %w[A] }
|
||||
subject { service.execute(repository) }
|
||||
|
||||
context 'without permissions' do
|
||||
it { is_expected.to include(status: :error) }
|
||||
end
|
||||
|
||||
context 'with permissions' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
context 'when no params are specified' do
|
||||
let(:params) { {} }
|
||||
|
||||
it 'does not remove anything' do
|
||||
expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag)
|
||||
|
||||
is_expected.to include(status: :error)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with empty tags' do
|
||||
let(:tags) { [] }
|
||||
|
||||
it 'does not remove anything' do
|
||||
expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag)
|
||||
|
||||
is_expected.to include(status: :error)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with dummy tags disabled' do
|
||||
let(:tags) { %w[A Ba] }
|
||||
|
||||
before do
|
||||
stub_feature_flags(container_registry_smart_delete: false)
|
||||
end
|
||||
|
||||
it 'deletes tags one by one' do
|
||||
expect_delete_tag('sha256:configA')
|
||||
expect_delete_tag('sha256:configB')
|
||||
is_expected.to include(status: :success)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with dummy tags enabled' do
|
||||
let(:tags) { %w[A Ba] }
|
||||
|
||||
it 'deletes the tags using a dummy image' do
|
||||
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
|
||||
|
||||
stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/A")
|
||||
.to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
|
||||
|
||||
stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/Ba")
|
||||
.to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
|
||||
|
||||
expect_delete_tag('sha256:dummy')
|
||||
|
||||
is_expected.to include(status: :success)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stub_tag_digest(tag, digest)
|
||||
stub_request(:head, "http://registry.gitlab/v2/#{repository.path}/manifests/#{tag}")
|
||||
.to_return(status: 200, body: "", headers: { 'docker-content-digest' => digest })
|
||||
end
|
||||
|
||||
def stub_digest_config(digest, created_at)
|
||||
allow_any_instance_of(ContainerRegistry::Client)
|
||||
.to receive(:blob)
|
||||
.with(repository.path, digest, nil) do
|
||||
{ 'created' => created_at.to_datetime.rfc3339 }.to_json if created_at
|
||||
end
|
||||
end
|
||||
|
||||
def stub_upload(content, digest)
|
||||
expect_any_instance_of(ContainerRegistry::Client)
|
||||
.to receive(:upload_blob)
|
||||
.with(repository.path, content, digest) { double(success?: true ) }
|
||||
end
|
||||
|
||||
def expect_delete_tag(digest)
|
||||
expect_any_instance_of(ContainerRegistry::Client)
|
||||
.to receive(:delete_repository_tag)
|
||||
.with(repository.path, digest) { true }
|
||||
end
|
||||
end
|
|
@ -49,11 +49,11 @@ module WaitForRequests
|
|||
return true unless javascript_test?
|
||||
|
||||
finished_all_ajax_requests? &&
|
||||
finished_all_vue_resource_requests?
|
||||
finished_all_axios_requests?
|
||||
end
|
||||
|
||||
def finished_all_vue_resource_requests?
|
||||
Capybara.page.evaluate_script('window.activeVueResources || 0').zero?
|
||||
def finished_all_axios_requests?
|
||||
Capybara.page.evaluate_script('window.pendingRequests || 0').zero?
|
||||
end
|
||||
|
||||
def finished_all_ajax_requests?
|
||||
|
|
|
@ -1131,7 +1131,6 @@ vue-apollo,3.0.0-beta.25,ISC
|
|||
vue-functional-data-merge,2.0.6,MIT
|
||||
vue-hot-reload-api,2.3.0,MIT
|
||||
vue-loader,15.4.2,MIT
|
||||
vue-resource,1.5.0,MIT
|
||||
vue-router,3.0.1,MIT
|
||||
vue-style-loader,4.1.0,MIT
|
||||
vue-template-compiler,2.5.17,MIT
|
||||
|
|
|
193
yarn.lock
193
yarn.lock
|
@ -1191,11 +1191,6 @@
|
|||
consola "^2.3.0"
|
||||
node-fetch "^2.3.0"
|
||||
|
||||
"@sindresorhus/is@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
|
||||
integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
|
||||
|
||||
"@types/anymatch@*":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.0.tgz#d1d55958d1fccc5527d4aba29fc9c4b942f563ff"
|
||||
|
@ -2517,19 +2512,6 @@ cache-loader@^2.0.1:
|
|||
normalize-path "^3.0.0"
|
||||
schema-utils "^1.0.0"
|
||||
|
||||
cacheable-request@^2.1.1:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d"
|
||||
integrity sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=
|
||||
dependencies:
|
||||
clone-response "1.0.2"
|
||||
get-stream "3.0.0"
|
||||
http-cache-semantics "3.8.1"
|
||||
keyv "3.0.0"
|
||||
lowercase-keys "1.0.0"
|
||||
normalize-url "2.0.1"
|
||||
responselike "1.0.2"
|
||||
|
||||
cached-path-relative@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.2.tgz#a13df4196d26776220cc3356eb147a52dba2c6db"
|
||||
|
@ -2895,13 +2877,6 @@ clone-regexp@^2.1.0:
|
|||
dependencies:
|
||||
is-regexp "^2.0.0"
|
||||
|
||||
clone-response@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
|
||||
integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
|
||||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
clone-stats@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680"
|
||||
|
@ -3894,13 +3869,6 @@ decode-uri-component@^0.2.0:
|
|||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
|
||||
|
||||
decompress-response@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
|
||||
integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
|
||||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
deep-equal@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
||||
|
@ -5306,7 +5274,7 @@ fresh@0.5.2:
|
|||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
|
||||
|
||||
from2@^2.1.0, from2@^2.1.1:
|
||||
from2@^2.1.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
|
||||
integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=
|
||||
|
@ -5442,7 +5410,7 @@ get-stdin@~5.0.1:
|
|||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
|
||||
integrity sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=
|
||||
|
||||
get-stream@3.0.0, get-stream@^3.0.0:
|
||||
get-stream@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
|
||||
integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
|
||||
|
@ -5722,29 +5690,6 @@ got@^6.7.1:
|
|||
unzip-response "^2.0.1"
|
||||
url-parse-lax "^1.0.0"
|
||||
|
||||
got@^8.0.3:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/got/-/got-8.3.0.tgz#6ba26e75f8a6cc4c6b3eb1fe7ce4fec7abac8533"
|
||||
integrity sha512-kBNy/S2CGwrYgDSec5KTWGKUvupwkkTVAjIsVFF2shXO13xpZdFP4d4kxa//CLX2tN/rV0aYwK8vY6UKWGn2vQ==
|
||||
dependencies:
|
||||
"@sindresorhus/is" "^0.7.0"
|
||||
cacheable-request "^2.1.1"
|
||||
decompress-response "^3.3.0"
|
||||
duplexer3 "^0.1.4"
|
||||
get-stream "^3.0.0"
|
||||
into-stream "^3.1.0"
|
||||
is-retry-allowed "^1.1.0"
|
||||
isurl "^1.0.0-alpha5"
|
||||
lowercase-keys "^1.0.0"
|
||||
mimic-response "^1.0.0"
|
||||
p-cancelable "^0.4.0"
|
||||
p-timeout "^2.0.1"
|
||||
pify "^3.0.0"
|
||||
safe-buffer "^5.1.1"
|
||||
timed-out "^4.0.1"
|
||||
url-parse-lax "^3.0.0"
|
||||
url-to-options "^1.0.1"
|
||||
|
||||
graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b"
|
||||
|
@ -5840,23 +5785,11 @@ has-flag@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
has-symbol-support-x@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.3.0.tgz#588bd6927eaa0e296afae24160659167fc2be4f8"
|
||||
integrity sha512-kLtS+N9qwz+Buc6TUfcW5iGb59hLLr5qfxTACi/0uGpH1u5NMNWsdU57KoYRBywvPykeRmu5qfB5x0chpDSWlg==
|
||||
|
||||
has-symbols@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
|
||||
integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=
|
||||
|
||||
has-to-string-tag-x@^1.2.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.3.0.tgz#78e3d98c3c0ec9413e970eb8d766249a1e13058f"
|
||||
integrity sha512-Fu9Nwv8/VNJMvKjkldzXHO+yeX+TCelwUQ9dGW2LrAfHfHi6zVqQt+Qjilf0qGHvpl6Fap6o8aDhWhMt5hY/1g==
|
||||
dependencies:
|
||||
has-symbol-support-x "^1.3.0"
|
||||
|
||||
has-unicode@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
||||
|
@ -6059,11 +5992,6 @@ htmlparser2@^3.10.0, htmlparser2@^3.9.0:
|
|||
inherits "^2.0.1"
|
||||
readable-stream "^3.0.6"
|
||||
|
||||
http-cache-semantics@3.8.1:
|
||||
version "3.8.1"
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
|
||||
integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==
|
||||
|
||||
http-deceiver@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
|
||||
|
@ -6303,14 +6231,6 @@ interpret@^1.0.0, interpret@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
|
||||
integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=
|
||||
|
||||
into-stream@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6"
|
||||
integrity sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=
|
||||
dependencies:
|
||||
from2 "^2.1.1"
|
||||
p-is-promise "^1.1.0"
|
||||
|
||||
invariant@^2.2.2, invariant@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||
|
@ -6578,11 +6498,6 @@ is-obj@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
|
||||
integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
|
||||
|
||||
is-object@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
|
||||
integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA=
|
||||
|
||||
is-path-cwd@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
|
||||
|
@ -6653,7 +6568,7 @@ is-resolvable@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
|
||||
integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==
|
||||
|
||||
is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0:
|
||||
is-retry-allowed@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
|
||||
integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=
|
||||
|
@ -6836,14 +6751,6 @@ istextorbinary@^2.2.1:
|
|||
editions "^1.3.3"
|
||||
textextensions "2"
|
||||
|
||||
isurl@^1.0.0-alpha5:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67"
|
||||
integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==
|
||||
dependencies:
|
||||
has-to-string-tag-x "^1.2.0"
|
||||
is-object "^1.0.1"
|
||||
|
||||
iterall@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7"
|
||||
|
@ -7386,11 +7293,6 @@ jsesc@~0.5.0:
|
|||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
|
||||
integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
|
||||
|
||||
json-buffer@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
|
||||
integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
|
||||
|
||||
json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
|
||||
|
@ -7569,13 +7471,6 @@ katex@^0.10.0:
|
|||
dependencies:
|
||||
commander "^2.16.0"
|
||||
|
||||
keyv@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373"
|
||||
integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==
|
||||
dependencies:
|
||||
json-buffer "3.0.0"
|
||||
|
||||
killable@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.0.tgz#da8b84bd47de5395878f95d64d02f2449fe05e6b"
|
||||
|
@ -7885,7 +7780,7 @@ lower-case@^1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
|
||||
integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
|
||||
|
||||
lowercase-keys@1.0.0, lowercase-keys@^1.0.0:
|
||||
lowercase-keys@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
|
||||
integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=
|
||||
|
@ -8285,11 +8180,6 @@ mimic-fn@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||
|
||||
mimic-response@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e"
|
||||
integrity sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=
|
||||
|
||||
minify@^4.1.1:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minify/-/minify-4.1.2.tgz#88755f4faa5f7ab6d0c64fdd659aa34ea658f180"
|
||||
|
@ -8733,15 +8623,6 @@ normalize-selector@^0.2.0:
|
|||
resolved "https://registry.yarnpkg.com/normalize-selector/-/normalize-selector-0.2.0.tgz#d0b145eb691189c63a78d201dc4fdb1293ef0c03"
|
||||
integrity sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=
|
||||
|
||||
normalize-url@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6"
|
||||
integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==
|
||||
dependencies:
|
||||
prepend-http "^2.0.0"
|
||||
query-string "^5.0.1"
|
||||
sort-keys "^2.0.0"
|
||||
|
||||
normalize-url@^1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
|
||||
|
@ -9003,11 +8884,6 @@ osenv@0, osenv@^0.1.4:
|
|||
os-homedir "^1.0.0"
|
||||
os-tmpdir "^1.0.0"
|
||||
|
||||
p-cancelable@^0.4.0:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0"
|
||||
integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==
|
||||
|
||||
p-defer@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
|
||||
|
@ -9025,11 +8901,6 @@ p-finally@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
|
||||
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
|
||||
|
||||
p-is-promise@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
|
||||
integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=
|
||||
|
||||
p-is-promise@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
|
||||
|
@ -9073,13 +8944,6 @@ p-reduce@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa"
|
||||
integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=
|
||||
|
||||
p-timeout@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038"
|
||||
integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==
|
||||
dependencies:
|
||||
p-finally "^1.0.0"
|
||||
|
||||
p-try@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
|
||||
|
@ -9619,11 +9483,6 @@ prepend-http@^1.0.0, prepend-http@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
||||
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
||||
|
||||
prepend-http@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
||||
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
|
||||
|
||||
prettier@1.16.3:
|
||||
version "1.16.3"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.3.tgz#8c62168453badef702f34b45b6ee899574a6a65d"
|
||||
|
@ -9924,15 +9783,6 @@ query-string@^4.1.0:
|
|||
object-assign "^4.1.0"
|
||||
strict-uri-encode "^1.0.0"
|
||||
|
||||
query-string@^5.0.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
|
||||
integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==
|
||||
dependencies:
|
||||
decode-uri-component "^0.2.0"
|
||||
object-assign "^4.1.0"
|
||||
strict-uri-encode "^1.0.0"
|
||||
|
||||
querystring-es3@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
||||
|
@ -10598,13 +10448,6 @@ resolve@1.x, resolve@^1.1.3, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0, re
|
|||
dependencies:
|
||||
path-parse "^1.0.6"
|
||||
|
||||
responselike@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
|
||||
integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=
|
||||
dependencies:
|
||||
lowercase-keys "^1.0.0"
|
||||
|
||||
restore-cursor@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
|
||||
|
@ -11115,13 +10958,6 @@ sort-keys@^1.0.0:
|
|||
dependencies:
|
||||
is-plain-obj "^1.0.0"
|
||||
|
||||
sort-keys@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"
|
||||
integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=
|
||||
dependencies:
|
||||
is-plain-obj "^1.0.0"
|
||||
|
||||
sortablejs@^1.10.0, sortablejs@^1.9.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.10.0.tgz#0ebc054acff2486569194a2f975b2b145dd5e7d6"
|
||||
|
@ -11820,7 +11656,7 @@ timeago.js@^3.0.2:
|
|||
dependencies:
|
||||
"@types/jquery" "^2.0.40"
|
||||
|
||||
timed-out@^4.0.0, timed-out@^4.0.1:
|
||||
timed-out@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
|
||||
integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
|
||||
|
@ -12431,13 +12267,6 @@ url-parse-lax@^1.0.0:
|
|||
dependencies:
|
||||
prepend-http "^1.0.1"
|
||||
|
||||
url-parse-lax@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
|
||||
integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=
|
||||
dependencies:
|
||||
prepend-http "^2.0.0"
|
||||
|
||||
url-parse@^1.4.3:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8"
|
||||
|
@ -12451,11 +12280,6 @@ url-search-params-polyfill@^5.0.0:
|
|||
resolved "https://registry.yarnpkg.com/url-search-params-polyfill/-/url-search-params-polyfill-5.0.0.tgz#09b98337c89dcf6c6a6a0bfeb096f6ba83b7526b"
|
||||
integrity sha512-+SCD22QJp4UnqPOI5UTTR0Ljuh8cHbjEf1lIiZrZ8nHTlTixqwVsVQTSfk5vrmDz7N09/Y+ka5jQr0ff35FnQQ==
|
||||
|
||||
url-to-options@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
|
||||
integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=
|
||||
|
||||
url@0.10.3:
|
||||
version "0.10.3"
|
||||
resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64"
|
||||
|
@ -12750,13 +12574,6 @@ vue-loader@^15.4.2, vue-loader@^15.7.0:
|
|||
vue-hot-reload-api "^2.3.0"
|
||||
vue-style-loader "^4.1.0"
|
||||
|
||||
vue-resource@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-1.5.1.tgz#0f3d685e3254d21800bebd966edcf56c34b3b6e4"
|
||||
integrity sha512-o6V4wNgeqP+9v9b2bPXrr20CGNQPEXjpbUWdZWq9GJhqVeAGcYoeTtn/D4q059ZiyN0DIrDv/ADrQUmlUQcsmg==
|
||||
dependencies:
|
||||
got "^8.0.3"
|
||||
|
||||
vue-router@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.2.tgz#dedc67afe6c4e2bc25682c8b1c2a8c0d7c7e56be"
|
||||
|
|
Loading…
Reference in New Issue