Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-06-13 12:13:50 +00:00
parent de5a3e7e69
commit d939f38b0f
55 changed files with 844 additions and 390 deletions

View File

@ -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'

View File

@ -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;

View File

@ -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');

View File

@ -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;
}

View File

@ -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>

View File

@ -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 */

View File

@ -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;
}

View File

@ -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,

View File

@ -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,
};
}

View File

@ -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

View File

@ -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'

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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|

View File

@ -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.

View File

@ -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
#

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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`.

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -49,3 +49,5 @@ module Gitlab
end
end
end
Gitlab::Ci::Pipeline::Chain::Skip.prepend_mod

View File

@ -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}."

View File

@ -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",

View File

@ -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 {

View File

@ -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",

View File

@ -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)

View File

@ -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

View File

@ -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', () => {

View File

@ -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: [],
}),
},
]);
});
});

View File

@ -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}`, () => {

View File

@ -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 });
});

View File

@ -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

View File

@ -648,6 +648,7 @@ project:
- project_repository
- users
- maintainers
- owners_and_maintainers
- requesters
- namespace_members
- namespace_requesters

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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'

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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=

View File

@ -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
}
}

View File

@ -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
},
})
}

View File

@ -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)

View File

@ -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)

View File

@ -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
View File

@ -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"