Commit f292f676 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 3ebc63ca 286935e2
...@@ -656,7 +656,7 @@ class Repository ...@@ -656,7 +656,7 @@ class Repository
end end
end end
def tree(sha = :head, path = nil, recursive: false) def tree(sha = :head, path = nil, recursive: false, pagination_params: nil)
if sha == :head if sha == :head
return unless head_commit return unless head_commit
...@@ -667,7 +667,7 @@ class Repository ...@@ -667,7 +667,7 @@ class Repository
end end
end end
Tree.new(self, sha, path, recursive: recursive) Tree.new(self, sha, path, recursive: recursive, pagination_params: pagination_params)
end end
def blob_at_branch(branch_name, path) def blob_at_branch(branch_name, path)
......
...@@ -4,9 +4,9 @@ class Tree ...@@ -4,9 +4,9 @@ class Tree
include Gitlab::MarkupHelper include Gitlab::MarkupHelper
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
attr_accessor :repository, :sha, :path, :entries attr_accessor :repository, :sha, :path, :entries, :cursor
def initialize(repository, sha, path = '/', recursive: false) def initialize(repository, sha, path = '/', recursive: false, pagination_params: nil)
path = '/' if path.blank? path = '/' if path.blank?
@repository = repository @repository = repository
...@@ -14,7 +14,7 @@ class Tree ...@@ -14,7 +14,7 @@ class Tree
@path = path @path = path
git_repo = @repository.raw_repository git_repo = @repository.raw_repository
@entries = Gitlab::Git::Tree.where(git_repo, @sha, @path, recursive) @entries, @cursor = Gitlab::Git::Tree.where(git_repo, @sha, @path, recursive, pagination_params)
end end
def readme_path def readme_path
......
...@@ -6,20 +6,24 @@ ...@@ -6,20 +6,24 @@
.form-check .form-check
= f.check_box :auto_devops_enabled, class: 'form-check-input' = f.check_box :auto_devops_enabled, class: 'form-check-input'
= f.label :auto_devops_enabled, class: 'form-check-label' do = f.label :auto_devops_enabled, class: 'form-check-label' do
%strong= s_('CICD|Default to Auto DevOps pipeline for all projects') = s_('CICD|Default to Auto DevOps pipeline for all projects')
.form-text.text-muted .form-text.text-muted
= s_('CICD|The Auto DevOps pipeline runs by default in all projects with no CI/CD configuration file.') = s_('CICD|The Auto DevOps pipeline runs by default in all projects with no CI/CD configuration file.')
= link_to _('What is Auto DevOps?'), help_page_path('topics/autodevops/index.md'), target: '_blank' = link_to _('What is Auto DevOps?'), help_page_path('topics/autodevops/index.md'), target: '_blank'
.form-group .form-group
= f.label :auto_devops_domain, s_('AdminSettings|Auto DevOps domain'), class: 'label-bold' = f.label :auto_devops_domain, s_('AdminSettings|Auto DevOps domain'), class: 'label-bold'
= f.text_field :auto_devops_domain, class: 'form-control gl-form-input', placeholder: 'domain.com' = f.text_field :auto_devops_domain, class: 'form-control gl-form-input', placeholder: 'example.com'
.form-text.text-muted .form-text.text-muted
= s_("AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages.") = s_("AdminSettings|The default domain to use for Auto Review Apps and Auto Deploy stages in all projects.")
= link_to _('Learn more.'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-review-apps'), target: '_blank'
.form-group .form-group
.form-check .form-check
= f.check_box :shared_runners_enabled, class: 'form-check-input' = f.check_box :shared_runners_enabled, class: 'form-check-input'
= f.label :shared_runners_enabled, class: 'form-check-label' do = f.label :shared_runners_enabled, class: 'form-check-label' do
= s_("AdminSettings|Enable shared runners for new projects") = s_("AdminSettings|Enable shared runners for new projects")
.form-text.text-muted
= s_("AdminSettings|All new projects can use the instance's shared runners by default.")
= render_if_exists 'admin/application_settings/shared_runners_minutes_setting', form: f = render_if_exists 'admin/application_settings/shared_runners_minutes_setting', form: f
...@@ -31,32 +35,32 @@ ...@@ -31,32 +35,32 @@
= f.label :max_artifacts_size, _('Maximum artifacts size (MB)'), class: 'label-bold' = f.label :max_artifacts_size, _('Maximum artifacts size (MB)'), class: 'label-bold'
= f.number_field :max_artifacts_size, class: 'form-control gl-form-input' = f.number_field :max_artifacts_size, class: 'form-control gl-form-input'
.form-text.text-muted .form-text.text-muted
= _("Set the maximum file size for each job's artifacts") = _("The maximum file size for job artifacts.")
= link_to sprite_icon('question-o'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size') = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size')
.form-group .form-group
= f.label :default_artifacts_expire_in, _('Default artifacts expiration'), class: 'label-bold' = f.label :default_artifacts_expire_in, _('Default artifacts expiration'), class: 'label-bold'
= f.text_field :default_artifacts_expire_in, class: 'form-control gl-form-input' = f.text_field :default_artifacts_expire_in, class: 'form-control gl-form-input'
.form-text.text-muted .form-text.text-muted
= html_escape(_("Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: %{code_open}4 mins 2 sec%{code_close}, %{code_open}2h42min%{code_close}.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe } = html_escape(_("The default expiration time for job artifacts. 0 for unlimited. The default unit is in seconds, but you can use other units, for example %{code_open}4 mins 2 sec%{code_close}, %{code_open}2h42min%{code_close}.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
= link_to sprite_icon('question-o'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration') = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
.form-group .form-group
.form-check .form-check
= f.check_box :keep_latest_artifact, class: 'form-check-input' = f.check_box :keep_latest_artifact, class: 'form-check-input'
= f.label :keep_latest_artifact, class: 'form-check-label' do = f.label :keep_latest_artifact, class: 'form-check-label' do
%strong = s_('AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines')
= s_('AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines')
.form-text.text-muted .form-text.text-muted
= s_('AdminSettings|The latest artifacts for all jobs in the most recent successful pipelines in each project are stored and do not expire.') = s_('AdminSettings|The latest artifacts for all jobs in the most recent successful pipelines in each project are stored and do not expire.')
.form-group .form-group
= f.label :archive_builds_in_human_readable, _('Archive jobs'), class: 'label-bold' = f.label :archive_builds_in_human_readable, _('Archive jobs'), class: 'label-bold'
= f.text_field :archive_builds_in_human_readable, class: 'form-control gl-form-input', placeholder: 'never' = f.text_field :archive_builds_in_human_readable, class: 'form-control gl-form-input'
.form-text.text-muted .form-text.text-muted
= html_escape(_("Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: %{code_open}15 days%{code_close}, %{code_open}1 month%{code_close}, %{code_open}2 years%{code_close}.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe } = html_escape(_("Jobs older than the configured time are considered expired and are archived. Archived jobs can no longer be retried. Leave empty to never archive jobs automatically. The default unit is in days, but you can use other units, for example %{code_open}15 days%{code_close}, %{code_open}1 month%{code_close}, %{code_open}2 years%{code_close}. Minimum value is 1 day.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
= link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'archive-jobs')
.form-group .form-group
.form-check .form-check
= f.check_box :protected_ci_variables, class: 'form-check-input' = f.check_box :protected_ci_variables, class: 'form-check-input'
= f.label :protected_ci_variables, class: 'form-check-label' do = f.label :protected_ci_variables, class: 'form-check-label' do
%strong= s_('AdminSettings|Protect CI/CD variables by default') = s_('AdminSettings|Protect CI/CD variables by default')
.form-text.text-muted .form-text.text-muted
= s_('AdminSettings|New CI/CD variables in projects and groups default to protected.') = s_('AdminSettings|New CI/CD variables in projects and groups default to protected.')
.form-group .form-group
......
...@@ -45,7 +45,7 @@ Note that currently on GitLab.com, any messages in `production.log` aren't ...@@ -45,7 +45,7 @@ Note that currently on GitLab.com, any messages in `production.log` aren't
indexed by Elasticsearch due to the sheer volume and noise. They indexed by Elasticsearch due to the sheer volume and noise. They
do end up in Google Stackdriver, but it is still harder to search for do end up in Google Stackdriver, but it is still harder to search for
logs there. See the [GitLab.com logging logs there. See the [GitLab.com logging
documentation](https://gitlab.com/gitlab-com/runbooks/blob/master/logging/doc/README.md) documentation](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging)
for more details. for more details.
## Use structured (JSON) logging ## Use structured (JSON) logging
......
...@@ -13,7 +13,7 @@ import BurnCharts from 'ee/burndown_chart/components/burn_charts.vue'; ...@@ -13,7 +13,7 @@ import BurnCharts from 'ee/burndown_chart/components/burn_charts.vue';
import { TYPE_ITERATION } from '~/graphql_shared/constants'; import { TYPE_ITERATION } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils'; import { convertToGraphQLId } from '~/graphql_shared/utils';
import { formatDate } from '~/lib/utils/datetime_utility'; import { formatDate } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale'; import { __, s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { Namespace } from '../constants'; import { Namespace } from '../constants';
import query from '../queries/iteration.query.graphql'; import query from '../queries/iteration.query.graphql';
...@@ -51,7 +51,9 @@ export default { ...@@ -51,7 +51,9 @@ export default {
return data[this.namespaceType]?.iterations?.nodes[0] || {}; return data[this.namespaceType]?.iterations?.nodes[0] || {};
}, },
error(err) { error(err) {
this.error = err.message; this.error = s__('Iterations|Unable to find iteration.');
// eslint-disable-next-line no-console
console.error(err.message);
}, },
}, },
}, },
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
query Iteration($fullPath: ID!, $id: ID!, $isGroup: Boolean = true) { query Iteration($fullPath: ID!, $id: ID!, $isGroup: Boolean = true) {
group(fullPath: $fullPath) @include(if: $isGroup) { group(fullPath: $fullPath) @include(if: $isGroup) {
id
iterations(id: $id, first: 1, includeAncestors: true) { iterations(id: $id, first: 1, includeAncestors: true) {
nodes { nodes {
...IterationReport ...IterationReport
...@@ -10,6 +11,7 @@ query Iteration($fullPath: ID!, $id: ID!, $isGroup: Boolean = true) { ...@@ -10,6 +11,7 @@ query Iteration($fullPath: ID!, $id: ID!, $isGroup: Boolean = true) {
} }
project(fullPath: $fullPath) @skip(if: $isGroup) { project(fullPath: $fullPath) @skip(if: $isGroup) {
id
iterations(id: $id, first: 1, includeAncestors: true) { iterations(id: $id, first: 1, includeAncestors: true) {
nodes { nodes {
...IterationReport ...IterationReport
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# todo: should this use IterationsCadenceID! ? # todo: should this use IterationsCadenceID! ?
query IterationCadence($fullPath: ID!, $id: ID!) { query IterationCadence($fullPath: ID!, $id: ID!) {
group(fullPath: $fullPath) { group(fullPath: $fullPath) {
id
iterationCadences(id: $id) { iterationCadences(id: $id) {
nodes { nodes {
...IterationCadence ...IterationCadence
......
...@@ -10,6 +10,7 @@ query IterationIssues( ...@@ -10,6 +10,7 @@ query IterationIssues(
$lastPageSize: Int $lastPageSize: Int
) { ) {
group(fullPath: $fullPath) @include(if: $isGroup) { group(fullPath: $fullPath) @include(if: $isGroup) {
id
issues( issues(
iterationId: [$id] iterationId: [$id]
before: $beforeCursor before: $beforeCursor
...@@ -22,6 +23,7 @@ query IterationIssues( ...@@ -22,6 +23,7 @@ query IterationIssues(
} }
} }
project(fullPath: $fullPath) @skip(if: $isGroup) { project(fullPath: $fullPath) @skip(if: $isGroup) {
id
issues( issues(
iterationId: [$id] iterationId: [$id]
before: $beforeCursor before: $beforeCursor
......
...@@ -11,6 +11,7 @@ query IterationIssuesWithLabelFilter( ...@@ -11,6 +11,7 @@ query IterationIssuesWithLabelFilter(
$lastPageSize: Int $lastPageSize: Int
) { ) {
group(fullPath: $fullPath) @include(if: $isGroup) { group(fullPath: $fullPath) @include(if: $isGroup) {
id
issues( issues(
iterationId: [$id] iterationId: [$id]
labelName: $labelName labelName: $labelName
...@@ -24,6 +25,7 @@ query IterationIssuesWithLabelFilter( ...@@ -24,6 +25,7 @@ query IterationIssuesWithLabelFilter(
} }
} }
project(fullPath: $fullPath) @skip(if: $isGroup) { project(fullPath: $fullPath) @skip(if: $isGroup) {
id
issues( issues(
iterationId: [$id] iterationId: [$id]
labelName: $labelName labelName: $labelName
......
...@@ -11,6 +11,7 @@ query Iterations( ...@@ -11,6 +11,7 @@ query Iterations(
$lastPageSize: Int $lastPageSize: Int
) { ) {
group(fullPath: $fullPath) @include(if: $isGroup) { group(fullPath: $fullPath) @include(if: $isGroup) {
id
iterations( iterations(
state: $state state: $state
before: $beforeCursor before: $beforeCursor
...@@ -27,6 +28,7 @@ query Iterations( ...@@ -27,6 +28,7 @@ query Iterations(
} }
} }
project(fullPath: $fullPath) @skip(if: $isGroup) { project(fullPath: $fullPath) @skip(if: $isGroup) {
id
iterations( iterations(
state: $state state: $state
before: $beforeCursor before: $beforeCursor
......
...@@ -2,5 +2,5 @@ ...@@ -2,5 +2,5 @@
= form.label :shared_runners_minutes, _('Pipeline minutes quota'), class: 'label-bold' = form.label :shared_runners_minutes, _('Pipeline minutes quota'), class: 'label-bold'
= form.number_field :shared_runners_minutes, class: 'form-control gl-form-input' = form.number_field :shared_runners_minutes, class: 'form-control gl-form-input'
.form-text.text-muted .form-text.text-muted
= _('Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited.') = _('The maximum number of pipeline minutes that a group can use on shared runners per month. 0 for unlimited.')
= link_to sprite_icon('question-o'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'shared-runners-pipeline-minutes-quota'), target: '_blank' = link_to _('What are shared runner pipeline minutes?'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'shared-runners-pipeline-minutes-quota'), target: '_blank'
...@@ -51,6 +51,7 @@ describe('Iteration cadence form', () => { ...@@ -51,6 +51,7 @@ describe('Iteration cadence form', () => {
const getCadenceSuccess = { const getCadenceSuccess = {
data: { data: {
group: { group: {
id: 'gid://gitlab/Group/114',
iterationCadences: { iterationCadences: {
nodes: [iterationCadence], nodes: [iterationCadence],
}, },
......
...@@ -38,7 +38,9 @@ describe('Iteration Form', () => { ...@@ -38,7 +38,9 @@ describe('Iteration Form', () => {
}; };
const readMutationSuccess = { const readMutationSuccess = {
data: { group: { iterations: { nodes: [iteration] }, errors: [] } }, data: {
group: { id: 'gid://gitlab/Group/114', iterations: { nodes: [iteration] }, errors: [] },
},
}; };
const createMutationSuccess = { data: { iterationCreate: { iteration, errors: [] } } }; const createMutationSuccess = { data: { iterationCreate: { iteration, errors: [] } } };
const createMutationFailure = { const createMutationFailure = {
......
...@@ -56,7 +56,6 @@ describe('Iterations report issues', () => { ...@@ -56,7 +56,6 @@ describe('Iterations report issues', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
it('shows spinner while loading', () => { it('shows spinner while loading', () => {
......
...@@ -39,7 +39,8 @@ describe('Iterations report', () => { ...@@ -39,7 +39,8 @@ describe('Iterations report', () => {
const mountComponent = ({ const mountComponent = ({
props = defaultProps, props = defaultProps,
iterationQueryHandler = jest.fn().mockResolvedValue(mockGroupIterations), mockQueryResponse = mockGroupIterations,
iterationQueryHandler = jest.fn().mockResolvedValue(mockQueryResponse),
} = {}) => { } = {}) => {
localVue.use(VueApollo); localVue.use(VueApollo);
mockApollo = createMockApollo([[query, iterationQueryHandler]]); mockApollo = createMockApollo([[query, iterationQueryHandler]]);
...@@ -104,7 +105,7 @@ describe('Iterations report', () => { ...@@ -104,7 +105,7 @@ describe('Iterations report', () => {
], ],
])('when viewing an iteration in a %s', (_, props, mockIteration, expectedParams) => { ])('when viewing an iteration in a %s', (_, props, mockIteration, expectedParams) => {
it('calls a query with correct parameters', () => { it('calls a query with correct parameters', () => {
const iterationQueryHandler = jest.fn(); const iterationQueryHandler = jest.fn().mockResolvedValue(mockIteration);
mountComponent({ mountComponent({
props, props,
iterationQueryHandler, iterationQueryHandler,
...@@ -128,7 +129,6 @@ describe('Iterations report', () => { ...@@ -128,7 +129,6 @@ describe('Iterations report', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
describe('empty state', () => { describe('empty state', () => {
...@@ -137,6 +137,7 @@ describe('Iterations report', () => { ...@@ -137,6 +137,7 @@ describe('Iterations report', () => {
iterationQueryHandler: jest.fn().mockResolvedValue({ iterationQueryHandler: jest.fn().mockResolvedValue({
data: { data: {
group: { group: {
id: 'gid://gitlab/Group/1',
iterations: { iterations: {
nodes: [], nodes: [],
}, },
...@@ -212,12 +213,18 @@ describe('Iterations report', () => { ...@@ -212,12 +213,18 @@ describe('Iterations report', () => {
'when user $description and they are viewing an iteration within a $namespaceType', 'when user $description and they are viewing an iteration within a $namespaceType',
({ canEdit, namespaceType, canEditIteration }) => { ({ canEdit, namespaceType, canEditIteration }) => {
beforeEach(() => { beforeEach(() => {
const mockQueryResponse = {
[Namespace.Group]: mockGroupIterations,
[Namespace.Project]: mockProjectIterations,
}[namespaceType];
mountComponent({ mountComponent({
props: { props: {
...defaultProps, ...defaultProps,
canEditIteration, canEditIteration,
namespaceType, namespaceType,
}, },
mockQueryResponse,
}); });
}); });
......
...@@ -14,6 +14,7 @@ export const mockIterationNode = { ...@@ -14,6 +14,7 @@ export const mockIterationNode = {
export const mockGroupIterations = { export const mockGroupIterations = {
data: { data: {
group: { group: {
id: 'gid://gitlab/Group/114',
iterations: { iterations: {
nodes: [mockIterationNode], nodes: [mockIterationNode],
__typename: 'IterationConnection', __typename: 'IterationConnection',
...@@ -26,6 +27,7 @@ export const mockGroupIterations = { ...@@ -26,6 +27,7 @@ export const mockGroupIterations = {
export const mockProjectIterations = { export const mockProjectIterations = {
data: { data: {
project: { project: {
id: 'gid://gitlab/Project/114',
iterations: { iterations: {
nodes: [mockIterationNode], nodes: [mockIterationNode],
__typename: 'IterationConnection', __typename: 'IterationConnection',
......
...@@ -14,9 +14,12 @@ module Gitlab ...@@ -14,9 +14,12 @@ module Gitlab
include Gitlab::Git::RuggedImpl::UseRugged include Gitlab::Git::RuggedImpl::UseRugged
override :tree_entries override :tree_entries
def tree_entries(repository, sha, path, recursive) def tree_entries(repository, sha, path, recursive, pagination_params = nil)
if use_rugged?(repository, :rugged_tree_entries) if use_rugged?(repository, :rugged_tree_entries)
execute_rugged_call(:tree_entries_with_flat_path_from_rugged, repository, sha, path, recursive) [
execute_rugged_call(:tree_entries_with_flat_path_from_rugged, repository, sha, path, recursive),
nil
]
else else
super super
end end
......
...@@ -15,15 +15,15 @@ module Gitlab ...@@ -15,15 +15,15 @@ module Gitlab
# Uses rugged for raw objects # Uses rugged for raw objects
# #
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/320 # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/320
def where(repository, sha, path = nil, recursive = false) def where(repository, sha, path = nil, recursive = false, pagination_params = nil)
path = nil if path == '' || path == '/' path = nil if path == '' || path == '/'
tree_entries(repository, sha, path, recursive) tree_entries(repository, sha, path, recursive, pagination_params)
end end
def tree_entries(repository, sha, path, recursive) def tree_entries(repository, sha, path, recursive, pagination_params = nil)
wrapped_gitaly_errors do wrapped_gitaly_errors do
repository.gitaly_commit_client.tree_entries(repository, sha, path, recursive) repository.gitaly_commit_client.tree_entries(repository, sha, path, recursive, pagination_params)
end end
end end
......
...@@ -111,17 +111,22 @@ module Gitlab ...@@ -111,17 +111,22 @@ module Gitlab
nil nil
end end
def tree_entries(repository, revision, path, recursive) def tree_entries(repository, revision, path, recursive, pagination_params)
request = Gitaly::GetTreeEntriesRequest.new( request = Gitaly::GetTreeEntriesRequest.new(
repository: @gitaly_repo, repository: @gitaly_repo,
revision: encode_binary(revision), revision: encode_binary(revision),
path: path.present? ? encode_binary(path) : '.', path: path.present? ? encode_binary(path) : '.',
recursive: recursive recursive: recursive,
pagination_params: pagination_params
) )
request.sort = Gitaly::GetTreeEntriesRequest::SortBy::TREES_FIRST if pagination_params
response = GitalyClient.call(@repository.storage, :commit_service, :get_tree_entries, request, timeout: GitalyClient.medium_timeout) response = GitalyClient.call(@repository.storage, :commit_service, :get_tree_entries, request, timeout: GitalyClient.medium_timeout)
response.flat_map do |message| cursor = nil
entries = response.flat_map do |message|
cursor = message.pagination_cursor if message.pagination_cursor
message.entries.map do |gitaly_tree_entry| message.entries.map do |gitaly_tree_entry|
Gitlab::Git::Tree.new( Gitlab::Git::Tree.new(
id: gitaly_tree_entry.oid, id: gitaly_tree_entry.oid,
...@@ -135,6 +140,8 @@ module Gitlab ...@@ -135,6 +140,8 @@ module Gitlab
) )
end end
end end
[entries, cursor]
end end
def commit_count(ref, options = {}) def commit_count(ref, options = {})
......
...@@ -2362,6 +2362,9 @@ msgstr "" ...@@ -2362,6 +2362,9 @@ msgstr ""
msgid "AdminProjects|Delete Project %{projectName}?" msgid "AdminProjects|Delete Project %{projectName}?"
msgstr "" msgstr ""
msgid "AdminSettings|All new projects can use the instance's shared runners by default."
msgstr ""
msgid "AdminSettings|Auto DevOps domain" msgid "AdminSettings|Auto DevOps domain"
msgstr "" msgstr ""
...@@ -2407,7 +2410,7 @@ msgstr "" ...@@ -2407,7 +2410,7 @@ msgstr ""
msgid "AdminSettings|Set a CI/CD template as the required pipeline configuration for all projects in the instance. Project CI/CD configuration merges into the required pipeline configuration when the pipeline runs. %{link_start}What is a required pipeline configuration?%{link_end}" msgid "AdminSettings|Set a CI/CD template as the required pipeline configuration for all projects in the instance. Project CI/CD configuration merges into the required pipeline configuration when the pipeline runs. %{link_start}What is a required pipeline configuration?%{link_end}"
msgstr "" msgstr ""
msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." msgid "AdminSettings|The default domain to use for Auto Review Apps and Auto Deploy stages in all projects."
msgstr "" msgstr ""
msgid "AdminSettings|The default name for the initial branch of new repositories created in the instance." msgid "AdminSettings|The default name for the initial branch of new repositories created in the instance."
...@@ -18806,6 +18809,9 @@ msgstr "" ...@@ -18806,6 +18809,9 @@ msgstr ""
msgid "Jobs fail if they run longer than the timeout time. Input value is in seconds by default. Human readable input is also accepted, for example %{code_open}1 hour%{code_close}." msgid "Jobs fail if they run longer than the timeout time. Input value is in seconds by default. Human readable input is also accepted, for example %{code_open}1 hour%{code_close}."
msgstr "" msgstr ""
msgid "Jobs older than the configured time are considered expired and are archived. Archived jobs can no longer be retried. Leave empty to never archive jobs automatically. The default unit is in days, but you can use other units, for example %{code_open}15 days%{code_close}, %{code_open}1 month%{code_close}, %{code_open}2 years%{code_close}. Minimum value is 1 day."
msgstr ""
msgid "Jobs|Are you sure you want to proceed?" msgid "Jobs|Are you sure you want to proceed?"
msgstr "" msgstr ""
...@@ -30006,24 +30012,12 @@ msgstr "" ...@@ -30006,24 +30012,12 @@ msgstr ""
msgid "Set the default branch for this project. All merge requests and commits are made against this branch unless you specify a different one." msgid "Set the default branch for this project. All merge requests and commits are made against this branch unless you specify a different one."
msgstr "" msgstr ""
msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: %{code_open}4 mins 2 sec%{code_close}, %{code_open}2h42min%{code_close}."
msgstr ""
msgid "Set the due date to %{due_date}." msgid "Set the due date to %{due_date}."
msgstr "" msgstr ""
msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: %{code_open}15 days%{code_close}, %{code_open}1 month%{code_close}, %{code_open}2 years%{code_close}."
msgstr ""
msgid "Set the iteration to %{iteration_reference}." msgid "Set the iteration to %{iteration_reference}."
msgstr "" msgstr ""
msgid "Set the maximum file size for each job's artifacts"
msgstr ""
msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
msgstr ""
msgid "Set the milestone to %{milestone_reference}." msgid "Set the milestone to %{milestone_reference}."
msgstr "" msgstr ""
...@@ -32690,6 +32684,9 @@ msgstr "" ...@@ -32690,6 +32684,9 @@ msgstr ""
msgid "The default branch for this project has been changed. Please update your bookmarks." msgid "The default branch for this project has been changed. Please update your bookmarks."
msgstr "" msgstr ""
msgid "The default expiration time for job artifacts. 0 for unlimited. The default unit is in seconds, but you can use other units, for example %{code_open}4 mins 2 sec%{code_close}, %{code_open}2h42min%{code_close}."
msgstr ""
msgid "The dependency list details information about the components used within your project." msgid "The dependency list details information about the components used within your project."
msgstr "" msgstr ""
...@@ -32845,12 +32842,18 @@ msgstr "" ...@@ -32845,12 +32842,18 @@ msgstr ""
msgid "The maximum file size allowed is %{size}." msgid "The maximum file size allowed is %{size}."
msgstr "" msgstr ""
msgid "The maximum file size for job artifacts."
msgstr ""
msgid "The maximum file size in megabytes for individual job artifacts." msgid "The maximum file size in megabytes for individual job artifacts."
msgstr "" msgstr ""
msgid "The maximum file size is %{size}." msgid "The maximum file size is %{size}."
msgstr "" msgstr ""
msgid "The maximum number of pipeline minutes that a group can use on shared runners per month. 0 for unlimited."
msgstr ""
msgid "The maximum number of slices allowed to run concurrently during Elasticsearch reindexing. Learn more about %{max_slices_running_link_start}maximum running slices configuration%{max_slices_link_end}." msgid "The maximum number of slices allowed to run concurrently during Elasticsearch reindexing. Learn more about %{max_slices_running_link_start}maximum running slices configuration%{max_slices_link_end}."
msgstr "" msgstr ""
...@@ -36968,6 +36971,9 @@ msgstr "" ...@@ -36968,6 +36971,9 @@ msgstr ""
msgid "What are project audit events?" msgid "What are project audit events?"
msgstr "" msgstr ""
msgid "What are shared runner pipeline minutes?"
msgstr ""
msgid "What are you searching for?" msgid "What are you searching for?"
msgstr "" msgstr ""
......
...@@ -70,7 +70,7 @@ gitlab: ...@@ -70,7 +70,7 @@ gitlab:
memory: 800M memory: 800M
limits: limits:
cpu: 450m cpu: 450m
memory: 1200M memory: 2000M
webservice: webservice:
resources: resources:
requests: requests:
......
...@@ -6,29 +6,44 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do ...@@ -6,29 +6,44 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
shared_examples :repo do shared_examples :repo do
let(:tree) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID) } subject(:tree) { Gitlab::Git::Tree.where(repository, sha, path, recursive, pagination_params) }
it { expect(tree).to be_kind_of Array } let(:sha) { SeedRepo::Commit::ID }
it { expect(tree.empty?).to be_falsey } let(:path) { nil }
it { expect(tree.count(&:dir?)).to eq(2) } let(:recursive) { false }
it { expect(tree.count(&:file?)).to eq(10) } let(:pagination_params) { nil }
it { expect(tree.count(&:submodule?)).to eq(2) }
it 'returns an empty array when called with an invalid ref' do let(:entries) { tree.first }
expect(described_class.where(repository, 'foobar-does-not-exist')).to eq([]) let(:cursor) { tree.second }
it { expect(entries).to be_kind_of Array }
it { expect(entries.empty?).to be_falsey }
it { expect(entries.count(&:dir?)).to eq(2) }
it { expect(entries.count(&:file?)).to eq(10) }
it { expect(entries.count(&:submodule?)).to eq(2) }
it { expect(cursor&.next_cursor).to be_blank }
context 'with an invalid ref' do
let(:sha) { 'foobar-does-not-exist' }
it { expect(entries).to eq([]) }
it { expect(cursor).to be_nil }
end end
it 'returns a list of tree objects' do context 'when path is provided' do
entries = described_class.where(repository, SeedRepo::Commit::ID, 'files', true) let(:path) { 'files' }
let(:recursive) { true }
expect(entries.map(&:path)).to include('files/html', it 'returns a list of tree objects' do
'files/markdown/ruby-style-guide.md') expect(entries.map(&:path)).to include('files/html',
expect(entries.count).to be >= 10 'files/markdown/ruby-style-guide.md')
expect(entries).to all(be_a(Gitlab::Git::Tree)) expect(entries.count).to be >= 10
expect(entries).to all(be_a(Gitlab::Git::Tree))
end
end end
describe '#dir?' do describe '#dir?' do
let(:dir) { tree.select(&:dir?).first } let(:dir) { entries.select(&:dir?).first }
it { expect(dir).to be_kind_of Gitlab::Git::Tree } it { expect(dir).to be_kind_of Gitlab::Git::Tree }
it { expect(dir.id).to eq('3c122d2b7830eca25235131070602575cf8b41a1') } it { expect(dir.id).to eq('3c122d2b7830eca25235131070602575cf8b41a1') }
...@@ -41,7 +56,8 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do ...@@ -41,7 +56,8 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do
context :subdir do context :subdir do
# rubocop: disable Rails/FindBy # rubocop: disable Rails/FindBy
# This is not ActiveRecord where..first # This is not ActiveRecord where..first
let(:subdir) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID, 'files').first } let(:path) { 'files' }
let(:subdir) { entries.first }
# rubocop: enable Rails/FindBy # rubocop: enable Rails/FindBy
it { expect(subdir).to be_kind_of Gitlab::Git::Tree } it { expect(subdir).to be_kind_of Gitlab::Git::Tree }
...@@ -55,7 +71,8 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do ...@@ -55,7 +71,8 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do
context :subdir_file do context :subdir_file do
# rubocop: disable Rails/FindBy # rubocop: disable Rails/FindBy
# This is not ActiveRecord where..first # This is not ActiveRecord where..first
let(:subdir_file) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID, 'files/ruby').first } let(:path) { 'files/ruby' }
let(:subdir_file) { entries.first }
# rubocop: enable Rails/FindBy # rubocop: enable Rails/FindBy
it { expect(subdir_file).to be_kind_of Gitlab::Git::Tree } it { expect(subdir_file).to be_kind_of Gitlab::Git::Tree }
...@@ -68,10 +85,11 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do ...@@ -68,10 +85,11 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do
context :flat_path do context :flat_path do
let(:filename) { 'files/flat/path/correct/content.txt' } let(:filename) { 'files/flat/path/correct/content.txt' }
let(:oid) { create_file(filename) } let(:sha) { create_file(filename) }
let(:path) { 'files/flat' }
# rubocop: disable Rails/FindBy # rubocop: disable Rails/FindBy
# This is not ActiveRecord where..first # This is not ActiveRecord where..first
let(:subdir_file) { Gitlab::Git::Tree.where(repository, oid, 'files/flat').first } let(:subdir_file) { entries.first }
# rubocop: enable Rails/FindBy # rubocop: enable Rails/FindBy
let(:repository_rugged) { Rugged::Repository.new(File.join(SEED_STORAGE_PATH, TEST_REPO_PATH)) } let(:repository_rugged) { Rugged::Repository.new(File.join(SEED_STORAGE_PATH, TEST_REPO_PATH)) }
...@@ -116,7 +134,7 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do ...@@ -116,7 +134,7 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do
end end
describe '#file?' do describe '#file?' do
let(:file) { tree.select(&:file?).first } let(:file) { entries.select(&:file?).first }
it { expect(file).to be_kind_of Gitlab::Git::Tree } it { expect(file).to be_kind_of Gitlab::Git::Tree }
it { expect(file.id).to eq('dfaa3f97ca337e20154a98ac9d0be76ddd1fcc82') } it { expect(file.id).to eq('dfaa3f97ca337e20154a98ac9d0be76ddd1fcc82') }
...@@ -125,21 +143,21 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do ...@@ -125,21 +143,21 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do
end end
describe '#readme?' do describe '#readme?' do
let(:file) { tree.select(&:readme?).first } let(:file) { entries.select(&:readme?).first }
it { expect(file).to be_kind_of Gitlab::Git::Tree } it { expect(file).to be_kind_of Gitlab::Git::Tree }
it { expect(file.name).to eq('README.md') } it { expect(file.name).to eq('README.md') }
end end
describe '#contributing?' do describe '#contributing?' do
let(:file) { tree.select(&:contributing?).first } let(:file) { entries.select(&:contributing?).first }
it { expect(file).to be_kind_of Gitlab::Git::Tree } it { expect(file).to be_kind_of Gitlab::Git::Tree }
it { expect(file.name).to eq('CONTRIBUTING.md') } it { expect(file.name).to eq('CONTRIBUTING.md') }
end end
describe '#submodule?' do describe '#submodule?' do
let(:submodule) { tree.select(&:submodule?).first } let(:submodule) { entries.select(&:submodule?).first }
it { expect(submodule).to be_kind_of Gitlab::Git::Tree } it { expect(submodule).to be_kind_of Gitlab::Git::Tree }
it { expect(submodule.id).to eq('79bceae69cb5750d6567b223597999bfa91cb3b9') } it { expect(submodule.id).to eq('79bceae69cb5750d6567b223597999bfa91cb3b9') }
...@@ -149,7 +167,16 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do ...@@ -149,7 +167,16 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do
end end
describe '.where with Gitaly enabled' do describe '.where with Gitaly enabled' do
it_behaves_like :repo it_behaves_like :repo do
context 'with pagination parameters' do
let(:pagination_params) { { limit: 3, page_token: nil } }
it 'returns paginated list of tree objects' do
expect(entries.count).to eq(3)
expect(cursor.next_cursor).to be_present
end
end
end
end end
describe '.where with Rugged enabled', :enable_rugged do describe '.where with Rugged enabled', :enable_rugged do
...@@ -161,6 +188,15 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do ...@@ -161,6 +188,15 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do
described_class.where(repository, SeedRepo::Commit::ID, 'files', false) described_class.where(repository, SeedRepo::Commit::ID, 'files', false)
end end
it_behaves_like :repo it_behaves_like :repo do
context 'with pagination parameters' do
let(:pagination_params) { { limit: 3, page_token: nil } }
it 'does not support pagination' do
expect(entries.count).to be >= 10
expect(cursor).to be_nil
end
end
end
end end
end end
...@@ -169,7 +169,11 @@ RSpec.describe Gitlab::GitalyClient::CommitService do ...@@ -169,7 +169,11 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
end end
describe '#tree_entries' do describe '#tree_entries' do
subject { client.tree_entries(repository, revision, path, recursive, pagination_params) }
let(:path) { '/' } let(:path) { '/' }
let(:recursive) { false }
let(:pagination_params) { nil }
it 'sends a get_tree_entries message' do it 'sends a get_tree_entries message' do
expect_any_instance_of(Gitaly::CommitService::Stub) expect_any_instance_of(Gitaly::CommitService::Stub)
...@@ -177,7 +181,7 @@ RSpec.describe Gitlab::GitalyClient::CommitService do ...@@ -177,7 +181,7 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([]) .and_return([])
client.tree_entries(repository, revision, path, false) is_expected.to eq([[], nil])
end end
context 'with UTF-8 params strings' do context 'with UTF-8 params strings' do
...@@ -190,7 +194,26 @@ RSpec.describe Gitlab::GitalyClient::CommitService do ...@@ -190,7 +194,26 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([]) .and_return([])
client.tree_entries(repository, revision, path, false) is_expected.to eq([[], nil])
end
end
context 'with pagination parameters' do
let(:pagination_params) { { limit: 3, page_token: nil } }
it 'responds with a pagination cursor' do
pagination_cursor = Gitaly::PaginationCursor.new(next_cursor: 'aabbccdd')
response = Gitaly::GetTreeEntriesResponse.new(
entries: [],
pagination_cursor: pagination_cursor
)
expect_any_instance_of(Gitaly::CommitService::Stub)
.to receive(:get_tree_entries)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([response])
is_expected.to eq([[], pagination_cursor])
end end
end end
end end
......
...@@ -2523,24 +2523,46 @@ RSpec.describe Repository do ...@@ -2523,24 +2523,46 @@ RSpec.describe Repository do
end end
shared_examples '#tree' do shared_examples '#tree' do
subject { repository.tree(sha, path, recursive: recursive, pagination_params: pagination_params) }
let(:sha) { :head }
let(:path) { nil }
let(:recursive) { false }
let(:pagination_params) { nil }
context 'using a non-existing repository' do context 'using a non-existing repository' do
before do before do
allow(repository).to receive(:head_commit).and_return(nil) allow(repository).to receive(:head_commit).and_return(nil)
end end
it 'returns nil' do it { is_expected.to be_nil }
expect(repository.tree(:head)).to be_nil
end context 'when path is defined' do
let(:path) { 'README.md' }
it 'returns nil when using a path' do it { is_expected.to be_nil }
expect(repository.tree(:head, 'README.md')).to be_nil
end end
end end
context 'using an existing repository' do context 'using an existing repository' do
it 'returns a Tree' do it { is_expected.to be_an_instance_of(Tree) }
expect(repository.tree(:head)).to be_an_instance_of(Tree)
expect(repository.tree('v1.1.1')).to be_an_instance_of(Tree) context 'when different sha is set' do
let(:sha) { 'v1.1.1' }
it { is_expected.to be_an_instance_of(Tree) }
end
context 'when recursive is true' do
let(:recursive) { true }
it { is_expected.to be_an_instance_of(Tree) }
end
context 'with pagination parameters' do
let(:pagination_params) { { limit: 10, page_token: nil } }
it { is_expected.to be_an_instance_of(Tree) }
end end
end end
end end
......
...@@ -6,7 +6,7 @@ RSpec.describe Tree do ...@@ -6,7 +6,7 @@ RSpec.describe Tree do
let(:repository) { create(:project, :repository).repository } let(:repository) { create(:project, :repository).repository }
let(:sha) { repository.root_ref } let(:sha) { repository.root_ref }
subject { described_class.new(repository, '54fcc214') } subject(:tree) { described_class.new(repository, '54fcc214') }
describe '#readme' do describe '#readme' do
before do before do
...@@ -66,4 +66,10 @@ RSpec.describe Tree do ...@@ -66,4 +66,10 @@ RSpec.describe Tree do
expect(subject.readme.name).to eq 'README.md' expect(subject.readme.name).to eq 'README.md'
end end
end end
describe '#cursor' do
subject { tree.cursor }
it { is_expected.to be_an_instance_of(Gitaly::PaginationCursor) }
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment