Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-12-15 09:10:00 +00:00
parent 4f33294e27
commit a79324ad1f
58 changed files with 852 additions and 192 deletions

View File

@ -147,6 +147,8 @@
/ee/spec/javascripts/ @gitlab-org/maintainers/frontend
/spec/frontend/ @gitlab-org/maintainers/frontend
/ee/spec/frontend/ @gitlab-org/maintainers/frontend
/spec/frontend_integration/ @gitlab-org/maintainers/frontend
/ee/spec/frontend_integration/ @gitlab-org/maintainers/frontend
[Database]
/db/ @gitlab-org/maintainers/database

View File

@ -82,7 +82,6 @@ export default class FileTemplateMediator {
initPageEvents() {
this.listenForFilenameInput();
this.prepFileContentForSubmit();
this.listenForPreviewMode();
}
@ -92,12 +91,6 @@ export default class FileTemplateMediator {
});
}
prepFileContentForSubmit() {
this.$commitForm.submit(() => {
this.$fileContent.val(this.editor.getValue());
});
}
listenForPreviewMode() {
this.$navLinks.on('click', 'a', e => {
const urlPieces = e.target.href.split('#');

View File

@ -6,6 +6,7 @@ import TemplateSelectorMediator from '../blob/file_template_mediator';
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
import EditorLite from '~/editor/editor_lite';
import { FileTemplateExtension } from '~/editor/editor_file_template_ext';
import { insertFinalNewline } from '~/lib/utils/text_utility';
export default class EditBlob {
// The options object has:
@ -49,7 +50,7 @@ export default class EditBlob {
});
form.addEventListener('submit', () => {
fileContentEl.value = this.editor.getValue();
fileContentEl.value = insertFinalNewline(this.editor.getValue());
});
}

View File

@ -191,7 +191,13 @@ export const fetchDiffFilesMeta = ({ commit, state }) => {
commit(types.SET_MERGE_REQUEST_DIFFS, data.merge_request_diffs || []);
commit(types.SET_DIFF_METADATA, strippedData);
worker.postMessage(prepareDiffData(data, state.diffFiles));
worker.postMessage(
prepareDiffData({
diff: data,
priorFiles: state.diffFiles,
meta: true,
}),
);
return data;
})

View File

@ -73,7 +73,10 @@ export default {
},
[types.SET_DIFF_DATA_BATCH](state, data) {
const files = prepareDiffData(data, state.diffFiles);
const files = prepareDiffData({
diff: data,
priorFiles: state.diffFiles,
});
Object.assign(state, {
...convertObjectPropsToCamelCase(data),
@ -143,7 +146,7 @@ export default {
},
[types.ADD_COLLAPSED_DIFFS](state, { file, data }) {
const files = prepareDiffData(data);
const files = prepareDiffData({ diff: data });
const [newFileData] = files.filter(f => f.file_hash === file.file_hash);
const selectedFile = state.diffFiles.find(f => f.file_hash === file.file_hash);
Object.assign(selectedFile, { ...newFileData });

View File

@ -386,9 +386,9 @@ function deduplicateFilesList(files) {
return Object.values(dedupedFiles);
}
export function prepareDiffData(diff, priorFiles = []) {
export function prepareDiffData({ diff, priorFiles = [], meta = false }) {
const cleanedFiles = (diff.diff_files || [])
.map((file, index, allFiles) => prepareRawDiffFile({ file, allFiles }))
.map((file, index, allFiles) => prepareRawDiffFile({ file, allFiles, meta }))
.map(ensureBasicDiffFileLines)
.map(prepareDiffFileLines)
.map(finalizeDiffFile);

View File

@ -4,6 +4,7 @@ import {
DIFF_FILE_MANUAL_COLLAPSE,
DIFF_FILE_AUTOMATIC_COLLAPSE,
} from '../constants';
import { uuids } from './uuids';
function fileSymlinkInformation(file, fileList) {
const duplicates = fileList.filter(iteratedFile => iteratedFile.file_hash === file.file_hash);
@ -32,16 +33,29 @@ function collapsed(file) {
};
}
export function prepareRawDiffFile({ file, allFiles }) {
Object.assign(file, {
function identifier(file) {
return uuids({
seeds: [file.file_identifier_hash, file.content_sha],
})[0];
}
export function prepareRawDiffFile({ file, allFiles, meta = false }) {
const additionalProperties = {
brokenSymlink: fileSymlinkInformation(file, allFiles),
viewer: {
...file.viewer,
...collapsed(file),
},
});
};
return file;
// It's possible, but not confirmed, that `content_sha` isn't available sometimes
// See: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49506#note_464692057
// We don't want duplicate IDs if that's the case, so we just don't assign an ID
if (!meta && file.content_sha) {
additionalProperties.id = identifier(file);
}
return Object.assign(file, additionalProperties);
}
export function collapsedType(file) {

View File

@ -1,7 +1,8 @@
import { editor as monacoEditor, Uri } from 'monaco-editor';
import Disposable from './disposable';
import eventHub from '../../eventhub';
import { trimTrailingWhitespace, insertFinalNewline } from '../../utils';
import { trimTrailingWhitespace } from '../../utils';
import { insertFinalNewline } from '~/lib/utils/text_utility';
import { defaultModelOptions } from '../editor_options';
export default class Model {

View File

@ -97,10 +97,6 @@ export function trimTrailingWhitespace(content) {
return content.replace(/[^\S\r\n]+$/gm, '');
}
export function insertFinalNewline(content, eol = '\n') {
return content.slice(-eol.length) !== eol ? `${content}${eol}` : content;
}
export function getPathParents(path, maxDepth = Infinity) {
const pathComponents = path.split('/');
const paths = [];

View File

@ -411,3 +411,13 @@ export const hasContent = obj => isString(obj) && obj.trim() !== '';
export const isValidSha1Hash = str => {
return /^[0-9a-f]{5,40}$/.test(str);
};
/**
* Adds a final newline to the content if it doesn't already exist
*
* @param {*} content Content
* @param {*} endOfLine Type of newline: CRLF='\r\n', LF='\n', CR='\r'
*/
export function insertFinalNewline(content, endOfLine = '\n') {
return content.slice(-endOfLine.length) !== endOfLine ? `${content}${endOfLine}` : content;
}

View File

@ -15,7 +15,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
before_action :authorize_admin_group_member!, except: admin_not_required_endpoints
before_action do
push_frontend_feature_flag(:group_members_filtered_search, @group)
push_frontend_feature_flag(:group_members_filtered_search, @group, default_enabled: true)
end
skip_before_action :check_two_factor_requirement, only: :leave

View File

@ -57,7 +57,7 @@ class Import::BulkImportsController < ApplicationController
end
def create_params
params.permit(:bulk_import, [*bulk_import_params])
params.permit(bulk_import: bulk_import_params)[:bulk_import]
end
def bulk_import_params
@ -127,7 +127,7 @@ class Import::BulkImportsController < ApplicationController
def credentials
{
url: session[url_key],
access_token: [access_token_key]
access_token: session[access_token_key]
}
end
end

View File

@ -745,31 +745,7 @@ class MergeRequestDiff < ApplicationRecord
def sort_diffs(diffs)
return diffs unless sort_diffs?
diffs.sort do |a, b|
compare_path_parts(path_parts(a), path_parts(b))
end
end
def path_parts(diff)
(diff.new_path.presence || diff.old_path).split(::File::SEPARATOR)
end
# Used for sorting the file paths by:
# 1. Directory name
# 2. Depth
# 3. File name
def compare_path_parts(a_parts, b_parts)
a_part = a_parts.shift
b_part = b_parts.shift
return 1 if a_parts.size < b_parts.size && a_parts.empty?
return -1 if a_parts.size > b_parts.size && b_parts.empty?
comparison = a_part <=> b_part
return comparison unless comparison == 0
compare_path_parts(a_parts, b_parts)
Gitlab::Diff::FileCollectionSorter.new(diffs).sort
end
def sort_diffs?

View File

@ -5,6 +5,6 @@ class RawUsageData < ApplicationRecord
validates :recorded_at, presence: true, uniqueness: true
def update_sent_at!
self.update_column(:sent_at, Time.current) if Feature.enabled?(:save_raw_usage_data)
self.update_column(:sent_at, Time.current)
end
end

View File

@ -2,7 +2,9 @@
class DiffsMetadataEntity < DiffsEntity
unexpose :diff_files
expose :raw_diff_files, as: :diff_files, using: DiffFileMetadataEntity
expose :diff_files, using: DiffFileMetadataEntity do |diffs, _|
diffs.raw_diff_files(sorted: true)
end
expose :conflict_resolution_path do |_, options|
presenter(options[:merge_request]).conflict_resolution_path

View File

@ -12,4 +12,8 @@ class Import::BulkImportEntity < Grape::Entity
expose :full_path do |entity|
entity['full_path']
end
expose :web_url do |entity|
entity['web_url']
end
end

View File

@ -13,7 +13,7 @@ class PaginatedDiffEntity < Grape::Entity
submodule_links = Gitlab::SubmoduleLinks.new(merge_request.project.repository)
DiffFileEntity.represent(
diffs.diff_files,
diffs.diff_files(sorted: true),
options.merge(
submodule_links: submodule_links,
code_navigation_path: code_navigation_path(diffs),

View File

@ -43,8 +43,6 @@ class SubmitUsagePingService
private
def save_raw_usage_data(usage_data)
return unless Feature.enabled?(:save_raw_usage_data)
RawUsageData.safe_find_or_create_by(recorded_at: usage_data[:recorded_at]) do |record|
record.payload = usage_data
end

View File

@ -4,7 +4,7 @@
- show_access_requests = can_manage_members && @requesters.exists?
- invited_active = params[:search_invited].present? || params[:invited_members_page].present?
- vue_members_list_enabled = Feature.enabled?(:vue_group_members_list, @group, default_enabled: true)
- filtered_search_enabled = Feature.enabled?(:group_members_filtered_search, @group)
- filtered_search_enabled = Feature.enabled?(:group_members_filtered_search, @group, default_enabled: true)
- current_user_is_group_owner = @group && @group.has_owner?(current_user)
- form_item_label_css_class = 'label-bold gl-mr-2 gl-mb-0 gl-py-2 align-self-md-center'

View File

@ -27,9 +27,6 @@
= sprite_icon("rocket", size: 12)
= _("Release")
= link_to release.name, project_releases_path(@project, anchor: release.tag), class: 'gl-text-blue-600!'
- if release.description.present?
.md.gl-mt-3
= markdown_field(release, :description)
.row-fixed-content.controls.flex-row
- if tag.has_signature?

View File

@ -0,0 +1,5 @@
---
title: Update deprecated button on pipeline security table
merge_request: 49620
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Save usage ping payload in raw_usage_data table
merge_request: 49559
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Sort commit/compare diff files directory first
merge_request: 49136
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Add final newline on submit in blob editor
merge_request: 49681
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Convert group member filter dropdowns to filtered search bar
merge_request: 49505
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Remove release notes from Tags page
merge_request: 49979
author:
type: removed

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/289911
milestone: '13.7'
type: development
group: group::access
default_enabled: false
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: save_raw_usage_data
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38457
rollout_issue_url:
milestone: '13.3'
type: development
group: group::product analytics
default_enabled: false

View File

@ -4121,6 +4121,56 @@ type CreateClusterAgentPayload {
errors: [String!]!
}
"""
Autogenerated input type of CreateComplianceFramework
"""
input CreateComplianceFrameworkInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Color to represent the compliance framework as a hexadecimal value. e.g. #ABC123.
"""
color: String!
"""
Description of the compliance framework.
"""
description: String!
"""
Name of the compliance framework.
"""
name: String!
"""
Full path of the namespace to add the compliance framework to.
"""
namespacePath: ID!
}
"""
Autogenerated return type of CreateComplianceFramework
"""
type CreateComplianceFrameworkPayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The created compliance framework.
"""
framework: ComplianceFramework
}
"""
Autogenerated input type of CreateCustomEmoji
"""
@ -14545,6 +14595,7 @@ type Mutation {
createBoard(input: CreateBoardInput!): CreateBoardPayload
createBranch(input: CreateBranchInput!): CreateBranchPayload
createClusterAgent(input: CreateClusterAgentInput!): CreateClusterAgentPayload
createComplianceFramework(input: CreateComplianceFrameworkInput!): CreateComplianceFrameworkPayload
"""
Available only when feature flag `custom_emoji` is enabled.

View File

@ -11320,6 +11320,150 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "CreateComplianceFrameworkInput",
"description": "Autogenerated input type of CreateComplianceFramework",
"fields": null,
"inputFields": [
{
"name": "namespacePath",
"description": "Full path of the namespace to add the compliance framework to.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "name",
"description": "Name of the compliance framework.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "description",
"description": "Description of the compliance framework.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "color",
"description": "Color to represent the compliance framework as a hexadecimal value. e.g. #ABC123.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "CreateComplianceFrameworkPayload",
"description": "Autogenerated return type of CreateComplianceFramework",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "framework",
"description": "The created compliance framework.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "ComplianceFramework",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "CreateCustomEmojiInput",
@ -40796,6 +40940,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createComplianceFramework",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "CreateComplianceFrameworkInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "CreateComplianceFrameworkPayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createCustomEmoji",
"description": " Available only when feature flag `custom_emoji` is enabled.",

View File

@ -693,6 +693,16 @@ Autogenerated return type of CreateClusterAgent.
| `clusterAgent` | ClusterAgent | Cluster agent created after mutation |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
### CreateComplianceFrameworkPayload
Autogenerated return type of CreateComplianceFramework.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `framework` | ComplianceFramework | The created compliance framework. |
### CreateCustomEmojiPayload
Autogenerated return type of CreateCustomEmoji.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -15,7 +15,9 @@ source branch into a target branch. By default, the CI pipeline runs jobs
against the source branch.
With *pipelines for merged results*, the pipeline runs as if the changes from
the source branch have already been merged into the target branch.
the source branch have already been merged into the target branch. The commit shown for the pipeline does not exist on the source or target branches but represents the combined target and source branches.
![Merge request widget for merged results pipeline](img/merged_result_pipeline.png)
If the pipeline fails due to a problem in the target branch, you can wait until the
target is fixed and re-run the pipeline.

View File

@ -65,6 +65,39 @@ execution time and the error output.
![Test Reports Widget](img/junit_test_report.png)
### Number of recent failures
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241759) in GitLab 13.7.
> - It's [deployed behind a feature flag](../user/feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-the-number-of-recent-failures). **(CORE ONLY)**
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
If a test failed in the project's default branch in the last 14 days, a message like
`Failed {n} time(s) in {default_branch} in the last 14 days` is displayed for that test.
#### Enable or disable the number of recent failures **(CORE ONLY)**
Displaying the number of failures in the last 14 days is under development and not
ready for production use. It is deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../administration/feature_flags.md)
can enable it.
To enable it:
```ruby
Feature.enable(:test_failure_history)
```
To disable it:
```ruby
Feature.disable(:test_failure_history)
```
## How to set it up
To enable the Unit test reports in merge requests, you need to add

View File

@ -654,6 +654,17 @@ When the docs are generated, the output is:
To stop the command, press <kbd>Control</kbd>+<kbd>C</kbd>.
### Spaces between words
Use only standard spaces between words. The search engine for the documentation
website doesn't split words separated with
[non-breaking spaces](https://en.wikipedia.org/wiki/Non-breaking_space) when
indexing, and fails to create expected individual search terms. Tests that search
for certain words separated by regular spaces can't find words separated by
non-breaking spaces.
Tested in [`lint-doc.sh`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/scripts/lint-doc.sh).
## Lists
- Always start list items with a capital letter, unless they're parameters or

View File

@ -74,7 +74,7 @@ which means that the reported licenses might be incomplete or inaccurate.
| JavaScript | [Yarn](https://yarnpkg.com/)|[License Finder](https://github.com/pivotal/LicenseFinder)|
| Go | go get, gvt, glide, dep, trash, govendor |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Erlang | [Rebar](https://www.rebar3.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) | | [License Finder](https://github.com/pivotal/LicenseFinder) |
| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) | [License Finder](https://github.com/pivotal/LicenseFinder) |
| Objective-C, Swift | [CocoaPods](https://cocoapods.org/) v0.39 and below |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Elixir | [Mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| C++/C | [Conan](https://conan.io/) |[License Finder](https://github.com/pivotal/LicenseFinder)|

View File

@ -30,12 +30,16 @@ module Gitlab
@diffs ||= diffable.raw_diffs(diff_options)
end
def diff_files
raw_diff_files
def diff_files(sorted: false)
raw_diff_files(sorted: sorted)
end
def raw_diff_files
@raw_diff_files ||= diffs.decorate! { |diff| decorate_diff!(diff) }
def raw_diff_files(sorted: false)
strong_memoize(:"raw_diff_files_#{sorted}") do
collection = diffs.decorate! { |diff| decorate_diff!(diff) }
collection = sort_diffs(collection) if sorted
collection
end
end
def diff_file_paths
@ -111,6 +115,12 @@ module Gitlab
fallback_diff_refs: fallback_diff_refs,
stats: stats)
end
def sort_diffs(diffs)
return diffs unless Feature.enabled?(:sort_diffs, project, default_enabled: false)
Gitlab::Diff::FileCollectionSorter.new(diffs).sort
end
end
end
end

View File

@ -16,7 +16,7 @@ module Gitlab
fallback_diff_refs: merge_request_diff.fallback_diff_refs)
end
def diff_files
def diff_files(sorted: false)
strong_memoize(:diff_files) do
diff_files = super
@ -26,6 +26,12 @@ module Gitlab
end
end
def raw_diff_files(sorted: false)
# We force `sorted` to `false` as we don't need to sort the diffs when
# dealing with `MergeRequestDiff` since we sort its files on create.
super(sorted: false)
end
override :write_cache
def write_cache
highlight_cache.write_if_empty

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
module Gitlab
module Diff
class FileCollectionSorter
attr_reader :diffs
def initialize(diffs)
@diffs = diffs
end
def sort
diffs.sort do |a, b|
compare_path_parts(path_parts(a), path_parts(b))
end
end
private
def path_parts(diff)
(diff.new_path.presence || diff.old_path).split(::File::SEPARATOR)
end
# Used for sorting the file paths by:
# 1. Directory name
# 2. Depth
# 3. File name
def compare_path_parts(a_parts, b_parts)
a_part = a_parts.shift
b_part = b_parts.shift
return 1 if a_parts.size < b_parts.size && a_parts.empty?
return -1 if a_parts.size > b_parts.size && b_parts.empty?
comparison = a_part <=> b_part
return comparison unless comparison == 0
compare_path_parts(a_parts, b_parts)
end
end
end
end

View File

@ -11805,9 +11805,6 @@ msgstr ""
msgid "Feature flag was successfully removed."
msgstr ""
msgid "Feature not available"
msgstr ""
msgid "FeatureFlags|%d user"
msgid_plural "FeatureFlags|%d users"
msgstr[0] ""

View File

@ -6,7 +6,7 @@ module RuboCop
class IDType < RuboCop::Cop::Cop
MSG = 'Do not use GraphQL::ID_TYPE, use a specific GlobalIDType instead'
WHITELISTED_ARGUMENTS = %i[iid full_path project_path group_path target_project_path].freeze
WHITELISTED_ARGUMENTS = %i[iid full_path project_path group_path target_project_path namespace_path].freeze
def_node_search :graphql_id_type?, <<~PATTERN
(send nil? :argument (_ #does_not_match?) (const (const nil? :GraphQL) :ID_TYPE) ...)

View File

@ -18,6 +18,21 @@ then
((ERRORCODE++))
fi
# Test for non-standard spaces (NBSP, NNBSP) in documentation.
echo '=> Checking for non-standard spaces...'
echo
grep --extended-regexp --binary-file=without-match --recursive '[ ]' doc/ >/dev/null 2>&1
if [ $? -eq 0 ]
then
echo '✖ ERROR: Non-standard spaces (NBSP, NNBSP) should not be used in documentation.
https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#spaces-between-words
Replace with standard spaces:' >&2
# Find the spaces, then add color codes with sed to highlight each NBSP or NNBSP in the output.
grep --extended-regexp --binary-file=without-match --recursive --color=auto '[ ]' doc \
| sed -e ''//s//`printf "\033[0;101m\033[0m"`/'' -e ''/ /s//`printf "\033[0;101m \033[0m"`/''
((ERRORCODE++))
fi
# Ensure that the CHANGELOG.md does not contain duplicate versions
DUPLICATE_CHANGELOG_VERSIONS=$(grep --extended-regexp '^## .+' CHANGELOG.md | sed -E 's| \(.+\)||' | sort -r | uniq -d)
echo '=> Checking for CHANGELOG.md duplicate entries...'

View File

@ -57,8 +57,8 @@ RSpec.describe Import::BulkImportsController do
let(:client_response) do
double(
parsed_response: [
{ 'id' => 1, 'full_name' => 'group1', 'full_path' => 'full/path/group1' },
{ 'id' => 2, 'full_name' => 'group2', 'full_path' => 'full/path/group2' }
{ 'id' => 1, 'full_name' => 'group1', 'full_path' => 'full/path/group1', 'web_url' => 'http://demo.host/full/path/group1' },
{ 'id' => 2, 'full_name' => 'group2', 'full_path' => 'full/path/group2', 'web_url' => 'http://demo.host/full/path/group1' }
]
)
end
@ -132,12 +132,27 @@ RSpec.describe Import::BulkImportsController do
end
describe 'POST create' do
let(:instance_url) { "http://fake-intance" }
let(:pat) { "fake-pat" }
before do
session[:bulk_import_gitlab_access_token] = pat
session[:bulk_import_gitlab_url] = instance_url
end
it 'executes BulkImportService' do
expect_next_instance_of(BulkImportService) do |service|
bulk_import_params = [{ "source_type" => "group_entity",
"source_full_path" => "full_path",
"destination_name" =>
"destination_name",
"destination_namespace" => "root" }]
expect_next_instance_of(
BulkImportService, user, bulk_import_params, { url: instance_url, access_token: pat }) do |service|
expect(service).to receive(:execute)
end
post :create
post :create, params: { bulk_import: bulk_import_params }
expect(response).to have_gitlab_http_status(:ok)
end

View File

@ -12,13 +12,18 @@ describe('Blob Editing', () => {
const useMock = jest.fn();
const mockInstance = {
use: useMock,
getValue: jest.fn(),
setValue: jest.fn(),
getValue: jest.fn().mockReturnValue('test value'),
focus: jest.fn(),
};
beforeEach(() => {
setFixtures(
`<div class="js-edit-blob-form"><div id="file_path"></div><div id="editor"></div><input id="file-content"></div>`,
);
setFixtures(`
<form class="js-edit-blob-form">
<div id="file_path"></div>
<div id="editor"></div>
<textarea id="file-content"></textarea>
</form>
`);
jest.spyOn(EditorLite.prototype, 'createInstance').mockReturnValue(mockInstance);
});
afterEach(() => {
@ -55,4 +60,15 @@ describe('Blob Editing', () => {
expect(EditorMarkdownExtension).toHaveBeenCalledTimes(1);
});
});
it('adds trailing newline to the blob content on submit', async () => {
const form = document.querySelector('.js-edit-blob-form');
const fileContentEl = document.getElementById('file-content');
await initEditor();
form.dispatchEvent(new Event('submit'));
expect(fileContentEl.value).toBe('test value\n');
});
});

View File

@ -409,10 +409,13 @@ describe('DiffsStoreUtils', () => {
diff_files: [{ ...mock, [INLINE_DIFF_LINES_KEY]: undefined }],
};
preparedDiff.diff_files = utils.prepareDiffData(preparedDiff);
splitInlineDiff.diff_files = utils.prepareDiffData(splitInlineDiff);
splitParallelDiff.diff_files = utils.prepareDiffData(splitParallelDiff);
completedDiff.diff_files = utils.prepareDiffData(completedDiff, [mock]);
preparedDiff.diff_files = utils.prepareDiffData({ diff: preparedDiff });
splitInlineDiff.diff_files = utils.prepareDiffData({ diff: splitInlineDiff });
splitParallelDiff.diff_files = utils.prepareDiffData({ diff: splitParallelDiff });
completedDiff.diff_files = utils.prepareDiffData({
diff: completedDiff,
priorFiles: [mock],
});
});
it('sets the renderIt and collapsed attribute on files', () => {
@ -447,7 +450,10 @@ describe('DiffsStoreUtils', () => {
content_sha: 'ABC',
file_hash: 'DEF',
};
const updatedFilesList = utils.prepareDiffData({ diff_files: [fakeNewFile] }, priorFiles);
const updatedFilesList = utils.prepareDiffData({
diff: { diff_files: [fakeNewFile] },
priorFiles,
});
expect(updatedFilesList).toEqual([mock, fakeNewFile]);
});
@ -460,7 +466,10 @@ describe('DiffsStoreUtils', () => {
{ ...mock, [INLINE_DIFF_LINES_KEY]: undefined },
{ ...mock, [INLINE_DIFF_LINES_KEY]: undefined, content_sha: 'ABC', file_hash: 'DEF' },
];
const updatedFilesList = utils.prepareDiffData({ diff_files: fakeBatch }, priorFiles);
const updatedFilesList = utils.prepareDiffData({
diff: { diff_files: fakeBatch },
priorFiles,
});
expect(updatedFilesList).toEqual([
mock,
@ -498,7 +507,7 @@ describe('DiffsStoreUtils', () => {
beforeEach(() => {
mock = getDiffMetadataMock();
preparedDiffFiles = utils.prepareDiffData(mock);
preparedDiffFiles = utils.prepareDiffData({ diff: mock, meta: true });
});
it('sets the renderIt and collapsed attribute on files', () => {
@ -514,7 +523,7 @@ describe('DiffsStoreUtils', () => {
const fileMock = getDiffFileMock();
const metaData = getDiffMetadataMock();
const priorFiles = [fileMock];
const updatedFilesList = utils.prepareDiffData(metaData, priorFiles);
const updatedFilesList = utils.prepareDiffData({ diff: metaData, priorFiles, meta: true });
expect(updatedFilesList.length).toEqual(2);
expect(updatedFilesList[0]).toEqual(fileMock);
@ -539,7 +548,7 @@ describe('DiffsStoreUtils', () => {
const fileMock = getDiffFileMock();
const metaMock = getDiffMetadataMock();
const priorFiles = [{ ...fileMock }];
const updatedFilesList = utils.prepareDiffData(metaMock, priorFiles);
const updatedFilesList = utils.prepareDiffData({ diff: metaMock, priorFiles, meta: true });
expect(updatedFilesList).toEqual([
fileMock,

View File

@ -1,31 +1,42 @@
import { prepareRawDiffFile } from '~/diffs/utils/diff_file';
const DIFF_FILES = [
{
file_hash: 'ABC', // This file is just a normal file
},
{
file_hash: 'DEF', // This file replaces a symlink
a_mode: '0',
b_mode: '0755',
},
{
file_hash: 'DEF', // This symlink is replaced by a file
a_mode: '120000',
b_mode: '0',
},
{
file_hash: 'GHI', // This symlink replaces a file
a_mode: '0',
b_mode: '120000',
},
{
file_hash: 'GHI', // This file is replaced by a symlink
a_mode: '0755',
b_mode: '0',
},
];
function getDiffFiles() {
return [
{
file_hash: 'ABC', // This file is just a normal file
file_identifier_hash: 'ABC1',
content_sha: 'C047347',
},
{
file_hash: 'DEF', // This file replaces a symlink
file_identifier_hash: 'DEF1',
content_sha: 'C047347',
a_mode: '0',
b_mode: '0755',
},
{
file_hash: 'DEF', // This symlink is replaced by a file
file_identifier_hash: 'DEF2',
content_sha: 'C047347',
a_mode: '120000',
b_mode: '0',
},
{
file_hash: 'GHI', // This symlink replaces a file
file_identifier_hash: 'GHI1',
content_sha: 'C047347',
a_mode: '0',
b_mode: '120000',
},
{
file_hash: 'GHI', // This file is replaced by a symlink
file_identifier_hash: 'GHI2',
content_sha: 'C047347',
a_mode: '0755',
b_mode: '0',
},
];
}
function makeBrokenSymlinkObject(replaced, wasSymbolic, isSymbolic, wasReal, isReal) {
return {
replaced,
@ -38,6 +49,12 @@ function makeBrokenSymlinkObject(replaced, wasSymbolic, isSymbolic, wasReal, isR
describe('diff_file utilities', () => {
describe('prepareRawDiffFile', () => {
let files;
beforeEach(() => {
files = getDiffFiles();
});
it.each`
fileIndex | description | brokenSymlink
${0} | ${'a file that is not symlink-adjacent'} | ${false}
@ -49,12 +66,51 @@ describe('diff_file utilities', () => {
'properly marks $description with the correct .brokenSymlink value',
({ fileIndex, brokenSymlink }) => {
const preppedRaw = prepareRawDiffFile({
file: DIFF_FILES[fileIndex],
allFiles: DIFF_FILES,
file: files[fileIndex],
allFiles: files,
});
expect(preppedRaw.brokenSymlink).toStrictEqual(brokenSymlink);
},
);
it.each`
fileIndex | id
${0} | ${'e075da30-4ec7-4e1c-a505-fe0fb0efe2d8'}
${1} | ${'5ab05419-123e-4d18-8454-0b8c3d9f3f91'}
${2} | ${'94eb6bba-575c-4504-bd8e-5d302364d31e'}
${3} | ${'06d669b2-29b7-4f47-9731-33fc38a8db61'}
${4} | ${'edd3e8f9-07f9-4647-8171-544c72e5a175'}
`('sets the file id properly { id: $id } on normal diff files', ({ fileIndex, id }) => {
const preppedFile = prepareRawDiffFile({
file: files[fileIndex],
allFiles: files,
});
expect(preppedFile.id).toBe(id);
});
it('does not set the `id` property for metadata diff files', () => {
const preppedFile = prepareRawDiffFile({
file: files[0],
allFiles: files,
meta: true,
});
expect(preppedFile).not.toHaveProp('id');
});
it('does not set the id property if the file is missing a `content_sha`', () => {
const fileMissingContentSha = { ...files[0] };
delete fileMissingContentSha.content_sha;
const preppedFile = prepareRawDiffFile({
file: fileMissingContentSha,
allFiles: files,
});
expect(preppedFile).not.toHaveProp('id');
});
});
});

View File

@ -4,7 +4,6 @@ import {
registerLanguages,
registerSchema,
trimPathComponents,
insertFinalNewline,
trimTrailingWhitespace,
getPathParents,
getPathParent,
@ -225,29 +224,6 @@ describe('WebIDE utils', () => {
});
});
describe('addFinalNewline', () => {
it.each`
input | output
${'some text'} | ${'some text\n'}
${'some text\n'} | ${'some text\n'}
${'some text\n\n'} | ${'some text\n\n'}
${'some\n text'} | ${'some\n text\n'}
`('adds a newline if it doesnt already exist for input: $input', ({ input, output }) => {
expect(insertFinalNewline(input)).toBe(output);
});
it.each`
input | output
${'some text'} | ${'some text\r\n'}
${'some text\r\n'} | ${'some text\r\n'}
${'some text\n'} | ${'some text\n\r\n'}
${'some text\r\n\r\n'} | ${'some text\r\n\r\n'}
${'some\r\n text'} | ${'some\r\n text\r\n'}
`('works with CRLF newline style; input: $input', ({ input, output }) => {
expect(insertFinalNewline(input, '\r\n')).toBe(output);
});
});
describe('getPathParents', () => {
it.each`
path | parents

View File

@ -340,4 +340,27 @@ describe('text_utility', () => {
expect(textUtils.isValidSha1Hash(hash)).toBe(valid);
});
});
describe('insertFinalNewline', () => {
it.each`
input | output
${'some text'} | ${'some text\n'}
${'some text\n'} | ${'some text\n'}
${'some text\n\n'} | ${'some text\n\n'}
${'some\n text'} | ${'some\n text\n'}
`('adds a newline if it doesnt already exist for input: $input', ({ input, output }) => {
expect(textUtils.insertFinalNewline(input)).toBe(output);
});
it.each`
input | output
${'some text'} | ${'some text\r\n'}
${'some text\r\n'} | ${'some text\r\n'}
${'some text\n'} | ${'some text\n\r\n'}
${'some text\r\n\r\n'} | ${'some text\r\n\r\n'}
${'some\r\n text'} | ${'some\r\n text\r\n'}
`('works with CRLF newline style; input: $input', ({ input, output }) => {
expect(textUtils.insertFinalNewline(input, '\r\n')).toBe(output);
});
});
});

View File

@ -4,17 +4,75 @@ require 'spec_helper'
RSpec.describe Gitlab::Diff::FileCollection::Commit do
let(:project) { create(:project, :repository) }
let(:diffable) { project.commit }
let(:collection_default_args) do
{ diff_options: {} }
end
it_behaves_like 'diff statistics' do
let(:collection_default_args) do
{ diff_options: {} }
end
let(:diffable) { project.commit }
let(:stub_path) { 'bar/branch-test.txt' }
end
it_behaves_like 'unfoldable diff' do
let(:diffable) { project.commit }
it_behaves_like 'unfoldable diff'
it_behaves_like 'sortable diff files' do
let(:diffable) { project.commit('913c66a') }
let(:unsorted_diff_files_paths) do
[
'.DS_Store',
'CHANGELOG',
'MAINTENANCE.md',
'PROCESS.md',
'VERSION',
'encoding/feature-1.txt',
'encoding/feature-2.txt',
'encoding/hotfix-1.txt',
'encoding/hotfix-2.txt',
'encoding/russian.rb',
'encoding/test.txt',
'encoding/テスト.txt',
'encoding/テスト.xls',
'files/.DS_Store',
'files/html/500.html',
'files/images/logo-black.png',
'files/images/logo-white.png',
'files/js/application.js',
'files/js/commit.js.coffee',
'files/markdown/ruby-style-guide.md',
'files/ruby/popen.rb',
'files/ruby/regex.rb',
'files/ruby/version_info.rb'
]
end
let(:sorted_diff_files_paths) do
[
'encoding/feature-1.txt',
'encoding/feature-2.txt',
'encoding/hotfix-1.txt',
'encoding/hotfix-2.txt',
'encoding/russian.rb',
'encoding/test.txt',
'encoding/テスト.txt',
'encoding/テスト.xls',
'files/html/500.html',
'files/images/logo-black.png',
'files/images/logo-white.png',
'files/js/application.js',
'files/js/commit.js.coffee',
'files/markdown/ruby-style-guide.md',
'files/ruby/popen.rb',
'files/ruby/regex.rb',
'files/ruby/version_info.rb',
'files/.DS_Store',
'.DS_Store',
'CHANGELOG',
'MAINTENANCE.md',
'PROCESS.md',
'VERSION'
]
end
end
end

View File

@ -27,4 +27,43 @@ RSpec.describe Gitlab::Diff::FileCollection::Compare do
let(:diffable) { Compare.new(raw_compare, project) }
let(:stub_path) { '.gitignore' }
end
it_behaves_like 'sortable diff files' do
let(:diffable) { Compare.new(raw_compare, project) }
let(:collection_default_args) do
{
project: diffable.project,
diff_options: {},
diff_refs: diffable.diff_refs
}
end
let(:unsorted_diff_files_paths) do
[
'.DS_Store',
'.gitignore',
'.gitmodules',
'Gemfile.zip',
'files/.DS_Store',
'files/ruby/popen.rb',
'files/ruby/regex.rb',
'files/ruby/version_info.rb',
'gitlab-shell'
]
end
let(:sorted_diff_files_paths) do
[
'files/ruby/popen.rb',
'files/ruby/regex.rb',
'files/ruby/version_info.rb',
'files/.DS_Store',
'.DS_Store',
'.gitignore',
'.gitmodules',
'Gemfile.zip',
'gitlab-shell'
]
end
end
end

View File

@ -144,4 +144,18 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do
it_behaves_like 'cacheable diff collection' do
let(:cacheable_files_count) { batch_size }
end
it_behaves_like 'unsortable diff files' do
let(:diffable) { merge_request.merge_request_diff }
let(:collection_default_args) do
{ diff_options: {} }
end
subject do
described_class.new(merge_request.merge_request_diff,
batch_page,
batch_size,
**collection_default_args)
end
end
end

View File

@ -54,4 +54,11 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiff do
it 'returns a valid instance of a DiffCollection' do
expect(diff_files).to be_a(Gitlab::Git::DiffCollection)
end
it_behaves_like 'unsortable diff files' do
let(:diffable) { merge_request.merge_request_diff }
let(:collection_default_args) do
{ diff_options: {} }
end
end
end

View File

@ -0,0 +1,51 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Diff::FileCollectionSorter do
let(:diffs) do
[
double(new_path: '.dir/test', old_path: '.dir/test'),
double(new_path: '', old_path: '.file'),
double(new_path: '1-folder/A-file.ext', old_path: '1-folder/A-file.ext'),
double(new_path: nil, old_path: '1-folder/M-file.ext'),
double(new_path: '1-folder/Z-file.ext', old_path: '1-folder/Z-file.ext'),
double(new_path: '', old_path: '1-folder/nested/A-file.ext'),
double(new_path: '1-folder/nested/M-file.ext', old_path: '1-folder/nested/M-file.ext'),
double(new_path: nil, old_path: '1-folder/nested/Z-file.ext'),
double(new_path: '2-folder/A-file.ext', old_path: '2-folder/A-file.ext'),
double(new_path: '', old_path: '2-folder/M-file.ext'),
double(new_path: '2-folder/Z-file.ext', old_path: '2-folder/Z-file.ext'),
double(new_path: nil, old_path: '2-folder/nested/A-file.ext'),
double(new_path: 'A-file.ext', old_path: 'A-file.ext'),
double(new_path: '', old_path: 'M-file.ext'),
double(new_path: 'Z-file.ext', old_path: 'Z-file.ext')
]
end
subject { described_class.new(diffs) }
describe '#sort' do
let(:sorted_files_paths) { subject.sort.map { |file| file.new_path.presence || file.old_path } }
it 'returns list sorted directory first' do
expect(sorted_files_paths).to eq([
'.dir/test',
'1-folder/nested/A-file.ext',
'1-folder/nested/M-file.ext',
'1-folder/nested/Z-file.ext',
'1-folder/A-file.ext',
'1-folder/M-file.ext',
'1-folder/Z-file.ext',
'2-folder/nested/A-file.ext',
'2-folder/A-file.ext',
'2-folder/M-file.ext',
'2-folder/Z-file.ext',
'.file',
'A-file.ext',
'M-file.ext',
'Z-file.ext'
])
end
end
end

View File

@ -16,28 +16,10 @@ RSpec.describe RawUsageData do
describe '#update_sent_at!' do
let(:raw_usage_data) { create(:raw_usage_data) }
context 'with save_raw_usage_data feature enabled' do
before do
stub_feature_flags(save_raw_usage_data: true)
end
it 'updates sent_at' do
raw_usage_data.update_sent_at!
it 'updates sent_at' do
raw_usage_data.update_sent_at!
expect(raw_usage_data.sent_at).not_to be_nil
end
end
context 'with save_raw_usage_data feature disabled' do
before do
stub_feature_flags(save_raw_usage_data: false)
end
it 'updates sent_at' do
raw_usage_data.update_sent_at!
expect(raw_usage_data.sent_at).to be_nil
end
expect(raw_usage_data.sent_at).not_to be_nil
end
end
end

View File

@ -7,14 +7,15 @@ RSpec.describe Import::BulkImportEntity do
{
'id' => 1,
'full_name' => 'test',
'full_path' => 'full/path/test',
'full_path' => 'full/path/tes',
'web_url' => 'http://web.url/path',
'foo' => 'bar'
}
end
subject { described_class.represent(importable_data).as_json }
%w[id full_name full_path].each do |attribute|
%w[id full_name full_path web_url].each do |attribute|
it "exposes #{attribute}" do
expect(subject[attribute.to_sym]).to eq(importable_data[attribute])
end

View File

@ -134,10 +134,9 @@ RSpec.describe SubmitUsagePingService do
it_behaves_like 'saves DevOps report data from the response'
end
context 'with save_raw_usage_data feature enabled' do
context 'with saving raw_usage_data' do
before do
stub_response(body: with_dev_ops_score_params)
stub_feature_flags(save_raw_usage_data: true)
end
it 'creates a raw_usage_data record' do
@ -159,18 +158,6 @@ RSpec.describe SubmitUsagePingService do
end
end
context 'with save_raw_usage_data feature disabled' do
before do
stub_response(body: with_dev_ops_score_params)
end
it 'does not create a raw_usage_data record' do
stub_feature_flags(save_raw_usage_data: false)
expect { subject.execute }.to change(RawUsageData, :count).by(0)
end
end
context 'and usage ping response has unsuccessful status' do
before do
stub_response(body: nil, status: 504)

View File

@ -143,3 +143,55 @@ RSpec.shared_examples 'cacheable diff collection' do
end
end
end
shared_examples_for 'sortable diff files' do
subject { described_class.new(diffable, **collection_default_args) }
describe '#raw_diff_files' do
let(:raw_diff_files_paths) do
subject.raw_diff_files(sorted: sorted).map { |file| file.new_path.presence || file.old_path }
end
context 'when sorted is false (default)' do
let(:sorted) { false }
it 'returns unsorted diff files' do
expect(raw_diff_files_paths).to eq(unsorted_diff_files_paths)
end
end
context 'when sorted is true' do
let(:sorted) { true }
it 'returns sorted diff files' do
expect(raw_diff_files_paths).to eq(sorted_diff_files_paths)
end
context 'when sort_diffs feature flag is disabled' do
before do
stub_feature_flags(sort_diffs: false)
end
it 'returns unsorted diff files' do
expect(raw_diff_files_paths).to eq(unsorted_diff_files_paths)
end
end
end
end
end
shared_examples_for 'unsortable diff files' do
subject { described_class.new(diffable, **collection_default_args) }
describe '#raw_diff_files' do
it 'does not call Gitlab::Diff::FileCollectionSorter even when sorted is true' do
# Ensure that diffable is created before expectation to ensure that we are
# not calling it from `FileCollectionSorter` from `#raw_diff_files`.
diffable
expect(Gitlab::Diff::FileCollectionSorter).not_to receive(:new)
subject.raw_diff_files(sorted: true)
end
end
end