提交 6cd5b7db 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 b3e0658c
......@@ -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,
......
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:
......
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)
......
# 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
---
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
---
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
status :ok
result = ::Projects::ContainerRepository::DeleteTagsService
.new(repository.project, current_user, tags: [declared_params[:tag_name]])
.execute(repository)
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)
......
# 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
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
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} ***"
......
# 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
let(:tags) { %w[tc1 test.] }
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
expect_delete_tags(tags)
bulk_destroy_tags(['rc1', 'test.'])
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
......
// 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;
});
};
// 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
......
# 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
......
......@@ -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"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册