Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
de5a3e7e69
commit
d939f38b0f
|
|
@ -71,7 +71,6 @@ RSpec/FactoryBot/AvoidCreate:
|
|||
- 'ee/spec/helpers/security_helper_spec.rb'
|
||||
- 'ee/spec/helpers/subscriptions_helper_spec.rb'
|
||||
- 'ee/spec/helpers/timeboxes_helper_spec.rb'
|
||||
- 'ee/spec/helpers/trial_status_widget_helper_spec.rb'
|
||||
- 'ee/spec/helpers/trials_helper_spec.rb'
|
||||
- 'ee/spec/helpers/users/identity_verification_helper_spec.rb'
|
||||
- 'ee/spec/helpers/users_helper_spec.rb'
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
ISSUABLE_CHANGE_LABEL,
|
||||
ISSUABLE_COMMENT_OR_REPLY,
|
||||
ISSUABLE_EDIT_DESCRIPTION,
|
||||
MR_COPY_SOURCE_BRANCH_NAME,
|
||||
ISSUABLE_COPY_REF,
|
||||
} from './keybindings';
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ export default class ShortcutsIssuable {
|
|||
[ISSUABLE_CHANGE_LABEL, () => ShortcutsIssuable.openSidebarDropdown('labels')],
|
||||
[ISSUABLE_COMMENT_OR_REPLY, ShortcutsIssuable.replyWithSelectedText],
|
||||
[ISSUABLE_EDIT_DESCRIPTION, ShortcutsIssuable.editIssue],
|
||||
[MR_COPY_SOURCE_BRANCH_NAME, () => this.copyBranchName()],
|
||||
[ISSUABLE_COPY_REF, () => this.copyIssuableRef()],
|
||||
]);
|
||||
|
||||
|
|
@ -164,6 +166,17 @@ export default class ShortcutsIssuable {
|
|||
return false;
|
||||
}
|
||||
|
||||
async copyBranchName() {
|
||||
const button = document.querySelector('.js-source-branch-copy');
|
||||
const branchName = button?.dataset.clipboardText;
|
||||
|
||||
if (branchName) {
|
||||
this.branchInMemoryButton.dataset.clipboardText = branchName;
|
||||
|
||||
this.branchInMemoryButton.dispatchEvent(new CustomEvent('click'));
|
||||
}
|
||||
}
|
||||
|
||||
async copyIssuableRef() {
|
||||
const refButton = document.querySelector('.js-copy-reference');
|
||||
const copiedRef = refButton?.dataset.clipboardText;
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export const I18N_DISCONNECTED_TOOLTIP = s__(
|
|||
|
||||
// Default online/stale status timeouts, actual values
|
||||
export const ONLINE_CONTACT_TIMEOUT_SECS = 2 * 60 * 60; // 2 hours
|
||||
export const STALE_TIMEOUT_SECS = 7889238; // Ruby's `3.months`
|
||||
export const STALE_TIMEOUT_SECS = 604800; // 7.days
|
||||
|
||||
// Registration dropdown
|
||||
export const I18N_REGISTER_INSTANCE_TYPE = s__('Runners|Register an instance runner');
|
||||
|
|
|
|||
|
|
@ -1,3 +1,57 @@
|
|||
class TOCHeading {
|
||||
parent = null;
|
||||
subHeadings = [];
|
||||
|
||||
constructor(text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
get level() {
|
||||
return this.parent ? this.parent.level + 1 : 0;
|
||||
}
|
||||
|
||||
addSubHeading(text) {
|
||||
const heading = new TOCHeading(text);
|
||||
heading.parent = this;
|
||||
this.subHeadings.push(heading);
|
||||
return heading;
|
||||
}
|
||||
|
||||
parentAt(level) {
|
||||
let parentNode = this;
|
||||
|
||||
while (parentNode.level > level) {
|
||||
parentNode = parentNode.parent;
|
||||
}
|
||||
|
||||
return parentNode;
|
||||
}
|
||||
|
||||
flattenIfEmpty() {
|
||||
this.subHeadings.forEach((subHeading) => {
|
||||
subHeading.flattenIfEmpty();
|
||||
});
|
||||
|
||||
if (!this.text && this.parent) {
|
||||
const index = this.parent.subHeadings.indexOf(this);
|
||||
this.parent.subHeadings.splice(index, 1, ...this.subHeadings);
|
||||
for (const subHeading of this.subHeadings) {
|
||||
subHeading.parent = this.parent;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
text: this.text,
|
||||
level: this.level,
|
||||
subHeadings: this.subHeadings.map((subHeading) => subHeading.toJSON()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function fillEmpty(headings) {
|
||||
for (let i = 0; i < headings.length; i += 1) {
|
||||
let j = headings[i - 1]?.level || 0;
|
||||
|
|
@ -12,41 +66,20 @@ export function fillEmpty(headings) {
|
|||
return headings;
|
||||
}
|
||||
|
||||
const exitHeadingBranch = (heading, targetLevel) => {
|
||||
let currentHeading = heading;
|
||||
|
||||
while (currentHeading.level > targetLevel) {
|
||||
currentHeading = currentHeading.parent;
|
||||
}
|
||||
|
||||
return currentHeading;
|
||||
};
|
||||
|
||||
export function toTree(headings) {
|
||||
fillEmpty(headings);
|
||||
|
||||
const tree = [];
|
||||
let currentHeading;
|
||||
for (let i = 0; i < headings.length; i += 1) {
|
||||
const heading = headings[i];
|
||||
if (heading.level === 1) {
|
||||
const h = { ...heading, subHeadings: [] };
|
||||
tree.push(h);
|
||||
currentHeading = h;
|
||||
} else if (heading.level > currentHeading.level) {
|
||||
const h = { ...heading, subHeadings: [], parent: currentHeading };
|
||||
currentHeading.subHeadings.push(h);
|
||||
currentHeading = h;
|
||||
} else if (heading.level <= currentHeading.level) {
|
||||
currentHeading = exitHeadingBranch(currentHeading, heading.level - 1);
|
||||
const tree = new TOCHeading();
|
||||
let currentHeading = tree;
|
||||
|
||||
const h = { ...heading, subHeadings: [], parent: currentHeading };
|
||||
(currentHeading?.subHeadings || headings).push(h);
|
||||
currentHeading = h;
|
||||
for (const heading of headings) {
|
||||
if (heading.level <= currentHeading.level) {
|
||||
currentHeading = currentHeading.parentAt(heading.level - 1);
|
||||
}
|
||||
currentHeading = (currentHeading || tree).addSubHeading(heading.text);
|
||||
}
|
||||
|
||||
return tree;
|
||||
return tree.flattenIfEmpty().toJSON();
|
||||
}
|
||||
|
||||
export function getHeadings(editor) {
|
||||
|
|
@ -63,5 +96,5 @@ export function getHeadings(editor) {
|
|||
return true;
|
||||
});
|
||||
|
||||
return toTree(headings);
|
||||
return toTree(headings).subHeadings;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ export default {
|
|||
:text="getNoteableData.source_branch"
|
||||
size="small"
|
||||
category="tertiary"
|
||||
class="gl-mx-1"
|
||||
class="gl-mx-1 js-source-branch-copy"
|
||||
/>
|
||||
</template>
|
||||
<template #source>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export const PACKAGE_TYPE_RUBYGEMS = 'RUBYGEMS';
|
|||
export const PACKAGE_TYPE_GENERIC = 'GENERIC';
|
||||
export const PACKAGE_TYPE_DEBIAN = 'DEBIAN';
|
||||
export const PACKAGE_TYPE_HELM = 'HELM';
|
||||
export const PACKAGE_TYPE_ML_MODEL = 'ML_MODEL';
|
||||
|
||||
export const TRACKING_LABEL_CODE_INSTRUCTION = 'code_instruction';
|
||||
export const TRACKING_LABEL_MAVEN_INSTALLATION = 'maven_installation';
|
||||
|
|
@ -217,6 +218,7 @@ export const PACKAGE_TYPES_OPTIONS = [
|
|||
{ value: 'RubyGems', title: s__('PackageRegistry|RubyGems') },
|
||||
{ value: 'Debian', title: s__('PackageRegistry|Debian') },
|
||||
{ value: 'Helm', title: s__('PackageRegistry|Helm') },
|
||||
{ value: 'Ml_Model', title: s__('PackageRegistry|Machine learning model') },
|
||||
];
|
||||
/* eslint-enable @gitlab/require-i18n-strings */
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
PACKAGE_TYPE_GENERIC,
|
||||
PACKAGE_TYPE_DEBIAN,
|
||||
PACKAGE_TYPE_HELM,
|
||||
PACKAGE_TYPE_ML_MODEL,
|
||||
LIST_KEY_PROJECT,
|
||||
SORT_FIELDS,
|
||||
} from './constants';
|
||||
|
|
@ -38,6 +39,8 @@ export const getPackageTypeLabel = (packageType) => {
|
|||
return s__('PackageRegistry|Debian');
|
||||
case PACKAGE_TYPE_HELM:
|
||||
return s__('PackageRegistry|Helm');
|
||||
case PACKAGE_TYPE_ML_MODEL:
|
||||
return s__('PackageRegistry|MlModel');
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
/* eslint-disable no-restricted-imports */
|
||||
import {
|
||||
BrowserClient,
|
||||
getCurrentHub,
|
||||
defaultStackParser,
|
||||
makeFetchTransport,
|
||||
defaultIntegrations,
|
||||
BrowserTracing,
|
||||
init,
|
||||
browserTracingIntegration,
|
||||
|
||||
// exports
|
||||
captureException,
|
||||
|
|
@ -17,12 +13,9 @@ const initSentry = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
const hub = getCurrentHub();
|
||||
|
||||
const page = document?.body?.dataset?.page;
|
||||
|
||||
const client = new BrowserClient({
|
||||
// Sentry.init(...) options
|
||||
init({
|
||||
dsn: gon.sentry_dsn,
|
||||
release: gon.revision,
|
||||
allowUrls:
|
||||
|
|
@ -30,19 +23,15 @@ const initSentry = () => {
|
|||
? [gon.gitlab_url]
|
||||
: [gon.gitlab_url, 'webpack-internal://'],
|
||||
environment: gon.sentry_environment,
|
||||
autoSessionTracking: true,
|
||||
|
||||
// Browser tracing configuration
|
||||
enableTracing: true,
|
||||
tracePropagationTargets: [/^\//], // only trace internal requests
|
||||
tracesSampleRate: gon.sentry_clientside_traces_sample_rate || 0,
|
||||
|
||||
// This configuration imitates the Sentry.init() default configuration
|
||||
// https://github.com/getsentry/sentry-javascript/blob/7.66.0/MIGRATION.md#explicit-client-options
|
||||
transport: makeFetchTransport,
|
||||
stackParser: defaultStackParser,
|
||||
integrations: [
|
||||
...defaultIntegrations,
|
||||
new BrowserTracing({
|
||||
beforeNavigate(context) {
|
||||
browserTracingIntegration({
|
||||
beforeStartSpan(context) {
|
||||
return {
|
||||
...context,
|
||||
// `page` acts as transaction name for performance tracing.
|
||||
|
|
@ -52,33 +41,27 @@ const initSentry = () => {
|
|||
},
|
||||
}),
|
||||
],
|
||||
initialScope(scope) {
|
||||
scope.setTags({
|
||||
version: gon.version,
|
||||
feature_category: gon.feature_category,
|
||||
page,
|
||||
});
|
||||
|
||||
if (gon.current_user_id) {
|
||||
scope.setUser({
|
||||
id: gon.current_user_id,
|
||||
});
|
||||
}
|
||||
|
||||
return scope;
|
||||
},
|
||||
});
|
||||
|
||||
hub.bindClient(client);
|
||||
|
||||
hub.setTags({
|
||||
version: gon.version,
|
||||
feature_category: gon.feature_category,
|
||||
page,
|
||||
});
|
||||
|
||||
if (gon.current_user_id) {
|
||||
hub.setUser({
|
||||
id: gon.current_user_id,
|
||||
});
|
||||
}
|
||||
|
||||
// The option `autoSessionTracking` is only avaialble on Sentry.init
|
||||
// this manually starts a session in a similar way.
|
||||
// See: https://github.com/getsentry/sentry-javascript/blob/7.66.0/packages/browser/src/sdk.ts#L204
|
||||
hub.startSession({ ignoreDuration: true }); // `ignoreDuration` counts only the page view.
|
||||
hub.captureSession();
|
||||
|
||||
// The _Sentry object is globally exported so it can be used by
|
||||
// ./sentry_browser_wrapper.js
|
||||
// This hack allows us to load a single version of `~/sentry/sentry_browser_wrapper`
|
||||
// in the browser, see app/views/layouts/_head.html.haml to find how it is imported.
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
window._Sentry = {
|
||||
captureException,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ const getTrialStatusWidgetData = (sidebarData) => {
|
|||
trialDiscoverPagePath,
|
||||
} = convertObjectPropsToCamelCase(sidebarData.trial_status_widget_data_attrs);
|
||||
|
||||
const { daysRemaining, targetId, trialEndDate } = convertObjectPropsToCamelCase(
|
||||
const { daysRemaining, trialEndDate } = convertObjectPropsToCamelCase(
|
||||
sidebarData.trial_status_popover_data_attrs,
|
||||
);
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ const getTrialStatusWidgetData = (sidebarData) => {
|
|||
planName,
|
||||
plansHref,
|
||||
daysRemaining,
|
||||
targetId,
|
||||
targetId: containerId,
|
||||
trialEndDate: new Date(trialEndDate),
|
||||
trialDiscoverPagePath,
|
||||
};
|
||||
|
|
@ -62,7 +62,7 @@ const getTrialStatusWidgetData = (sidebarData) => {
|
|||
widgetUrl,
|
||||
} = convertObjectPropsToCamelCase(sidebarData.duo_pro_trial_status_widget_data_attrs);
|
||||
|
||||
const { daysRemaining, trialEndDate, purchaseNowUrl } = convertObjectPropsToCamelCase(
|
||||
const { daysRemaining, trialEndDate } = convertObjectPropsToCamelCase(
|
||||
sidebarData.duo_pro_trial_status_popover_data_attrs,
|
||||
);
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ const getTrialStatusWidgetData = (sidebarData) => {
|
|||
daysRemaining,
|
||||
targetId: containerId,
|
||||
trialEndDate: new Date(trialEndDate),
|
||||
purchaseNowUrl,
|
||||
purchaseNowUrl: widgetUrl,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ module Repositories
|
|||
render_ok
|
||||
end
|
||||
|
||||
# POST /foo/bar.git/ssh-upload-pack" (git pull via SSH)
|
||||
def ssh_upload_pack
|
||||
render plain: "Not found", status: :not_found
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def deny_head_requests
|
||||
|
|
@ -58,6 +63,8 @@ module Repositories
|
|||
def git_command
|
||||
if action_name == 'info_refs'
|
||||
params[:service]
|
||||
elsif action_name == 'ssh_upload_pack'
|
||||
'git-upload-pack'
|
||||
else
|
||||
action_name.dasherize
|
||||
end
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@ module MergeRequestsHelper
|
|||
copy_action_description = _('Copy branch name')
|
||||
copy_action_shortcut = 'b'
|
||||
copy_button_title = "#{copy_action_description} <kbd class='flat ml-1' aria-hidden=true>#{copy_action_shortcut}</kbd>"
|
||||
copy_button = clipboard_button(text: merge_request.source_branch, title: copy_button_title, aria_keyshortcuts: copy_action_shortcut, aria_label: copy_action_description, class: '!gl-hidden md:!gl-inline-block gl-mx-1')
|
||||
copy_button = clipboard_button(text: merge_request.source_branch, title: copy_button_title, aria_keyshortcuts: copy_action_shortcut, aria_label: copy_action_description, class: '!gl-hidden md:!gl-inline-block gl-mx-1 js-source-branch-copy')
|
||||
|
||||
target_branch = link_to merge_request.target_branch, project_tree_path(merge_request.target_project, merge_request.target_branch), title: merge_request.target_branch, class: 'ref-container gl-display-inline-block gl-text-truncate gl-max-w-26 gl-mx-2'
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ module Emails
|
|||
@reason_text = _('You are receiving this email because you are an Owner of the Group.')
|
||||
else
|
||||
@target_url = project_settings_access_tokens_url(resource)
|
||||
@reason_text = _('You are receiving this email because you are a Maintainer of the Project.')
|
||||
@reason_text = _('You are receiving this email because you are either an Owner or Maintainer of the project.')
|
||||
end
|
||||
|
||||
mail_with_locale(
|
||||
|
|
|
|||
|
|
@ -83,13 +83,13 @@ module HasUserType
|
|||
projects&.first || groups&.first
|
||||
end
|
||||
|
||||
def resource_bot_owners
|
||||
def resource_bot_owners_and_maintainers
|
||||
return [] unless project_bot?
|
||||
|
||||
resource = resource_bot_resource
|
||||
return [] unless resource
|
||||
|
||||
return resource.maintainers if resource.is_a?(Project)
|
||||
return resource.owners_and_maintainers if resource.is_a?(Project)
|
||||
|
||||
resource
|
||||
.owners
|
||||
|
|
|
|||
|
|
@ -370,6 +370,7 @@ class Project < ApplicationRecord
|
|||
|
||||
has_many :users, -> { allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/422405") },
|
||||
through: :project_members
|
||||
|
||||
has_many :maintainers,
|
||||
-> do
|
||||
allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/422405")
|
||||
|
|
@ -378,6 +379,13 @@ class Project < ApplicationRecord
|
|||
through: :project_members,
|
||||
source: :user
|
||||
|
||||
has_many :owners_and_maintainers,
|
||||
-> do
|
||||
where(members: { access_level: [Gitlab::Access::OWNER, Gitlab::Access::MAINTAINER] })
|
||||
end,
|
||||
through: :project_members,
|
||||
source: :user
|
||||
|
||||
has_many :project_callouts, class_name: 'Users::ProjectCallout', foreign_key: :project_id
|
||||
|
||||
has_many :deploy_keys_projects, inverse_of: :project
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ module Ci
|
|||
Gitlab::Ci::Pipeline::Chain::Validate::Repository,
|
||||
Gitlab::Ci::Pipeline::Chain::Limit::RateLimit,
|
||||
Gitlab::Ci::Pipeline::Chain::Validate::SecurityOrchestrationPolicy,
|
||||
Gitlab::Ci::Pipeline::Chain::Skip,
|
||||
Gitlab::Ci::Pipeline::Chain::PipelineExecutionPolicies::FindConfigs,
|
||||
Gitlab::Ci::Pipeline::Chain::Skip,
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Content,
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Process,
|
||||
Gitlab::Ci::Pipeline::Chain::Validate::AfterConfig,
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ class NotificationService
|
|||
end
|
||||
|
||||
def bot_resource_access_token_about_to_expire(bot_user, token_name)
|
||||
recipients = bot_user.resource_bot_owners.select { |owner| owner.can?(:receive_notifications) }
|
||||
recipients = bot_user.resource_bot_owners_and_maintainers.select { |user| user.can?(:receive_notifications) }
|
||||
resource = bot_user.resource_bot_resource
|
||||
|
||||
recipients.each do |recipient|
|
||||
|
|
|
|||
|
|
@ -1272,6 +1272,16 @@ production: &base
|
|||
# Default is '.gitlab_workhorse_secret' relative to Rails.root (i.e. root of the GitLab app).
|
||||
# secret_file: /home/git/gitlab/.gitlab_workhorse_secret
|
||||
|
||||
topology_service:
|
||||
# enabled: false
|
||||
# address: topology-service.gitlab.example.com:443
|
||||
# ca_file: /home/git/gitlab/config/topology-service-ca.pem
|
||||
# certificate_file: /home/git/gitlab/config/topology-service-cert.pem
|
||||
# private_key_file: /home/git/gitlab/config/topology-service-key.pem
|
||||
|
||||
cell:
|
||||
# name: cell-1
|
||||
|
||||
gitlab_kas:
|
||||
# enabled: true
|
||||
# File that contains the secret key for verifying access for gitlab-kas.
|
||||
|
|
|
|||
|
|
@ -967,6 +967,22 @@ ObjectStoreSettings.new(Settings).parse!
|
|||
Settings['workhorse'] ||= {}
|
||||
Settings.workhorse['secret_file'] ||= Rails.root.join('.gitlab_workhorse_secret')
|
||||
|
||||
#
|
||||
# Topology Service
|
||||
#
|
||||
Settings['topology_service'] ||= {}
|
||||
Settings.topology_service['enabled'] ||= false
|
||||
Settings.topology_service['address'] ||= 'topology-service.gitlab.example.com:443'
|
||||
Settings.topology_service['ca_file'] ||= '/home/git/gitlab/config/topology-service-ca.pem'
|
||||
Settings.topology_service['certificate_file'] ||= '/home/git/gitlab/config/topology-service-cert.pem'
|
||||
Settings.topology_service['private_key_file'] ||= '/home/git/gitlab/config/topology-service-key.pem'
|
||||
|
||||
#
|
||||
# Cells
|
||||
#
|
||||
Settings['cell'] ||= {}
|
||||
Settings.cell['name'] ||= 'cell-1'
|
||||
|
||||
#
|
||||
# GitLab KAS
|
||||
#
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
topology_service_settings = Settings.topology_service
|
||||
return unless topology_service_settings.enabled
|
||||
|
||||
# Configuring the Topology Service will be done here
|
||||
# Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/451052
|
||||
# The code should look like when configuring
|
||||
# the topology service client.
|
||||
|
||||
# address = topology_service_settings.address
|
||||
# cell_settings = Settings.cell # will be used for the topology service requests metadata
|
||||
# See this draft MR for an example:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146528/diffs
|
||||
#
|
||||
# claim_service = Gitlab::Cells::ClaimService::Stub.new(address, :this_channel_is_insecure)
|
||||
|
|
@ -8,6 +8,9 @@ scope(path: '*repository_path', format: false) do
|
|||
get '/info/refs', action: :info_refs
|
||||
post '/git-upload-pack', action: :git_upload_pack
|
||||
post '/git-receive-pack', action: :git_receive_pack
|
||||
|
||||
# GitLab-Shell Git over SSH requests
|
||||
post '/ssh-upload-pack', action: :ssh_upload_pack
|
||||
end
|
||||
|
||||
# NOTE: LFS routes are exposed on all repository types, but we still check for
|
||||
|
|
|
|||
|
|
@ -122,6 +122,16 @@ module.exports = {
|
|||
medium: '200ms',
|
||||
fast: '100ms',
|
||||
},
|
||||
// TODO: Backport to GitLab UI.
|
||||
borderRadius: {
|
||||
none: '0',
|
||||
6: '1.5rem',
|
||||
base: '.25rem',
|
||||
full: '50%', // Tailwind gl-rounded-full is 9999px
|
||||
small: '.125rem',
|
||||
lg: '.5rem',
|
||||
pill: '.75rem',
|
||||
},
|
||||
// These extends probably should be moved to GitLab UI:
|
||||
extend: {
|
||||
// TODO: Backport to GitLab UI. This should be part of the default colors config.
|
||||
|
|
@ -133,10 +143,6 @@ module.exports = {
|
|||
// We have a border-1 class, while tailwind was missing it
|
||||
1: '1px',
|
||||
},
|
||||
borderRadius: {
|
||||
// Tailwind gl-rounded-full is 9999px
|
||||
full: '50%',
|
||||
},
|
||||
boxShadow: {
|
||||
none: 'none',
|
||||
// TODO: I don't think we have a --t-gray matching class... --t-gray-a-24 seems close
|
||||
|
|
|
|||
|
|
@ -8,13 +8,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** GitLab.com
|
||||
**Offering:** GitLab.com, GitLab Dedicated
|
||||
|
||||
You can run your CI/CD jobs on GitLab.com and GitLab Dedicated using GitLab-hosted runners to seamlessly build, test and deploy
|
||||
your application on different environments.
|
||||
|
||||
## Hosted runners for GitLab.com
|
||||
|
||||
DETAILS:
|
||||
**Offering:** GitLab.com
|
||||
|
||||
These runners fully integrated with GitLab.com and are enabled by default for all projects, with no configuration required.
|
||||
Your jobs can run on:
|
||||
|
||||
|
|
@ -98,6 +101,9 @@ You can find all GitLab Runner breaking changes under [Deprecations and removals
|
|||
|
||||
## Hosted runners for GitLab community contributions
|
||||
|
||||
DETAILS:
|
||||
**Offering:** GitLab.com
|
||||
|
||||
If you want to [contribute to GitLab](https://about.gitlab.com/community/contribute/), jobs will be picked up by the
|
||||
`gitlab-shared-runners-manager-X.gitlab.com` fleet of runners, dedicated for GitLab projects and related community forks.
|
||||
|
||||
|
|
@ -108,10 +114,12 @@ As we want to encourage people to contribute, these runners are free of charge.
|
|||
|
||||
## Hosted runners for GitLab Dedicated
|
||||
|
||||
These runners are created on-demand for GitLab Dedicated customers and are fully integrated with your GitLab Dedicated instance.
|
||||
Your jobs can run on:
|
||||
DETAILS:
|
||||
**Offering:** GitLab Dedicated
|
||||
**Status:** Beta
|
||||
|
||||
- [Hosted runners on Linux](hosted_runners/linux.md) ([beta](../../policy/experiment-beta-support.md#beta))
|
||||
These runners are created on demand for GitLab Dedicated customers and are fully integrated with your GitLab Dedicated instance.
|
||||
For more information, see [hosted runners for GitLab Dedicated](../../subscriptions/gitlab_dedicated/index.md#gitlab-runners).
|
||||
|
||||
## Supported image lifecycle
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ are very appreciative of the work done by translators and proofreaders!
|
|||
- Japanese
|
||||
- Tomo Dote - [GitLab](https://gitlab.com/fu7mu4), [Crowdin](https://crowdin.com/profile/fu7mu4)
|
||||
- Tsukasa Komatsubara - [GitLab](https://gitlab.com/tkomatsubara), [Crowdin](https://crowdin.com/profile/tkomatsubara)
|
||||
- Noriko Akiyama - [GitLab](https://gitlab.com/nakiyama-ext), [Crowdin](https://crowdin.com/profile/norikoakiyama)
|
||||
- Naoko Shirakuni - [GitLab](https://gitlab.com/SNaoko), [Crowdin](https://crowdin.com/profile/tamongen)
|
||||
- Megumi Uchikawa - [GitLab](https://gitlab.com/muchikawa), [Crowdin](https://crowdin.com/profile/muchikawa)
|
||||
- Korean
|
||||
- Sunjung Park - [GitLab](https://gitlab.com/sunjungp), [Crowdin](https://crowdin.com/profile/sunjungp)
|
||||
- Hwanyong Lee - [GitLab](https://gitlab.com/hwan_ajou), [Crowdin](https://crowdin.com/profile/grbear)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ For this bot:
|
|||
Other examples of internal users:
|
||||
|
||||
- [GitLab Admin Bot](https://gitlab.com/gitlab-org/gitlab/-/blob/278bc9018dd1515a10cbf15b6c6cd55cb5431407/app/models/user.rb#L950-960)
|
||||
- [GitLab Automation Bot](../user/group/iterations/index.md#gitlab-automation-bot-user)
|
||||
- [Alert Bot](../operations/incident_management/alerts.md#trigger-actions-from-alerts)
|
||||
- [Ghost User](../user/profile/account/delete_account.md#associated-records)
|
||||
- [Support Bot](../user/project/service_desk/configure.md#support-bot-user)
|
||||
|
|
@ -48,4 +49,5 @@ Other examples of internal users:
|
|||
- [Project access tokens](../user/project/settings/project_access_tokens.md).
|
||||
- [Group access tokens](../user/group/settings/group_access_tokens.md).
|
||||
|
||||
These are implemented as `project_{project_id}_bot_{random_string}` i.e. `group_{group_id}_bot_{random_string}` users, with a `PersonalAccessToken`.
|
||||
These are implemented as `project_{project_id}_bot_{random_string}` and `group_{group_id}_bot_{random_string}`
|
||||
users, with a `PersonalAccessToken`.
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ You can use the [project access tokens API](../api/project_access_tokens.md) to
|
|||
programmatically take action, such as
|
||||
[rotating a project access token](../api/project_access_tokens.md#rotate-a-project-access-token).
|
||||
|
||||
Project maintainers with a direct membership receive an email when project access tokens are 7 days or less from expiration. Inherited members do not receive an email.
|
||||
Project Owners and Maintainers with a direct membership receive an email when project access tokens are seven days or less from expiration. Inherited members do not receive an email.
|
||||
|
||||
## Group access tokens
|
||||
|
||||
|
|
|
|||
|
|
@ -64,8 +64,10 @@ To create an iteration cadence:
|
|||
- From the **Upcoming iterations** dropdown list, select how many upcoming iterations should be
|
||||
created and maintained by GitLab.
|
||||
- Optional. To move incomplete issues to the next iteration, select the **Enable roll over** checkbox.
|
||||
At the end of the current iteration, all open issues are added to the next iteration.
|
||||
Issues are moved at midnight in the instance time zone (UTC by default). Administrators can change the instance time zone.
|
||||
At the end of the current iteration, [Automation Bot](#gitlab-automation-bot-user) moves all open
|
||||
issues to the next iteration.
|
||||
Issues are moved at midnight in the instance time zone (UTC by default).
|
||||
Administrators can change the instance time zone.
|
||||
1. Select **Create cadence**. The cadence list page opens.
|
||||
|
||||
To manually manage the created cadence, see [Create an iteration manually](#create-an-iteration-manually).
|
||||
|
|
@ -163,6 +165,18 @@ To delete an iteration cadence:
|
|||
1. To the right of the cadence you want to delete, select the vertical ellipsis (**{ellipsis_v}**) and then select **Delete cadence**.
|
||||
1. Select **Delete cadence**.
|
||||
|
||||
### GitLab Automation Bot user
|
||||
|
||||
When iteration roll-over is enabled, at the end of the current iteration, all open issues are moved
|
||||
to the next iteration.
|
||||
|
||||
Iterations are changed by the special GitLab Automation Bot user, which you can see in the issue
|
||||
[system notes](../../project/system_notes.md).
|
||||
This user isn't a [billable user](../../../subscriptions/self_managed/index.md#billable-users),
|
||||
so it does not count toward the license limit count.
|
||||
|
||||
On GitLab.com, this is the `automation-bot1` user.
|
||||
|
||||
## Create an iteration manually
|
||||
|
||||
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/343889) the minimum user role from Developer to Reporter in GitLab 15.0.
|
||||
|
|
|
|||
|
|
@ -187,6 +187,12 @@ This token is used to initially clone the project while starting the workspace.
|
|||
Any Git operation you perform in the workspace uses this token for authentication and authorization.
|
||||
When you terminate the workspace, the token is revoked.
|
||||
|
||||
The `GIT_CONFIG_COUNT`, `GIT_CONFIG_KEY_n`, and `GIT_CONFIG_VALUE_n`
|
||||
[environment variables](https://git-scm.com/docs/git-config/#Documentation/git-config.txt-GITCONFIGCOUNT)
|
||||
are used for Git authentication in the workspace.
|
||||
Support for these variables was added in Git 2.31, so the Git version
|
||||
you use in the workspace container must be 2.31 and later.
|
||||
|
||||
## Pod interaction in a cluster
|
||||
|
||||
Workspaces run as pods in a Kubernetes cluster.
|
||||
|
|
|
|||
|
|
@ -49,3 +49,5 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::Ci::Pipeline::Chain::Skip.prepend_mod
|
||||
|
|
|
|||
|
|
@ -37250,6 +37250,9 @@ msgstr ""
|
|||
msgid "PackageRegistry|License information located at %{link}"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Machine learning model"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Manage storage used by package assets"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -37268,6 +37271,9 @@ msgstr ""
|
|||
msgid "PackageRegistry|Minimum access level for push"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|MlModel"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Name pattern"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -60458,10 +60464,10 @@ msgstr ""
|
|||
msgid "You are on a read-only GitLab instance."
|
||||
msgstr ""
|
||||
|
||||
msgid "You are receiving this email because you are a Maintainer of the Project."
|
||||
msgid "You are receiving this email because you are an Owner of the Group."
|
||||
msgstr ""
|
||||
|
||||
msgid "You are receiving this email because you are an Owner of the Group."
|
||||
msgid "You are receiving this email because you are either an Owner or Maintainer of the project."
|
||||
msgstr ""
|
||||
|
||||
msgid "You are receiving this message because you are a GitLab administrator for %{url}."
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
|
||||
"@rails/actioncable": "7.0.8-4",
|
||||
"@rails/ujs": "7.0.8-4",
|
||||
"@sentry/browser": "7.116.0",
|
||||
"@sentry/browser": "8.8.0",
|
||||
"@snowplow/browser-plugin-client-hints": "^3.9.0",
|
||||
"@snowplow/browser-plugin-form-tracking": "^3.9.0",
|
||||
"@snowplow/browser-plugin-ga-cookies": "^3.9.0",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
/* eslint-disable import/extensions */
|
||||
|
||||
import { deepEqual } from 'node:assert';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import {
|
||||
extractRules,
|
||||
|
|
@ -13,6 +16,11 @@ import {
|
|||
} from './lib/tailwind_migration.mjs';
|
||||
import { convertUtilsToCSSInJS, toMinimalUtilities } from './tailwind_all_the_way.mjs';
|
||||
|
||||
const EQUIV_FILE = path.resolve(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
'tailwind_equivalents.json',
|
||||
);
|
||||
|
||||
function darkModeResolver(str) {
|
||||
return str.replace(
|
||||
/var\(--([^,]+?), #([a-f\d]{8}|[a-f\d]{6}|[a-f\d]{4}|[a-f\d]{3})\)/g,
|
||||
|
|
@ -79,6 +87,27 @@ function ensureNoLegacyUtilIsUsedWithATailwindModifier(minimalUtils) {
|
|||
return fail;
|
||||
}
|
||||
|
||||
function ensureWeHaveTailwindEquivalentsForLegacyUtils(minimalUtils, equivalents) {
|
||||
let fail = 0;
|
||||
|
||||
for (const key of Object.keys(minimalUtils)) {
|
||||
const legacyClassName = key.replace(/^\./, 'gl-').replace('\\', '');
|
||||
/* Note: Right now we check that the equivalents are defined, future iteration could be:
|
||||
!equivalents[legacyClassName] to ensure that all used legacy utils actually have a tailwind equivalent
|
||||
and not null */
|
||||
if (!(legacyClassName in equivalents)) {
|
||||
console.warn(
|
||||
`New legacy util (${legacyClassName}) introduced which is untracked in tailwind_equivalents.json.`,
|
||||
);
|
||||
fail += 1;
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
console.log(`\t${fail} unmapped legacy utils found`);
|
||||
}
|
||||
return fail;
|
||||
}
|
||||
|
||||
console.log('# Converting legacy styles to CSS-in-JS definitions');
|
||||
|
||||
const stats = await convertUtilsToCSSInJS();
|
||||
|
|
@ -115,6 +144,10 @@ const { rules } = await toMinimalUtilities();
|
|||
console.log('## Running checks');
|
||||
failures += ensureNoLegacyUtilIsUsedWithATailwindModifier(rules);
|
||||
|
||||
console.log('# Checking if we have tailwind equivalents of all classes');
|
||||
const equivalents = JSON.parse(await readFile(EQUIV_FILE, 'utf-8'));
|
||||
failures += ensureWeHaveTailwindEquivalentsForLegacyUtils(rules, equivalents);
|
||||
|
||||
if (failures) {
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -64,40 +64,37 @@
|
|||
"gl-border-bottom-0!": "!gl-border-b-0",
|
||||
"gl-rounded-0": "gl-rounded-none",
|
||||
"gl-rounded-0!": "!gl-rounded-none",
|
||||
"gl-rounded-6": null,
|
||||
"gl-rounded-base": null,
|
||||
"gl-rounded-base!": null,
|
||||
"gl-rounded-full!": null,
|
||||
"gl-rounded-small": null,
|
||||
"gl-rounded-lg!": null,
|
||||
"gl-rounded-pill": null,
|
||||
"gl-rounded-base!": "!gl-rounded-base",
|
||||
"gl-rounded-full!": "!gl-rounded-full",
|
||||
"gl-rounded-lg!": "!gl-rounded-lg",
|
||||
"gl-rounded-left-none!": "!gl-rounded-l-none",
|
||||
"gl-rounded-top-left-base": null,
|
||||
"gl-rounded-top-left-base": "gl-rounded-tl-base",
|
||||
"gl-rounded-top-left-none": "gl-rounded-tl-none",
|
||||
"gl-rounded-top-left-none!": "!gl-rounded-tl-none",
|
||||
"gl-rounded-top-right-base": null,
|
||||
"gl-rounded-top-right-base!": null,
|
||||
"gl-rounded-top-right-base": "gl-rounded-tr-base",
|
||||
"gl-rounded-top-right-base!": "!gl-rounded-tr-base",
|
||||
"gl-rounded-top-right-none": "gl-rounded-tr-none",
|
||||
"gl-rounded-top-right-none!": "!gl-rounded-tr-none",
|
||||
"gl-rounded-top-base": null,
|
||||
"gl-rounded-bottom-left-small": null,
|
||||
"gl-rounded-bottom-left-base": null,
|
||||
"gl-rounded-bottom-left-base!": null,
|
||||
"gl-rounded-top-base": "gl-rounded-t-base",
|
||||
"gl-rounded-bottom-left-small": "gl-rounded-bl-small",
|
||||
"gl-rounded-bottom-left-base": "gl-rounded-bl-base",
|
||||
"gl-rounded-bottom-left-base!": "!gl-rounded-bl-base",
|
||||
"gl-rounded-bottom-left-none": "gl-rounded-bl-none",
|
||||
"gl-rounded-bottom-left-none!": "!gl-rounded-bl-none",
|
||||
"gl-rounded-bottom-right-small": null,
|
||||
"gl-rounded-bottom-right-base": null,
|
||||
"gl-rounded-bottom-right-base!": null,
|
||||
"gl-rounded-bottom-right-small": "gl-rounded-br-small",
|
||||
"gl-rounded-bottom-right-base": "gl-rounded-br-base",
|
||||
"gl-rounded-bottom-right-base!": "!gl-rounded-br-base",
|
||||
"gl-rounded-bottom-right-none": "gl-rounded-br-none",
|
||||
"gl-rounded-bottom-right-none!": "!gl-rounded-br-none",
|
||||
"gl-rounded-bottom-base": null,
|
||||
"gl-rounded-top-left-small": null,
|
||||
"gl-rounded-top-right-small": null,
|
||||
"gl-rounded-bottom-base": "gl-rounded-b-base",
|
||||
"gl-rounded-top-left-small": "gl-rounded-tl-small",
|
||||
"gl-rounded-top-right-small": "gl-rounded-tr-small",
|
||||
"gl-inset-border-1-gray-100!": null,
|
||||
"gl-inset-border-1-gray-400": null,
|
||||
"gl-inset-border-1-gray-400!": null,
|
||||
"gl-focus-inset-border-2-blue-400!": null,
|
||||
"gl-inset-border-1-red-500!": null,
|
||||
"gl-inset-border-b-2-blue-500": null,
|
||||
"gl-shadow-none!": "!gl-shadow-none",
|
||||
"gl-clearfix": null,
|
||||
"gl-clearfix!": null,
|
||||
|
|
@ -357,6 +354,7 @@
|
|||
"gl-py-6!": "!gl-py-6",
|
||||
"gl-m-0!": "!gl-m-0",
|
||||
"gl-mt-0!": "!gl-mt-0",
|
||||
"gl-mt-n1": "-gl-mt-1",
|
||||
"gl-mt-2!": "!gl-mt-2",
|
||||
"gl-mt-n2": "-gl-mt-2",
|
||||
"gl-mt-3!": "!gl-mt-3",
|
||||
|
|
@ -537,6 +535,7 @@
|
|||
"gl-line-height-36": null,
|
||||
"gl-line-height-42": null,
|
||||
"gl-vertical-align-top": "gl-align-top",
|
||||
"gl-vertical-align-middle": "gl-align-middle",
|
||||
"gl-vertical-align-bottom": "gl-align-bottom",
|
||||
"gl-vertical-align-text-bottom": "gl-align-text-bottom",
|
||||
"gl-vertical-align-text-bottom!": "!gl-align-text-bottom",
|
||||
|
|
|
|||
|
|
@ -88,6 +88,17 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
|
|||
it_behaves_like 'handles logging git upload pack operation'
|
||||
it_behaves_like 'handles logging git receive pack operation'
|
||||
|
||||
describe 'POST #ssh_upload_pack' do
|
||||
it 'returns not found error' do
|
||||
allow(controller).to receive(:verify_workhorse_api!).and_return(true)
|
||||
|
||||
post :ssh_upload_pack, params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(response.body).to eq 'Not found'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #git_upload_pack' do
|
||||
before do
|
||||
allow(controller).to receive(:verify_workhorse_api!).and_return(true)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ RSpec.describe 'Sentry', feature_category: :error_tracking do
|
|||
visit new_user_session_path
|
||||
|
||||
expect(has_requested_sentry).to eq(true)
|
||||
expect(evaluate_script('window._Sentry.SDK_VERSION')).to match(%r{^7\.})
|
||||
expect(evaluate_script('window._Sentry.SDK_VERSION')).to match(%r{^8\.})
|
||||
end
|
||||
|
||||
def has_requested_sentry
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ describe('RunnerTypeBadge', () => {
|
|||
expect(wrapper.text()).toBe(I18N_STATUS_STALE);
|
||||
expect(findBadge().props('variant')).toBe('warning');
|
||||
expect(getTooltip().value).toBe(
|
||||
"Runner hasn't contacted GitLab in more than 3 months and last contact was 1 year ago",
|
||||
"Runner hasn't contacted GitLab in more than 1 week and last contact was 1 year ago",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ describe('RunnerTypeBadge', () => {
|
|||
|
||||
expect(wrapper.text()).toBe(I18N_STATUS_STALE);
|
||||
expect(findBadge().props('variant')).toBe('warning');
|
||||
expect(getTooltip().value).toBe('Runner is older than 3 months and has never contacted GitLab');
|
||||
expect(getTooltip().value).toBe('Runner is older than 1 week and has never contacted GitLab');
|
||||
});
|
||||
|
||||
describe('does not fail when data is missing', () => {
|
||||
|
|
|
|||
|
|
@ -1,32 +1,89 @@
|
|||
import Heading from '~/content_editor/extensions/heading';
|
||||
import { toTree, getHeadings } from '~/content_editor/services/table_of_contents_utils';
|
||||
import { toTree, getHeadings, fillEmpty } from '~/content_editor/services/table_of_contents_utils';
|
||||
import { createTestEditor, createDocBuilder } from '../test_utils';
|
||||
|
||||
describe('content_editor/services/table_of_content_utils', () => {
|
||||
describe('toTree', () => {
|
||||
it('should fills in gaps in heading levels and convert headings to a tree', () => {
|
||||
expect(
|
||||
toTree([
|
||||
{ level: 3, text: '3' },
|
||||
{ level: 2, text: '2' },
|
||||
]),
|
||||
).toEqual([
|
||||
expect.objectContaining({
|
||||
level: 1,
|
||||
text: '',
|
||||
subHeadings: [
|
||||
expect.objectContaining({
|
||||
level: 2,
|
||||
text: '',
|
||||
subHeadings: [expect.objectContaining({ level: 3, text: '3', subHeadings: [] })],
|
||||
}),
|
||||
expect.objectContaining({ level: 2, text: '2', subHeadings: [] }),
|
||||
],
|
||||
}),
|
||||
const headings = [
|
||||
{ level: 3, text: 'Heading 3' },
|
||||
{ level: 4, text: 'Heading 4' },
|
||||
{ level: 2, text: 'Heading 2' },
|
||||
{ level: 1, text: 'Heading 1' },
|
||||
{ level: 4, text: 'Heading 4' },
|
||||
{ level: 3, text: 'Heading 3' },
|
||||
{ level: 2, text: 'Heading 2' },
|
||||
{ level: 1, text: 'Heading 1' },
|
||||
{ level: 6, text: 'Heading 6' },
|
||||
{ level: 3, text: 'Heading 3' },
|
||||
{ level: 5, text: 'Heading 5' },
|
||||
{ level: 1, text: 'Heading 1' },
|
||||
];
|
||||
|
||||
describe('fillEmpty', () => {
|
||||
it('fills in gaps in heading levels', () => {
|
||||
expect(fillEmpty(headings)).toEqual([
|
||||
{ level: 1, text: '' },
|
||||
{ level: 2, text: '' },
|
||||
{ level: 3, text: 'Heading 3' },
|
||||
{ level: 4, text: 'Heading 4' },
|
||||
{ level: 2, text: 'Heading 2' },
|
||||
{ level: 1, text: 'Heading 1' },
|
||||
{ level: 2, text: '' },
|
||||
{ level: 3, text: '' },
|
||||
{ level: 4, text: 'Heading 4' },
|
||||
{ level: 3, text: 'Heading 3' },
|
||||
{ level: 2, text: 'Heading 2' },
|
||||
{ level: 1, text: 'Heading 1' },
|
||||
{ level: 2, text: '' },
|
||||
{ level: 3, text: '' },
|
||||
{ level: 4, text: '' },
|
||||
{ level: 5, text: '' },
|
||||
{ level: 6, text: 'Heading 6' },
|
||||
{ level: 3, text: 'Heading 3' },
|
||||
{ level: 4, text: '' },
|
||||
{ level: 5, text: 'Heading 5' },
|
||||
{ level: 1, text: 'Heading 1' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toTree', () => {
|
||||
it('normalizes missing heading levels and returns a tree', () => {
|
||||
expect(toTree(headings)).toEqual({
|
||||
level: 0,
|
||||
subHeadings: [
|
||||
{
|
||||
text: 'Heading 3',
|
||||
level: 1,
|
||||
subHeadings: [{ text: 'Heading 4', level: 2, subHeadings: [] }],
|
||||
},
|
||||
{ text: 'Heading 2', level: 1, subHeadings: [] },
|
||||
{
|
||||
text: 'Heading 1',
|
||||
level: 1,
|
||||
subHeadings: [
|
||||
{ text: 'Heading 4', level: 2, subHeadings: [] },
|
||||
{ text: 'Heading 3', level: 2, subHeadings: [] },
|
||||
{ text: 'Heading 2', level: 2, subHeadings: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Heading 1',
|
||||
level: 1,
|
||||
subHeadings: [
|
||||
{ text: 'Heading 6', level: 2, subHeadings: [] },
|
||||
{
|
||||
text: 'Heading 3',
|
||||
level: 2,
|
||||
subHeadings: [{ text: 'Heading 5', level: 3, subHeadings: [] }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ text: 'Heading 1', level: 1, subHeadings: [] },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHeadings', () => {
|
||||
const tiptapEditor = createTestEditor({
|
||||
extensions: [Heading],
|
||||
|
|
@ -57,39 +114,33 @@ describe('content_editor/services/table_of_content_utils', () => {
|
|||
tiptapEditor.commands.setContent(initialDoc.toJSON());
|
||||
|
||||
expect(getHeadings(tiptapEditor)).toEqual([
|
||||
expect.objectContaining({
|
||||
{
|
||||
level: 1,
|
||||
text: 'Heading 1',
|
||||
subHeadings: [
|
||||
expect.objectContaining({
|
||||
{
|
||||
level: 2,
|
||||
text: 'Heading 1.1',
|
||||
subHeadings: [
|
||||
expect.objectContaining({ level: 3, text: 'Heading 1.1.1', subHeadings: [] }),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
subHeadings: [{ level: 3, text: 'Heading 1.1.1', subHeadings: [] }],
|
||||
},
|
||||
{
|
||||
level: 2,
|
||||
text: 'Heading 1.2',
|
||||
subHeadings: [
|
||||
expect.objectContaining({ level: 3, text: 'Heading 1.2.1', subHeadings: [] }),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({ level: 2, text: 'Heading 1.3', subHeadings: [] }),
|
||||
expect.objectContaining({
|
||||
subHeadings: [{ level: 3, text: 'Heading 1.2.1', subHeadings: [] }],
|
||||
},
|
||||
{ level: 2, text: 'Heading 1.3', subHeadings: [] },
|
||||
{
|
||||
level: 2,
|
||||
text: 'Heading 1.4',
|
||||
subHeadings: [
|
||||
expect.objectContaining({ level: 3, text: 'Heading 1.4.1', subHeadings: [] }),
|
||||
],
|
||||
}),
|
||||
subHeadings: [{ level: 3, text: 'Heading 1.4.1', subHeadings: [] }],
|
||||
},
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
},
|
||||
{
|
||||
level: 1,
|
||||
text: 'Heading 2',
|
||||
subHeadings: [],
|
||||
}),
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ describe('Packages shared utils', () => {
|
|||
${'COMPOSER'} | ${'Composer'}
|
||||
${'DEBIAN'} | ${'Debian'}
|
||||
${'HELM'} | ${'Helm'}
|
||||
${'ML_MODEL'} | ${'MlModel'}
|
||||
${'FOO'} | ${null}
|
||||
`(`package type`, ({ packageType, expectedResult }) => {
|
||||
it(`${packageType} should show as ${expectedResult}`, () => {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,5 @@
|
|||
/* eslint-disable no-restricted-imports */
|
||||
import {
|
||||
BrowserClient,
|
||||
defaultStackParser,
|
||||
makeFetchTransport,
|
||||
defaultIntegrations,
|
||||
BrowserTracing,
|
||||
|
||||
// exports
|
||||
captureException,
|
||||
SDK_VERSION,
|
||||
} from '@sentry/browser';
|
||||
import { captureException, SDK_VERSION } from '@sentry/browser';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
|
||||
import { initSentry } from '~/sentry/init_sentry';
|
||||
|
|
@ -29,19 +19,13 @@ jest.mock('@sentry/browser', () => {
|
|||
...jest.createMockFromModule('@sentry/browser'),
|
||||
|
||||
// unmock actual configuration options
|
||||
defaultStackParser: jest.requireActual('@sentry/browser').defaultStackParser,
|
||||
makeFetchTransport: jest.requireActual('@sentry/browser').makeFetchTransport,
|
||||
defaultIntegrations: jest.requireActual('@sentry/browser').defaultIntegrations,
|
||||
browserTracingIntegration: jest.requireActual('@sentry/browser').browserTracingIntegration,
|
||||
};
|
||||
});
|
||||
|
||||
describe('SentryConfig', () => {
|
||||
let mockBindClient;
|
||||
let mockSetTags;
|
||||
let mockSetUser;
|
||||
let mockBrowserClient;
|
||||
let mockStartSession;
|
||||
let mockCaptureSession;
|
||||
let mockScope;
|
||||
let mockSentryInit;
|
||||
|
||||
beforeEach(() => {
|
||||
window.gon = {
|
||||
|
|
@ -57,20 +41,11 @@ describe('SentryConfig', () => {
|
|||
|
||||
document.body.dataset.page = mockPage;
|
||||
|
||||
mockBindClient = jest.fn();
|
||||
mockSetTags = jest.fn();
|
||||
mockSetUser = jest.fn();
|
||||
mockStartSession = jest.fn();
|
||||
mockCaptureSession = jest.fn();
|
||||
mockBrowserClient = jest.spyOn(Sentry, 'BrowserClient');
|
||||
|
||||
jest.spyOn(Sentry, 'getCurrentHub').mockReturnValue({
|
||||
bindClient: mockBindClient,
|
||||
setTags: mockSetTags,
|
||||
setUser: mockSetUser,
|
||||
startSession: mockStartSession,
|
||||
captureSession: mockCaptureSession,
|
||||
});
|
||||
mockSentryInit = jest.spyOn(Sentry, 'init');
|
||||
mockScope = {
|
||||
setTags: jest.fn(),
|
||||
setUser: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -84,37 +59,38 @@ describe('SentryConfig', () => {
|
|||
initSentry();
|
||||
});
|
||||
|
||||
it('creates BrowserClient with gon values and configuration', () => {
|
||||
expect(mockBrowserClient).toHaveBeenCalledWith(
|
||||
it('calls Sentry.init with gon values and configuration', () => {
|
||||
expect(mockSentryInit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
dsn: mockDsn,
|
||||
release: mockRevision,
|
||||
allowUrls: [mockGitlabUrl, 'webpack-internal://'],
|
||||
environment: mockEnvironment,
|
||||
tracesSampleRate: mockSentryClientsideTracesSampleRate,
|
||||
autoSessionTracking: true,
|
||||
enableTracing: true,
|
||||
tracePropagationTargets: [/^\//],
|
||||
|
||||
transport: makeFetchTransport,
|
||||
stackParser: defaultStackParser,
|
||||
integrations: [...defaultIntegrations, expect.any(BrowserTracing)],
|
||||
tracesSampleRate: mockSentryClientsideTracesSampleRate,
|
||||
integrations: [{ afterAllSetup: expect.any(Function), name: 'BrowserTracing' }],
|
||||
initialScope: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('Uses data-page to set BrowserTracing transaction name', () => {
|
||||
const context = BrowserTracing.mock.calls[0][0].beforeNavigate();
|
||||
it('Uses data-page to set browserTracingIntegration transaction name', () => {
|
||||
const mockBrowserTracingIntegration = jest.spyOn(Sentry, 'browserTracingIntegration');
|
||||
|
||||
initSentry();
|
||||
|
||||
const context = mockBrowserTracingIntegration.mock.calls[0][0].beforeStartSpan();
|
||||
|
||||
expect(context).toMatchObject({ name: mockPage });
|
||||
});
|
||||
|
||||
it('binds the BrowserClient to the hub', () => {
|
||||
expect(mockBindClient).toHaveBeenCalledTimes(1);
|
||||
expect(mockBindClient).toHaveBeenCalledWith(expect.any(BrowserClient));
|
||||
});
|
||||
|
||||
it('calls Sentry.setTags with gon values', () => {
|
||||
expect(mockSetTags).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetTags).toHaveBeenCalledWith({
|
||||
mockSentryInit.mock.calls[0][0].initialScope(mockScope);
|
||||
|
||||
expect(mockScope.setTags).toHaveBeenCalledTimes(1);
|
||||
expect(mockScope.setTags).toHaveBeenCalledWith({
|
||||
page: mockPage,
|
||||
version: mockVersion,
|
||||
feature_category: mockFeatureCategory,
|
||||
|
|
@ -122,8 +98,10 @@ describe('SentryConfig', () => {
|
|||
});
|
||||
|
||||
it('calls Sentry.setUser with gon values', () => {
|
||||
expect(mockSetUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetUser).toHaveBeenCalledWith({
|
||||
mockSentryInit.mock.calls[0][0].initialScope(mockScope);
|
||||
|
||||
expect(mockScope.setUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScope.setUser).toHaveBeenCalledWith({
|
||||
id: mockCurrentUserId,
|
||||
});
|
||||
});
|
||||
|
|
@ -144,7 +122,9 @@ describe('SentryConfig', () => {
|
|||
});
|
||||
|
||||
it('does not call Sentry.setUser', () => {
|
||||
expect(mockSetUser).not.toHaveBeenCalled();
|
||||
mockSentryInit.mock.calls[0][0].initialScope(mockScope);
|
||||
|
||||
expect(mockScope.setUser).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -155,8 +135,7 @@ describe('SentryConfig', () => {
|
|||
});
|
||||
|
||||
it('Sentry.init is not called', () => {
|
||||
expect(mockBrowserClient).not.toHaveBeenCalled();
|
||||
expect(mockBindClient).not.toHaveBeenCalled();
|
||||
expect(mockSentryInit).not.toHaveBeenCalled();
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
expect(window._Sentry).toBe(undefined);
|
||||
|
|
@ -170,8 +149,7 @@ describe('SentryConfig', () => {
|
|||
});
|
||||
|
||||
it('Sentry.init is not called', () => {
|
||||
expect(mockBrowserClient).not.toHaveBeenCalled();
|
||||
expect(mockBindClient).not.toHaveBeenCalled();
|
||||
expect(mockSentryInit).not.toHaveBeenCalled();
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
expect(window._Sentry).toBe(undefined);
|
||||
|
|
@ -185,16 +163,24 @@ describe('SentryConfig', () => {
|
|||
});
|
||||
|
||||
it('calls Sentry.setTags with gon values', () => {
|
||||
expect(mockSetTags).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetTags).toHaveBeenCalledWith(
|
||||
mockSentryInit.mock.calls[0][0].initialScope(mockScope);
|
||||
|
||||
expect(mockScope.setTags).toHaveBeenCalledTimes(1);
|
||||
expect(mockScope.setTags).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
page: undefined,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('Uses location.path to set BrowserTracing transaction name', () => {
|
||||
const context = BrowserTracing.mock.calls[0][0].beforeNavigate({ op: 'pageload' });
|
||||
it('Uses location.path to set browserTracingIntegration transaction name', () => {
|
||||
const mockBrowserTracingIntegration = jest.spyOn(Sentry, 'browserTracingIntegration');
|
||||
|
||||
initSentry();
|
||||
|
||||
const context = mockBrowserTracingIntegration.mock.calls[0][0].beforeStartSpan({
|
||||
op: 'pageload',
|
||||
});
|
||||
|
||||
expect(context).toEqual({ op: 'pageload', name: window.location.pathname });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Ci::Pipeline::Chain::Skip do
|
||||
RSpec.describe Gitlab::Ci::Pipeline::Chain::Skip, feature_category: :pipeline_composition do
|
||||
let_it_be(:project, reload: true) { create(:project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:pipeline, reload: true) { create(:ci_pipeline, project: project) }
|
||||
|
|
@ -58,7 +58,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Skip do
|
|||
|
||||
context 'when [ci skip] should be ignored' do
|
||||
let(:command) do
|
||||
double('command', project: project, current_user: user, ignore_skip_ci: true)
|
||||
double('command', project: project, current_user: user, ignore_skip_ci: true, execution_policy_pipelines: nil)
|
||||
end
|
||||
|
||||
it 'does not break the chain' do
|
||||
|
|
@ -70,7 +70,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Skip do
|
|||
|
||||
context 'when pipeline should be skipped but not persisted' do
|
||||
let(:command) do
|
||||
double('command', project: project, current_user: user, ignore_skip_ci: false, save_incompleted: false)
|
||||
double('command', project: project, current_user: user, ignore_skip_ci: false, save_incompleted: false,
|
||||
execution_policy_pipelines: nil)
|
||||
end
|
||||
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -648,6 +648,7 @@ project:
|
|||
- project_repository
|
||||
- users
|
||||
- maintainers
|
||||
- owners_and_maintainers
|
||||
- requesters
|
||||
- namespace_members
|
||||
- namespace_requesters
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ RSpec.describe Emails::Profile, feature_category: :user_profile do
|
|||
it_behaves_like 'resource about to expire email'
|
||||
|
||||
it 'includes the email reason' do
|
||||
is_expected.to have_body_text _('You are receiving this email because you are a Maintainer of the Project.')
|
||||
is_expected.to have_body_text _('You are receiving this email because you are either an Owner or Maintainer of the project.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ RSpec.describe User, feature_category: :system_access do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'resource_bot_owners' do
|
||||
describe 'resource_bot_owners_and_maintainers' do
|
||||
it 'returns nil when user is not a project bot' do
|
||||
expect(human.resource_bot_resource).to be_nil
|
||||
end
|
||||
|
|
@ -161,10 +161,10 @@ RSpec.describe User, feature_category: :system_access do
|
|||
let(:user1) { create(:user) }
|
||||
let(:user2) { create(:user) }
|
||||
|
||||
subject(:owners) { project_bot.resource_bot_owners }
|
||||
subject(:owners_and_maintainers) { project_bot.resource_bot_owners_and_maintainers }
|
||||
|
||||
it 'returns an empty array when there is no owning resource' do
|
||||
expect(owners).to match_array([])
|
||||
expect(owners_and_maintainers).to match_array([])
|
||||
end
|
||||
|
||||
it 'returns group owners when owned by a group' do
|
||||
|
|
@ -172,15 +172,23 @@ RSpec.describe User, feature_category: :system_access do
|
|||
group.add_developer(project_bot)
|
||||
group.add_owner(user1)
|
||||
|
||||
expect(owners).to match_array([user1])
|
||||
expect(owners_and_maintainers).to match_array([user1])
|
||||
end
|
||||
|
||||
it 'returns project maintainers when owned by a project' do
|
||||
it 'returns project owners and maintainers when owned by a project' do
|
||||
project = create(:project)
|
||||
project.add_developer(project_bot)
|
||||
project.add_maintainer(user2)
|
||||
|
||||
expect(owners).to match_array([user2])
|
||||
expect(owners_and_maintainers).to match_array([project.owner, user2])
|
||||
end
|
||||
|
||||
it 'does not returns any other role than owner or maintainer' do
|
||||
project = create(:project)
|
||||
project.add_developer(project_bot)
|
||||
project.add_maintainer(user2)
|
||||
|
||||
expect(owners_and_maintainers).not_to include(project_bot)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
|
|||
it { is_expected.to belong_to(:pool_repository) }
|
||||
it { is_expected.to have_many(:users) }
|
||||
it { is_expected.to have_many(:maintainers).through(:project_members).source(:user).conditions(members: { access_level: Gitlab::Access::MAINTAINER }) }
|
||||
it { is_expected.to have_many(:owners_and_maintainers).through(:project_members).source(:user).conditions(members: { access_level: Gitlab::Access::MAINTAINER }) }
|
||||
it { is_expected.to have_many(:events) }
|
||||
it { is_expected.to have_many(:merge_requests) }
|
||||
it { is_expected.to have_many(:merge_request_metrics).class_name('MergeRequest::Metrics') }
|
||||
|
|
@ -261,6 +262,23 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
|
|||
end
|
||||
end
|
||||
|
||||
describe 'owners_and_maintainers association' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:maintainer) { create(:user) }
|
||||
let_it_be(:reporter) { create(:user) }
|
||||
let_it_be(:developer) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(maintainer)
|
||||
project.add_developer(developer)
|
||||
project.add_reporter(reporter)
|
||||
end
|
||||
|
||||
it 'returns only maintainers and owners' do
|
||||
expect(project.owners_and_maintainers).to match_array([maintainer, project.owner])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when deleting project' do
|
||||
# using delete rather than destroy due to `delete` skipping AR hooks/callbacks
|
||||
# so it's ensured to work at the DB level. Uses AFTER DELETE trigger.
|
||||
|
|
|
|||
|
|
@ -378,22 +378,24 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do
|
|||
|
||||
describe '#resource_access_token_about_to_expire' do
|
||||
let_it_be(:project_bot) { create(:user, :project_bot) }
|
||||
let_it_be(:expiring_token) { create(:personal_access_token, user: project_bot, expires_at: 5.days.from_now) }
|
||||
let_it_be(:expiring_token) { "Expiring Token" }
|
||||
|
||||
let_it_be(:owner1) { create(:user) }
|
||||
let_it_be(:owner2) { create(:user) }
|
||||
let_it_be(:maintainer) { create(:user) }
|
||||
let_it_be(:parent_group) { create(:group) }
|
||||
let_it_be(:group) { create(:group, parent: parent_group) }
|
||||
|
||||
subject(:notification_service) do
|
||||
notification.bot_resource_access_token_about_to_expire(project_bot, [expiring_token.name])
|
||||
notification.bot_resource_access_token_about_to_expire(project_bot, [expiring_token])
|
||||
end
|
||||
|
||||
context 'when the resource is a group' do
|
||||
let(:group) { create(:group) }
|
||||
|
||||
before do
|
||||
before_all do
|
||||
group.add_owner(owner1)
|
||||
group.add_owner(owner2)
|
||||
group.add_reporter(project_bot)
|
||||
group.add_maintainer(maintainer)
|
||||
end
|
||||
|
||||
it 'sends emails to the group owners' do
|
||||
|
|
@ -401,46 +403,119 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do
|
|||
have_enqueued_email(
|
||||
owner1,
|
||||
project_bot.resource_bot_resource,
|
||||
[expiring_token.name],
|
||||
[expiring_token],
|
||||
mail: "bot_resource_access_token_about_to_expire_email"
|
||||
).and(
|
||||
have_enqueued_email(
|
||||
owner2,
|
||||
project_bot.resource_bot_resource,
|
||||
[expiring_token.name],
|
||||
[expiring_token],
|
||||
mail: "bot_resource_access_token_about_to_expire_email"
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not send an email to group maintainer' do
|
||||
expect { notification_service }.not_to(
|
||||
have_enqueued_email(
|
||||
maintainer,
|
||||
project_bot.resource_bot_resource,
|
||||
[expiring_token],
|
||||
mail: "bot_resource_access_token_about_to_expire_email"
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
context 'when group has inherited members' do
|
||||
let_it_be(:project_bot) { create(:user, :project_bot) }
|
||||
let_it_be(:expiring_token_1) { "Expiring Token 1" }
|
||||
let_it_be(:expiring_token_2) { "Expirigin Token 2" }
|
||||
|
||||
subject(:notification_service) do
|
||||
notification.bot_resource_access_token_about_to_expire(project_bot, [expiring_token_1, expiring_token_2])
|
||||
end
|
||||
|
||||
before_all do
|
||||
parent_group.add_owner(owner2)
|
||||
group.add_owner(owner1)
|
||||
group.add_reporter(project_bot)
|
||||
end
|
||||
|
||||
it 'does not send email to inherited members' do
|
||||
expect { notification_service }.to(
|
||||
have_enqueued_email(
|
||||
owner1,
|
||||
project_bot.resource_bot_resource,
|
||||
[expiring_token_1, expiring_token_2],
|
||||
mail: "bot_resource_access_token_about_to_expire_email"
|
||||
)
|
||||
)
|
||||
|
||||
expect { notification_service }.not_to(
|
||||
have_enqueued_email(
|
||||
owner2,
|
||||
project_bot.resource_bot_resource,
|
||||
[expiring_token_1, expiring_token_2],
|
||||
mail: "bot_resource_access_token_about_to_expire_email"
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the resource is a project' do
|
||||
let(:project) { create(:project) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(owner1)
|
||||
project.add_maintainer(owner2)
|
||||
before_all do
|
||||
project.add_maintainer(maintainer)
|
||||
project.add_reporter(project_bot)
|
||||
end
|
||||
|
||||
it 'sends emails to the group owners' do
|
||||
it 'sends emails to the project maintainers and owners' do
|
||||
expect { notification_service }.to(
|
||||
have_enqueued_email(
|
||||
owner1,
|
||||
maintainer,
|
||||
project_bot.resource_bot_resource,
|
||||
[expiring_token.name],
|
||||
[expiring_token],
|
||||
mail: "bot_resource_access_token_about_to_expire_email"
|
||||
).and(
|
||||
have_enqueued_email(
|
||||
owner2,
|
||||
project.owner,
|
||||
project_bot.resource_bot_resource,
|
||||
[expiring_token.name],
|
||||
[expiring_token],
|
||||
mail: "bot_resource_access_token_about_to_expire_email"
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
context 'when project has inherited members' do
|
||||
before_all do
|
||||
project.namespace = group
|
||||
project.save!
|
||||
end
|
||||
|
||||
it 'does not send email to inherited members' do
|
||||
expect { notification_service }.to(
|
||||
have_enqueued_email(
|
||||
maintainer,
|
||||
project_bot.resource_bot_resource,
|
||||
[expiring_token],
|
||||
mail: "bot_resource_access_token_about_to_expire_email"
|
||||
)
|
||||
)
|
||||
|
||||
expect { notification_service }.not_to(
|
||||
have_enqueued_email(
|
||||
project.owner,
|
||||
project_bot.resource_bot_resource,
|
||||
[expiring_token],
|
||||
mail: "bot_resource_access_token_about_to_expire_email"
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -924,7 +924,6 @@
|
|||
- './ee/spec/helpers/security_helper_spec.rb'
|
||||
- './ee/spec/helpers/subscriptions_helper_spec.rb'
|
||||
- './ee/spec/helpers/timeboxes_helper_spec.rb'
|
||||
- './ee/spec/helpers/trial_status_widget_helper_spec.rb'
|
||||
- './ee/spec/helpers/users_helper_spec.rb'
|
||||
- './ee/spec/helpers/vulnerabilities_helper_spec.rb'
|
||||
- './ee/spec/initializers/1_settings_spec.rb'
|
||||
|
|
|
|||
|
|
@ -199,45 +199,35 @@ internal/dependencyproxy/dependencyproxy_test.go:410:27: response body must be c
|
|||
internal/dependencyproxy/dependencyproxy_test.go:412:3: go-require: do not use require in http handlers (testifylint)
|
||||
internal/dependencyproxy/dependencyproxy_test.go:413:3: go-require: do not use require in http handlers (testifylint)
|
||||
internal/dependencyproxy/dependencyproxy_test.go:421: internal/dependencyproxy/dependencyproxy_test.go:421: Line contains TODO/BUG/FIXME/NOTE/OPTIMIZE/HACK: "note that the timeout duration here is s..." (godox)
|
||||
internal/git/archive.go:5:1: package-comments: should have a package comment (revive)
|
||||
internal/git/archive.go:35:2: var-naming: struct field CommitId should be CommitID (revive)
|
||||
internal/git/archive.go:43:2: exported: exported var SendArchive should have comment or be unexported (revive)
|
||||
internal/git/archive.go:53: Function 'Inject' has too many statements (47 > 40) (funlen)
|
||||
internal/git/archive.go:73:29: Error return value of `cachedArchive.Close` is not checked (errcheck)
|
||||
internal/git/archive.go:99:23: Error return value of `tempFile.Close` is not checked (errcheck)
|
||||
internal/git/archive.go:100:18: Error return value of `os.Remove` is not checked (errcheck)
|
||||
internal/git/blob.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/blob.go:21:5: exported: exported var SendBlob should have comment or be unexported (revive)
|
||||
internal/git/diff.go:1: 1-47 lines are duplicate of `internal/git/format-patch.go:1-48` (dupl)
|
||||
internal/git/diff.go:22:5: exported: exported var SendDiff should have comment or be unexported (revive)
|
||||
internal/git/error.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/error.go:36:4: singleCaseSwitch: should rewrite switch statement to if statement (gocritic)
|
||||
internal/git/error_test.go:28: File is not `gofmt`-ed with `-s` (gofmt)
|
||||
internal/git/error_test.go:66:2: unnecessary trailing newline (whitespace)
|
||||
internal/git/format-patch.go:1: 1-48 lines are duplicate of `internal/git/diff.go:1-47` (dupl)
|
||||
internal/git/format-patch.go:22:5: exported: exported var SendPatch should have comment or be unexported (revive)
|
||||
internal/git/git-http.go:5:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/git-http.go:21:2: exported: comment on exported const GitConfigShowAllRefs should be of the form "GitConfigShowAllRefs ..." (revive)
|
||||
internal/git/git-http.go:26:1: exported: exported function ReceivePack should have comment or be unexported (revive)
|
||||
internal/git/git-http.go:30:1: exported: exported function UploadPack should have comment or be unexported (revive)
|
||||
internal/git/info-refs.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/info-refs.go:20:1: exported: exported function GetInfoRefsHandler should have comment or be unexported (revive)
|
||||
internal/git/info-refs_test.go:27:32: unused-parameter: parameter 'r' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
internal/git/io.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/io.go:141:22: Error return value of `tempfile.Close` is not checked (errcheck)
|
||||
internal/git/io.go:173:17: Error return value of `tempfile.Close` is not checked (errcheck)
|
||||
internal/git/io_test.go:20:27: unused-parameter: parameter 'b' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
internal/git/io_test.go:37:38: unused-parameter: parameter 'key' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
internal/git/io_test.go:89:14: string `test data` has 3 occurrences, make it a constant (goconst)
|
||||
internal/git/receive-pack.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/receive-pack.go:21:16: Error return value of `cw.Flush` is not checked (errcheck)
|
||||
internal/git/responsewriter.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/responsewriter.go:41:6: exported: exported type HTTPResponseWriter should have comment or be unexported (revive)
|
||||
internal/git/responsewriter.go:45:1: exported: exported function NewHTTPResponseWriter should have comment or be unexported (revive)
|
||||
internal/git/responsewriter.go:52:1: exported: exported method HTTPResponseWriter.Log should have comment or be unexported (revive)
|
||||
internal/git/snapshot.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/snapshot.go:27:2: exported: exported var SendSnapshot should have comment or be unexported (revive)
|
||||
internal/git/upload-pack.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/upload-pack.go:37:16: Error return value of `cw.Flush` is not checked (errcheck)
|
||||
internal/git/upload-pack_test.go:32:27: unused-parameter: parameter 'b' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
internal/git/upload-pack_test.go:51:38: unused-parameter: parameter 'req' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
|
|
@ -256,11 +246,11 @@ internal/gitaly/gitaly.go:84:1: exported: exported function NewSmartHTTPClient s
|
|||
internal/gitaly/gitaly.go:97:1: exported: exported function NewBlobClient should have comment or be unexported (revive)
|
||||
internal/gitaly/gitaly.go:106:1: exported: exported function NewRepositoryClient should have comment or be unexported (revive)
|
||||
internal/gitaly/gitaly.go:115:1: exported: exported function NewDiffClient should have comment or be unexported (revive)
|
||||
internal/gitaly/gitaly.go:138:5: shadow: declaration of "conn" shadows declaration at line 128 (govet)
|
||||
internal/gitaly/gitaly.go:152:1: exported: exported function CloseConnections should have comment or be unexported (revive)
|
||||
internal/gitaly/gitaly.go:157:13: Error return value of `conn.Close` is not checked (errcheck)
|
||||
internal/gitaly/gitaly.go:162:14: appendAssign: append result not assigned to the same slice (gocritic)
|
||||
internal/gitaly/gitaly.go:202:1: exported: exported function UnmarshalJSON should have comment or be unexported (revive)
|
||||
internal/gitaly/gitaly.go:145:5: shadow: declaration of "conn" shadows declaration at line 135 (govet)
|
||||
internal/gitaly/gitaly.go:159:1: exported: exported function CloseConnections should have comment or be unexported (revive)
|
||||
internal/gitaly/gitaly.go:164:13: Error return value of `conn.Close` is not checked (errcheck)
|
||||
internal/gitaly/gitaly.go:169:14: appendAssign: append result not assigned to the same slice (gocritic)
|
||||
internal/gitaly/gitaly.go:209:1: exported: exported function UnmarshalJSON should have comment or be unexported (revive)
|
||||
internal/gitaly/repository.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/gitaly/smarthttp.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/gitaly/smarthttp.go:13:6: exported: exported type SmartHTTPClient should have comment or be unexported (revive)
|
||||
|
|
@ -476,8 +466,8 @@ internal/upstream/roundtripper/roundtripper_test.go:49:72: unused-parameter: par
|
|||
internal/upstream/roundtripper/roundtripper_test.go:59:75: unused-parameter: parameter 'r' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
internal/upstream/routes.go:106:28: unused-parameter: parameter 'r' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
internal/upstream/routes.go:150:68: `(*upstream).wsRoute` - `matchers` always receives `nil` (unparam)
|
||||
internal/upstream/routes.go:210: Function 'configureRoutes' is too long (234 > 60) (funlen)
|
||||
internal/upstream/routes.go:382: internal/upstream/routes.go:382: Line contains TODO/BUG/FIXME/NOTE/OPTIMIZE/HACK: "TODO: We should probably not return a HT..." (godox)
|
||||
internal/upstream/routes.go:210: Function 'configureRoutes' is too long (235 > 60) (funlen)
|
||||
internal/upstream/routes.go:383: internal/upstream/routes.go:383: Line contains TODO/BUG/FIXME/NOTE/OPTIMIZE/HACK: "TODO: We should probably not return a HT..." (godox)
|
||||
internal/upstream/upstream.go:116: internal/upstream/upstream.go:116: Line contains TODO/BUG/FIXME/NOTE/OPTIMIZE/HACK: "TODO: move to LabKit https://gitlab.com/..." (godox)
|
||||
internal/upstream/upstream_test.go:266:3: go-require: do not use require in http handlers (testifylint)
|
||||
internal/upstream/upstream_test.go:267:3: go-require: do not use require in http handlers (testifylint)
|
||||
|
|
|
|||
|
|
@ -199,45 +199,35 @@ internal/dependencyproxy/dependencyproxy_test.go:410:27: response body must be c
|
|||
internal/dependencyproxy/dependencyproxy_test.go:412:3: go-require: do not use require in http handlers (testifylint)
|
||||
internal/dependencyproxy/dependencyproxy_test.go:413:3: go-require: do not use require in http handlers (testifylint)
|
||||
internal/dependencyproxy/dependencyproxy_test.go:421: internal/dependencyproxy/dependencyproxy_test.go:421: Line contains TODO/BUG/FIXME/NOTE/OPTIMIZE/HACK: "note that the timeout duration here is s..." (godox)
|
||||
internal/git/archive.go:5:1: package-comments: should have a package comment (revive)
|
||||
internal/git/archive.go:35:2: var-naming: struct field CommitId should be CommitID (revive)
|
||||
internal/git/archive.go:43:2: exported: exported var SendArchive should have comment or be unexported (revive)
|
||||
internal/git/archive.go:53: Function 'Inject' has too many statements (47 > 40) (funlen)
|
||||
internal/git/archive.go:73:29: Error return value of `cachedArchive.Close` is not checked (errcheck)
|
||||
internal/git/archive.go:99:23: Error return value of `tempFile.Close` is not checked (errcheck)
|
||||
internal/git/archive.go:100:18: Error return value of `os.Remove` is not checked (errcheck)
|
||||
internal/git/blob.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/blob.go:21:5: exported: exported var SendBlob should have comment or be unexported (revive)
|
||||
internal/git/diff.go:1: 1-47 lines are duplicate of `internal/git/format-patch.go:1-48` (dupl)
|
||||
internal/git/diff.go:22:5: exported: exported var SendDiff should have comment or be unexported (revive)
|
||||
internal/git/error.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/error.go:36:4: singleCaseSwitch: should rewrite switch statement to if statement (gocritic)
|
||||
internal/git/error_test.go:28: File is not `gofmt`-ed with `-s` (gofmt)
|
||||
internal/git/error_test.go:66:2: unnecessary trailing newline (whitespace)
|
||||
internal/git/format-patch.go:1: 1-48 lines are duplicate of `internal/git/diff.go:1-47` (dupl)
|
||||
internal/git/format-patch.go:22:5: exported: exported var SendPatch should have comment or be unexported (revive)
|
||||
internal/git/git-http.go:5:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/git-http.go:21:2: exported: comment on exported const GitConfigShowAllRefs should be of the form "GitConfigShowAllRefs ..." (revive)
|
||||
internal/git/git-http.go:26:1: exported: exported function ReceivePack should have comment or be unexported (revive)
|
||||
internal/git/git-http.go:30:1: exported: exported function UploadPack should have comment or be unexported (revive)
|
||||
internal/git/info-refs.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/info-refs.go:20:1: exported: exported function GetInfoRefsHandler should have comment or be unexported (revive)
|
||||
internal/git/info-refs_test.go:27:32: unused-parameter: parameter 'r' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
internal/git/io.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/io.go:141:22: Error return value of `tempfile.Close` is not checked (errcheck)
|
||||
internal/git/io.go:173:17: Error return value of `tempfile.Close` is not checked (errcheck)
|
||||
internal/git/io_test.go:20:27: unused-parameter: parameter 'b' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
internal/git/io_test.go:37:38: unused-parameter: parameter 'key' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
internal/git/io_test.go:89:14: string `test data` has 3 occurrences, make it a constant (goconst)
|
||||
internal/git/receive-pack.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/receive-pack.go:21:16: Error return value of `cw.Flush` is not checked (errcheck)
|
||||
internal/git/responsewriter.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/responsewriter.go:41:6: exported: exported type HTTPResponseWriter should have comment or be unexported (revive)
|
||||
internal/git/responsewriter.go:45:1: exported: exported function NewHTTPResponseWriter should have comment or be unexported (revive)
|
||||
internal/git/responsewriter.go:52:1: exported: exported method HTTPResponseWriter.Log should have comment or be unexported (revive)
|
||||
internal/git/snapshot.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/snapshot.go:27:2: exported: exported var SendSnapshot should have comment or be unexported (revive)
|
||||
internal/git/upload-pack.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/git/upload-pack.go:37:16: Error return value of `cw.Flush` is not checked (errcheck)
|
||||
internal/git/upload-pack_test.go:32:27: unused-parameter: parameter 'b' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
internal/git/upload-pack_test.go:51:38: unused-parameter: parameter 'req' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
|
|
@ -256,11 +246,11 @@ internal/gitaly/gitaly.go:84:1: exported: exported function NewSmartHTTPClient s
|
|||
internal/gitaly/gitaly.go:97:1: exported: exported function NewBlobClient should have comment or be unexported (revive)
|
||||
internal/gitaly/gitaly.go:106:1: exported: exported function NewRepositoryClient should have comment or be unexported (revive)
|
||||
internal/gitaly/gitaly.go:115:1: exported: exported function NewDiffClient should have comment or be unexported (revive)
|
||||
internal/gitaly/gitaly.go:138:5: shadow: declaration of "conn" shadows declaration at line 128 (govet)
|
||||
internal/gitaly/gitaly.go:152:1: exported: exported function CloseConnections should have comment or be unexported (revive)
|
||||
internal/gitaly/gitaly.go:157:13: Error return value of `conn.Close` is not checked (errcheck)
|
||||
internal/gitaly/gitaly.go:162:14: appendAssign: append result not assigned to the same slice (gocritic)
|
||||
internal/gitaly/gitaly.go:202:1: exported: exported function UnmarshalJSON should have comment or be unexported (revive)
|
||||
internal/gitaly/gitaly.go:145:5: shadow: declaration of "conn" shadows declaration at line 135 (govet)
|
||||
internal/gitaly/gitaly.go:159:1: exported: exported function CloseConnections should have comment or be unexported (revive)
|
||||
internal/gitaly/gitaly.go:164:13: Error return value of `conn.Close` is not checked (errcheck)
|
||||
internal/gitaly/gitaly.go:169:14: appendAssign: append result not assigned to the same slice (gocritic)
|
||||
internal/gitaly/gitaly.go:209:1: exported: exported function UnmarshalJSON should have comment or be unexported (revive)
|
||||
internal/gitaly/repository.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/gitaly/smarthttp.go:1:1: ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
internal/gitaly/smarthttp.go:13:6: exported: exported type SmartHTTPClient should have comment or be unexported (revive)
|
||||
|
|
@ -476,8 +466,8 @@ internal/upstream/roundtripper/roundtripper_test.go:49:72: unused-parameter: par
|
|||
internal/upstream/roundtripper/roundtripper_test.go:59:75: unused-parameter: parameter 'r' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
internal/upstream/routes.go:106:28: unused-parameter: parameter 'r' seems to be unused, consider removing or renaming it as _ (revive)
|
||||
internal/upstream/routes.go:150:68: `(*upstream).wsRoute` - `matchers` always receives `nil` (unparam)
|
||||
internal/upstream/routes.go:210: Function 'configureRoutes' is too long (234 > 60) (funlen)
|
||||
internal/upstream/routes.go:382: internal/upstream/routes.go:382: Line contains TODO/BUG/FIXME/NOTE/OPTIMIZE/HACK: "TODO: We should probably not return a HT..." (godox)
|
||||
internal/upstream/routes.go:210: Function 'configureRoutes' is too long (235 > 60) (funlen)
|
||||
internal/upstream/routes.go:383: internal/upstream/routes.go:383: Line contains TODO/BUG/FIXME/NOTE/OPTIMIZE/HACK: "TODO: We should probably not return a HT..." (godox)
|
||||
internal/upstream/upstream.go:116: internal/upstream/upstream.go:116: Line contains TODO/BUG/FIXME/NOTE/OPTIMIZE/HACK: "TODO: move to LabKit https://gitlab.com/..." (godox)
|
||||
internal/upstream/upstream_test.go:266:3: go-require: do not use require in http handlers (testifylint)
|
||||
internal/upstream/upstream_test.go:267:3: go-require: do not use require in http handlers (testifylint)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ require (
|
|||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2
|
||||
github.com/BurntSushi/toml v1.4.0
|
||||
github.com/alecthomas/chroma/v2 v2.14.0
|
||||
github.com/aws/aws-sdk-go v1.53.16
|
||||
github.com/aws/aws-sdk-go v1.53.17
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/getsentry/raven-go v0.2.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
|
|
@ -18,22 +18,22 @@ require (
|
|||
github.com/johannesboyne/gofakes3 v0.0.0-20240217095638-c55a48f17be6
|
||||
github.com/jpillora/backoff v1.0.0
|
||||
github.com/mitchellh/copystructure v1.2.0
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/prometheus/client_golang v1.19.1-0.20240328134234-93cf5d4f5f78
|
||||
github.com/redis/go-redis/v9 v9.5.2
|
||||
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/smartystreets/goconvey v1.8.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
gitlab.com/gitlab-org/gitaly/v16 v16.11.3
|
||||
gitlab.com/gitlab-org/gitaly/v16 v16.11.4
|
||||
gitlab.com/gitlab-org/labkit v1.21.0
|
||||
gocloud.dev v0.37.0
|
||||
golang.org/x/image v0.16.0
|
||||
golang.org/x/image v0.17.0
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/oauth2 v0.20.0
|
||||
golang.org/x/tools v0.19.0
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
|
||||
google.golang.org/grpc v1.64.0
|
||||
google.golang.org/protobuf v1.34.1
|
||||
google.golang.org/protobuf v1.34.2
|
||||
honnef.co/go/tools v0.4.7
|
||||
)
|
||||
|
||||
|
|
@ -118,10 +118,10 @@ require (
|
|||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/api v0.169.0 // indirect
|
||||
|
|
|
|||
|
|
@ -96,8 +96,8 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc
|
|||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/aws/aws-sdk-go v1.44.256/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aws/aws-sdk-go v1.53.16 h1:8oZjKQO/ml1WLUZw5hvF7pvYjPf8o9f57Wldoy/q9Qc=
|
||||
github.com/aws/aws-sdk-go v1.53.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||
github.com/aws/aws-sdk-go v1.53.17 h1:TwtYMzVBTaqPVj/pcemHRIgk01OycWEcEUyUUX0tpCI=
|
||||
github.com/aws/aws-sdk-go v1.53.17/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.25.3 h1:xYiLpZTQs1mzvz5PaI6uR0Wh57ippuEthxS4iK5v0n0=
|
||||
github.com/aws/aws-sdk-go-v2 v1.25.3/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU=
|
||||
|
|
@ -407,8 +407,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
|||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_golang v1.19.1-0.20240328134234-93cf5d4f5f78 h1:rSOwhjTtzeuOZS3pO9Gzy0vrGMHSR5s7eWiMKBTV8ns=
|
||||
github.com/prometheus/client_golang v1.19.1-0.20240328134234-93cf5d4f5f78/go.mod h1:kDK4t8GKrX8Q1xkeHV0TTro2F3HIgGRx7X1Kt3GEku8=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
||||
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
||||
|
|
@ -483,8 +483,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
|||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
gitlab.com/gitlab-org/gitaly/v16 v16.11.3 h1:WkcRKQ8lO22FeXe54RCE4+7YnLh3irisu63pbtc45hw=
|
||||
gitlab.com/gitlab-org/gitaly/v16 v16.11.3/go.mod h1:lJizRUtXRd1SBHjNbbbL9OsGN4TiugvfRBd8bIsdWI0=
|
||||
gitlab.com/gitlab-org/gitaly/v16 v16.11.4 h1:d6/QyoeThopYsu/vvF68wpCvsMNWxAIx7VgABwM3KwI=
|
||||
gitlab.com/gitlab-org/gitaly/v16 v16.11.4/go.mod h1:lJizRUtXRd1SBHjNbbbL9OsGN4TiugvfRBd8bIsdWI0=
|
||||
gitlab.com/gitlab-org/labkit v1.21.0 h1:hLmdBDtXjD1yOmZ+uJOac3a5Tlo83QaezwhES4IYik4=
|
||||
gitlab.com/gitlab-org/labkit v1.21.0/go.mod h1:zeATDAaSBelPcPLbTTq8J3ZJEHyPTLVBM1q3nva+/W4=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
|
|
@ -547,8 +547,8 @@ golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl
|
|||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
|
||||
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
|
||||
golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco=
|
||||
golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
|
@ -578,8 +578,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
|
@ -657,8 +657,9 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
@ -744,8 +745,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
@ -812,8 +813,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|||
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
@ -957,8 +958,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
|||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 h1:DkD0plWEVUB8v/Ru6kRBW30Hy/fRNBC8hPdcExuBZMc=
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.32.0/go.mod h1:wRKMf/tRASHwH/UOfPQ3IQmVFhTz2/1a1/mpXoIjF54=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
In this file we handle the Git over SSH GitLab-Shell requests
|
||||
*/
|
||||
|
||||
// Package git handles git operations
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
|
||||
|
||||
"gitlab.com/gitlab-org/gitaly/v16/client"
|
||||
|
||||
"gitlab.com/gitlab-org/gitlab/workhorse/internal/api"
|
||||
"gitlab.com/gitlab-org/gitlab/workhorse/internal/gitaly"
|
||||
"gitlab.com/gitlab-org/gitlab/workhorse/internal/helper/fail"
|
||||
)
|
||||
|
||||
type flushWriter struct {
|
||||
http.ResponseWriter
|
||||
controller *http.ResponseController
|
||||
}
|
||||
|
||||
func (f *flushWriter) Write(p []byte) (int, error) {
|
||||
n, err := f.ResponseWriter.Write(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, f.controller.Flush()
|
||||
}
|
||||
|
||||
// SSHUploadPack handles git pull SSH connection between GitLab-Shell and Gitaly through Workhorse
|
||||
func SSHUploadPack(a *api.API) http.Handler {
|
||||
return repoPreAuthorizeHandler(a, handleSSHUploadPack)
|
||||
}
|
||||
|
||||
func handleSSHUploadPack(w http.ResponseWriter, r *http.Request, a *api.Response) {
|
||||
controller := http.NewResponseController(w) //nolint:bodyclose // false-positive https://github.com/timakin/bodyclose/issues/52
|
||||
if err := controller.EnableFullDuplex(); err != nil {
|
||||
fail.Request(w, r, fmt.Errorf("enabling full duplex: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
conn, registry, err := gitaly.NewConnectionWithSidechannel(a.GitalyServer)
|
||||
if err != nil {
|
||||
fail.Request(w, r, fmt.Errorf("look up for gitaly connection: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
request := &gitalypb.SSHUploadPackWithSidechannelRequest{
|
||||
Repository: &a.Repository,
|
||||
GitProtocol: r.Header.Get("Git-Protocol"),
|
||||
GitConfigOptions: a.GitConfigOptions,
|
||||
}
|
||||
out := &flushWriter{ResponseWriter: w, controller: controller}
|
||||
_, err = client.UploadPackWithSidechannelWithResult(r.Context(), conn, registry, r.Body, out, out, request)
|
||||
if err != nil {
|
||||
fail.Request(w, r, fmt.Errorf("upload pack failed: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gitlab.com/gitlab-org/gitaly/v16/client"
|
||||
"gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
|
||||
|
||||
"gitlab.com/gitlab-org/gitlab/workhorse/internal/api"
|
||||
)
|
||||
|
||||
const (
|
||||
sshUploadPackPath = "/ssh-upload-pack"
|
||||
)
|
||||
|
||||
func TestSSHUploadPack(t *testing.T) {
|
||||
addr := setupGitalyServer(t)
|
||||
a := &api.Response{GitalyServer: api.GitalyServer{Address: addr}}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handleSSHUploadPack(w, r, a)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
res, err := http.Post(ts.URL+sshUploadPackPath, "", buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
}
|
||||
|
||||
func TestSSHUploadPack_GitalyConnection(t *testing.T) {
|
||||
a := &api.Response{GitalyServer: api.GitalyServer{Address: "wrong"}}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handleSSHUploadPack(w, r, a)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
res, err := http.Post(ts.URL+sshUploadPackPath, "", buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, http.StatusInternalServerError, res.StatusCode)
|
||||
}
|
||||
|
||||
func TestSSHUploadPack_FullDuplex(t *testing.T) {
|
||||
addr := setupGitalyServer(t)
|
||||
a := &api.Response{GitalyServer: api.GitalyServer{Address: addr}}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
r := httptest.NewRequest("POST", sshUploadPackPath, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handleSSHUploadPack(w, r, a)
|
||||
|
||||
res := w.Result()
|
||||
|
||||
err := res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, http.StatusInternalServerError, res.StatusCode)
|
||||
}
|
||||
|
||||
func setupGitalyServer(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
return startSmartHTTPServer(t, &smartHTTPServiceServer{
|
||||
handler: func(ctx context.Context, _ *gitalypb.PostUploadPackWithSidechannelRequest) (*gitalypb.PostUploadPackWithSidechannelResponse, error) {
|
||||
conn, err := client.OpenServerSidechannel(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
_, err = io.Copy(io.Discard, conn)
|
||||
require.NoError(t, err)
|
||||
|
||||
return &gitalypb.PostUploadPackWithSidechannelResponse{}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -121,6 +121,13 @@ func NewDiffClient(ctx context.Context, server api.GitalyServer) (context.Contex
|
|||
return withOutgoingMetadata(ctx, server), &DiffClient{grpcClient}, nil
|
||||
}
|
||||
|
||||
// NewConnectionWithSidechannel returns a Gitaly connection with a sidechannel
|
||||
func NewConnectionWithSidechannel(server api.GitalyServer) (*grpc.ClientConn, *gitalyclient.SidechannelRegistry, error) {
|
||||
conn, err := getOrCreateConnection(server)
|
||||
|
||||
return conn, sidechannelRegistry, err
|
||||
}
|
||||
|
||||
func getOrCreateConnection(server api.GitalyServer) (*grpc.ClientConn, error) {
|
||||
key := getCacheKey(server)
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,13 @@ func TestNewDiffClient(t *testing.T) {
|
|||
testOutgoingMetadata(ctx, t)
|
||||
}
|
||||
|
||||
func TestNewConnectionWithSidechannel(t *testing.T) {
|
||||
conn, sidechannel, err := NewConnectionWithSidechannel(serverFixture())
|
||||
require.NotNil(t, conn)
|
||||
require.Equal(t, sidechannelRegistry, sidechannel)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func testOutgoingMetadata(ctx context.Context, t *testing.T) {
|
||||
t.Helper()
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
|
|
|
|||
|
|
@ -252,6 +252,7 @@ func configureRoutes(u *upstream) {
|
|||
u.route("POST", gitProjectPattern+`git-upload-pack\z`, contentEncodingHandler(git.UploadPack(api)), withMatcher(isContentType("application/x-git-upload-pack-request"))),
|
||||
u.route("POST", gitProjectPattern+`git-receive-pack\z`, contentEncodingHandler(git.ReceivePack(api)), withMatcher(isContentType("application/x-git-receive-pack-request"))),
|
||||
u.route("PUT", gitProjectPattern+`gitlab-lfs/objects/([0-9a-f]{64})/([0-9]+)\z`, requestBodyUploader, withMatcher(isContentType("application/octet-stream"))),
|
||||
u.route("POST", gitProjectPattern+`ssh-upload-pack\z`, git.SSHUploadPack(api)),
|
||||
|
||||
// CI Artifacts
|
||||
u.route("POST", apiPattern+`v4/jobs/[0-9]+/artifacts\z`, contentEncodingHandler(upload.Artifacts(api, signingProxy, preparer, &u.Config))),
|
||||
|
|
|
|||
139
yarn.lock
139
yarn.lock
|
|
@ -2012,87 +2012,76 @@
|
|||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz#5b2fb4d8cd44c05deef8a7b0e6deb9ccb8939d18"
|
||||
integrity sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==
|
||||
|
||||
"@sentry-internal/feedback@7.116.0":
|
||||
version "7.116.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.116.0.tgz#f1352b1a0d5fd7b7167775330ccf03bcc1b7892b"
|
||||
integrity sha512-tmfO+RTCrhIWMs3yg8X0axhbjWRZLsldSfoXBgfjNCk/XwkYiVGp7WnYVbb+IO+01mHCsis9uaYOBggLgFRB5Q==
|
||||
"@sentry-internal/browser-utils@8.8.0":
|
||||
version "8.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.8.0.tgz#5378f2c19d30a051f0912944a64096210348ef98"
|
||||
integrity sha512-yE4khknnGpAxy3TeAD9TU1eUqa0GUJ2xluIAsHKkL+RXg3AgEssMO3DBDUbpHp+QANIjzKmZIXtbdTV+1P26aQ==
|
||||
dependencies:
|
||||
"@sentry/core" "7.116.0"
|
||||
"@sentry/types" "7.116.0"
|
||||
"@sentry/utils" "7.116.0"
|
||||
"@sentry/core" "8.8.0"
|
||||
"@sentry/types" "8.8.0"
|
||||
"@sentry/utils" "8.8.0"
|
||||
|
||||
"@sentry-internal/replay-canvas@7.116.0":
|
||||
version "7.116.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-7.116.0.tgz#1cd4a85f99dd3cd61120e087232f5cbea21d5eb2"
|
||||
integrity sha512-Sy0ydY7A97JY/IFTIj8U25kHqR5rL9oBk3HFE5EK9Phw56irVhHzEwLWae0jlFeCQEWoBYqpPgO5vXsaYzrWvw==
|
||||
"@sentry-internal/feedback@8.8.0":
|
||||
version "8.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.8.0.tgz#6fb81846d09d3f4dce8e3fd85a8d0f1d7ab49418"
|
||||
integrity sha512-mybzWx99DuCJxYCVPx12NHVSVbSDF1goEo+rhDGYY8kqyn+snoVBLQtsSdDXYwZyssS1G7Gh6WhX+JVDKcQO9A==
|
||||
dependencies:
|
||||
"@sentry/core" "7.116.0"
|
||||
"@sentry/replay" "7.116.0"
|
||||
"@sentry/types" "7.116.0"
|
||||
"@sentry/utils" "7.116.0"
|
||||
"@sentry/core" "8.8.0"
|
||||
"@sentry/types" "8.8.0"
|
||||
"@sentry/utils" "8.8.0"
|
||||
|
||||
"@sentry-internal/tracing@7.116.0":
|
||||
version "7.116.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.116.0.tgz#af3e4e264c440aa5525b5877a10b9a0f870b40e3"
|
||||
integrity sha512-y5ppEmoOlfr77c/HqsEXR72092qmGYS4QE5gSz5UZFn9CiinEwGfEorcg2xIrrCuU7Ry/ZU2VLz9q3xd04drRA==
|
||||
"@sentry-internal/replay-canvas@8.8.0":
|
||||
version "8.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.8.0.tgz#9920120d8f08b67203b0d15cc48739287448ac9c"
|
||||
integrity sha512-LUoPi38Y8VRnxorIMmKLpfpf+jguhOsovMsZ3ZLc+FvMER62IIvSt4GKK4ARmUBX7+v3r61fdUWqxFs1j3uUTg==
|
||||
dependencies:
|
||||
"@sentry/core" "7.116.0"
|
||||
"@sentry/types" "7.116.0"
|
||||
"@sentry/utils" "7.116.0"
|
||||
"@sentry-internal/replay" "8.8.0"
|
||||
"@sentry/core" "8.8.0"
|
||||
"@sentry/types" "8.8.0"
|
||||
"@sentry/utils" "8.8.0"
|
||||
|
||||
"@sentry/browser@7.116.0":
|
||||
version "7.116.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.116.0.tgz#950c1a9672bf886c556c2c7b9198b90189e3f0c2"
|
||||
integrity sha512-2aosATT5qE+QLKgTmyF9t5Emsluy1MBczYNuPmLhDxGNfB+MA86S8u7Hb0CpxdwjS0nt14gmbiOtJHoeAF3uTw==
|
||||
"@sentry-internal/replay@8.8.0":
|
||||
version "8.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.8.0.tgz#b102c6429a55bae021bd3eb1d9ca1c3b95f9a59a"
|
||||
integrity sha512-gMRWcjpiLJl03JB4rTMN2I4HOOJ6z611kdhUBYc+RRAue13A6uCSIPElgvlCMREkVmr/8eUKrCcIrpqj9PDJ4w==
|
||||
dependencies:
|
||||
"@sentry-internal/feedback" "7.116.0"
|
||||
"@sentry-internal/replay-canvas" "7.116.0"
|
||||
"@sentry-internal/tracing" "7.116.0"
|
||||
"@sentry/core" "7.116.0"
|
||||
"@sentry/integrations" "7.116.0"
|
||||
"@sentry/replay" "7.116.0"
|
||||
"@sentry/types" "7.116.0"
|
||||
"@sentry/utils" "7.116.0"
|
||||
"@sentry-internal/browser-utils" "8.8.0"
|
||||
"@sentry/core" "8.8.0"
|
||||
"@sentry/types" "8.8.0"
|
||||
"@sentry/utils" "8.8.0"
|
||||
|
||||
"@sentry/core@7.116.0":
|
||||
version "7.116.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.116.0.tgz#7cff43134878a696b2b3b981ae384ec3db9ac8c3"
|
||||
integrity sha512-J6Wmjjx+o7RwST0weTU1KaKUAlzbc8MGkJV1rcHM9xjNTWTva+nrcCM3vFBagnk2Gm/zhwv3h0PvWEqVyp3U1Q==
|
||||
"@sentry/browser@8.8.0":
|
||||
version "8.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.8.0.tgz#0c3b83299ad26703e708f0534d4ac97876a010be"
|
||||
integrity sha512-TkmbjV9pGpQ+OfUtIE8DaU467w73NqPTX/w/+241VlKpE9HbfranMG0N8Bibgt59GwoNIiC0NhmKaMTZg79elQ==
|
||||
dependencies:
|
||||
"@sentry/types" "7.116.0"
|
||||
"@sentry/utils" "7.116.0"
|
||||
"@sentry-internal/browser-utils" "8.8.0"
|
||||
"@sentry-internal/feedback" "8.8.0"
|
||||
"@sentry-internal/replay" "8.8.0"
|
||||
"@sentry-internal/replay-canvas" "8.8.0"
|
||||
"@sentry/core" "8.8.0"
|
||||
"@sentry/types" "8.8.0"
|
||||
"@sentry/utils" "8.8.0"
|
||||
|
||||
"@sentry/integrations@7.116.0":
|
||||
version "7.116.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.116.0.tgz#b641342249da76cd2feb2fb5511424b66f967449"
|
||||
integrity sha512-UZb60gaF+7veh1Yv79RiGvgGYOnU6xA97H+hI6tKgc1uT20YpItO4X56Vhp0lvyEyUGFZzBRRH1jpMDPNGPkqw==
|
||||
"@sentry/core@8.8.0":
|
||||
version "8.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.8.0.tgz#0071a27e366abcca8bde9afffb537ae0086434ba"
|
||||
integrity sha512-SnQ42rOuUO03WvhS+2aogKhEzCW9cxpnpPzs2obxnS04KoAz7VL3oYyIwiACrRTlKpwdb9y6vuO89fDvgqPQbA==
|
||||
dependencies:
|
||||
"@sentry/core" "7.116.0"
|
||||
"@sentry/types" "7.116.0"
|
||||
"@sentry/utils" "7.116.0"
|
||||
localforage "^1.8.1"
|
||||
"@sentry/types" "8.8.0"
|
||||
"@sentry/utils" "8.8.0"
|
||||
|
||||
"@sentry/replay@7.116.0":
|
||||
version "7.116.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.116.0.tgz#cde921133c8927be92d60baf03c2b0aea73380f1"
|
||||
integrity sha512-OrpDtV54pmwZuKp3g7PDiJg6ruRMJKOCzK08TF7IPsKrr4x4UQn56rzMOiABVuTjuS8lNfAWDar6c6vxXFz5KA==
|
||||
"@sentry/types@8.8.0":
|
||||
version "8.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.8.0.tgz#dfbea2fd4cb3104b5128ccf37a45c56a411c397e"
|
||||
integrity sha512-2EOkyHoSOJyCRCsK/O6iA3wyELkRApfY7jNxsC/Amgb5ftuGl/rGO6B4dNKjMJNLNvlkEqZIANoUKOcClBH6yw==
|
||||
|
||||
"@sentry/utils@8.8.0":
|
||||
version "8.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.8.0.tgz#f1e940ef1fe8ce44075b3eac040238900d1cccf2"
|
||||
integrity sha512-agLqo9KlXacj7NOcdYZUYqTKlFcPXdTzCnC2u9J1LxDjru9cogbiw6yyDtxBg3kpgYZubfOPz/7F2z9wCjK1cw==
|
||||
dependencies:
|
||||
"@sentry-internal/tracing" "7.116.0"
|
||||
"@sentry/core" "7.116.0"
|
||||
"@sentry/types" "7.116.0"
|
||||
"@sentry/utils" "7.116.0"
|
||||
|
||||
"@sentry/types@7.116.0":
|
||||
version "7.116.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.116.0.tgz#0be3434e7e53c86db4993e668af1c3a65bfb7519"
|
||||
integrity sha512-QCCvG5QuQrwgKzV11lolNQPP2k67Q6HHD9vllZ/C4dkxkjoIym8Gy+1OgAN3wjsR0f/kG9o5iZyglgNpUVRapQ==
|
||||
|
||||
"@sentry/utils@7.116.0":
|
||||
version "7.116.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.116.0.tgz#f32463ab10f76f464274233a9df202e5357d17ff"
|
||||
integrity sha512-Vn9fcvwTq91wJvCd7WTMWozimqMi+dEZ3ie3EICELC2diONcN16ADFdzn65CQQbYwmUzRjN9EjDN2k41pKZWhQ==
|
||||
dependencies:
|
||||
"@sentry/types" "7.116.0"
|
||||
"@sentry/types" "8.8.0"
|
||||
|
||||
"@sinclair/typebox@^0.24.1":
|
||||
version "0.24.40"
|
||||
|
|
@ -9270,13 +9259,6 @@ levn@~0.3.0:
|
|||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
|
||||
lie@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
|
||||
integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==
|
||||
dependencies:
|
||||
immediate "~3.0.5"
|
||||
|
||||
lie@~3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
|
||||
|
|
@ -9346,13 +9328,6 @@ loader-utils@^2.0.0:
|
|||
emojis-list "^3.0.0"
|
||||
json5 "^2.1.2"
|
||||
|
||||
localforage@^1.8.1:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4"
|
||||
integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==
|
||||
dependencies:
|
||||
lie "3.1.1"
|
||||
|
||||
locate-path@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
|
||||
|
|
|
|||
Loading…
Reference in New Issue