summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile.lock8
-rw-r--r--app/assets/javascripts/repository/components/preview/index.vue49
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue84
-rw-r--r--app/assets/javascripts/repository/components/tree_content.vue115
-rw-r--r--app/assets/javascripts/repository/graphql.js6
-rw-r--r--app/assets/javascripts/repository/pages/index.vue11
-rw-r--r--app/assets/javascripts/repository/pages/tree.vue6
-rw-r--r--app/assets/javascripts/repository/queries/getReadme.query.graphql5
-rw-r--r--app/assets/javascripts/repository/utils/readme.js17
-rw-r--r--app/assets/stylesheets/framework/snippets.scss7
-rw-r--r--app/controllers/application_controller.rb22
-rw-r--r--app/controllers/concerns/confirm_email_warning.rb7
-rw-r--r--app/controllers/concerns/uploads_actions.rb17
-rw-r--r--app/controllers/uploads_controller.rb2
-rw-r--r--app/models/concerns/issuable.rb20
-rw-r--r--app/views/import/manifest/_form.html.haml2
-rw-r--r--app/views/projects/_files.html.haml2
-rw-r--r--app/views/shared/snippets/_snippet.html.haml5
-rw-r--r--changelogs/unreleased/28350-manifest-error-file-attach.yml5
-rw-r--r--changelogs/unreleased/31964-make-snippet-list-easier-to-scan.yml5
-rw-r--r--changelogs/unreleased/35289-remove-existence-check-in-url-constrainer.yml5
-rw-r--r--changelogs/unreleased/allow-container-scanning-to-run-offline.yml5
-rw-r--r--changelogs/unreleased/sh-fix-bitbucket-importer-pr-state.yml5
-rw-r--r--config/routes/git_http.rb2
-rw-r--r--config/routes/project.rb62
-rw-r--r--lib/constraints/project_url_constrainer.rb9
-rw-r--r--lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml13
-rw-r--r--lib/gitlab/patch/draw_route.rb2
-rw-r--r--spec/controllers/application_controller_spec.rb16
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb4
-rw-r--r--spec/controllers/projects/error_tracking_controller_spec.rb2
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb4
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb4
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb2
-rw-r--r--spec/controllers/projects_controller_spec.rb2
-rw-r--r--spec/controllers/uploads_controller_spec.rb24
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb5
-rw-r--r--spec/features/projects/tags/user_views_tags_spec.rb2
-rw-r--r--spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap36
-rw-r--r--spec/frontend/repository/components/preview/index_spec.js49
-rw-r--r--spec/frontend/repository/components/table/index_spec.js66
-rw-r--r--spec/frontend/repository/components/tree_content_spec.js71
-rw-r--r--spec/lib/constraints/project_url_constrainer_spec.rb31
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb1
-rw-r--r--spec/models/concerns/issuable_spec.rb28
-rw-r--r--spec/requests/projects/blob_controller_spec.rb44
-rw-r--r--spec/requests/user_avatar_spec.rb36
-rw-r--r--spec/routing/project_routing_spec.rb4
-rw-r--r--spec/support/controllers/sessionless_auth_controller_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/controllers/todos_shared_examples.rb2
50 files changed, 655 insertions, 298 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index ecc68463f89..4eac23b7a71 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -803,8 +803,8 @@ GEM
actionpack (>= 4.0, < 7)
redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 2)
- redis-activesupport (5.0.7)
- activesupport (>= 3, < 6)
+ redis-activesupport (5.2.0)
+ activesupport (>= 3, < 7)
redis-store (>= 1.3, < 2)
redis-namespace (1.6.0)
redis (>= 3.0.4)
@@ -815,8 +815,8 @@ GEM
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
- redis-store (1.6.0)
- redis (>= 2.2, < 5)
+ redis-store (1.8.1)
+ redis (>= 4, < 5)
regexp_parser (1.5.1)
regexp_property_values (0.3.4)
representable (3.0.4)
diff --git a/app/assets/javascripts/repository/components/preview/index.vue b/app/assets/javascripts/repository/components/preview/index.vue
new file mode 100644
index 00000000000..564be211c46
--- /dev/null
+++ b/app/assets/javascripts/repository/components/preview/index.vue
@@ -0,0 +1,49 @@
+<script>
+import { GlLink, GlLoadingIcon } from '@gitlab/ui';
+import getReadmeQuery from '../../queries/getReadme.query.graphql';
+
+export default {
+ apollo: {
+ readme: {
+ query: getReadmeQuery,
+ variables() {
+ return {
+ url: this.blob.webUrl,
+ };
+ },
+ loadingKey: 'loading',
+ },
+ },
+ components: {
+ GlLink,
+ GlLoadingIcon,
+ },
+ props: {
+ blob: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ readme: null,
+ loading: 0,
+ };
+ },
+};
+</script>
+
+<template>
+ <article class="file-holder js-hide-on-navigation limited-width-container readme-holder">
+ <div class="file-title">
+ <i aria-hidden="true" class="fa fa-file-text-o fa-fw"></i>
+ <gl-link :href="blob.webUrl">
+ <strong>{{ blob.name }}</strong>
+ </gl-link>
+ </div>
+ <div class="blob-viewer">
+ <gl-loading-icon v-if="loading > 0" size="md" class="my-4 mx-auto" />
+ <div v-else-if="readme" v-html="readme.html"></div>
+ </div>
+ </article>
+</template>
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
index 98923c79c7a..ac20549acb8 100644
--- a/app/assets/javascripts/repository/components/table/index.vue
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -1,16 +1,12 @@
<script>
import { GlSkeletonLoading } from '@gitlab/ui';
-import createFlash from '~/flash';
import { sprintf, __ } from '../../../locale';
import getRefMixin from '../../mixins/get_ref';
-import getFiles from '../../queries/getFiles.query.graphql';
import getProjectPath from '../../queries/getProjectPath.query.graphql';
import TableHeader from './header.vue';
import TableRow from './row.vue';
import ParentRow from './parent_row.vue';
-const PAGE_SIZE = 100;
-
export default {
components: {
GlSkeletonLoading,
@@ -29,22 +25,24 @@ export default {
type: String,
required: true,
},
+ entries: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ isLoading: {
+ type: Boolean,
+ required: true,
+ },
},
data() {
return {
projectPath: '',
- nextPageCursor: '',
- entries: {
- trees: [],
- submodules: [],
- blobs: [],
- },
- isLoadingFiles: false,
};
},
computed: {
tableCaption() {
- if (this.isLoadingFiles) {
+ if (this.isLoading) {
return sprintf(
__(
'Loading files, directories, and submodules in the path %{path} for commit reference %{ref}',
@@ -59,65 +57,7 @@ export default {
);
},
showParentRow() {
- return !this.isLoadingFiles && ['', '/'].indexOf(this.path) === -1;
- },
- },
- watch: {
- $route: function routeChange() {
- this.entries.trees = [];
- this.entries.submodules = [];
- this.entries.blobs = [];
- this.nextPageCursor = '';
- this.fetchFiles();
- },
- },
- mounted() {
- // We need to wait for `ref` and `projectPath` to be set
- this.$nextTick(() => this.fetchFiles());
- },
- methods: {
- fetchFiles() {
- this.isLoadingFiles = true;
-
- return this.$apollo
- .query({
- query: getFiles,
- variables: {
- projectPath: this.projectPath,
- ref: this.ref,
- path: this.path || '/',
- nextPageCursor: this.nextPageCursor,
- pageSize: PAGE_SIZE,
- },
- })
- .then(({ data }) => {
- if (!data) return;
-
- const pageInfo = this.hasNextPage(data.project.repository.tree);
-
- this.isLoadingFiles = false;
- this.entries = Object.keys(this.entries).reduce(
- (acc, key) => ({
- ...acc,
- [key]: this.normalizeData(key, data.project.repository.tree[key].edges),
- }),
- {},
- );
-
- if (pageInfo && pageInfo.hasNextPage) {
- this.nextPageCursor = pageInfo.endCursor;
- this.fetchFiles();
- }
- })
- .catch(() => createFlash(__('An error occurred while fetching folder content.')));
- },
- normalizeData(key, data) {
- return this.entries[key].concat(data.map(({ node }) => node));
- },
- hasNextPage(data) {
- return []
- .concat(data.trees.pageInfo, data.submodules.pageInfo, data.blobs.pageInfo)
- .find(({ hasNextPage }) => hasNextPage);
+ return !this.isLoading && ['', '/'].indexOf(this.path) === -1;
},
},
};
@@ -145,7 +85,7 @@ export default {
:lfs-oid="entry.lfsOid"
/>
</template>
- <template v-if="isLoadingFiles">
+ <template v-if="isLoading">
<tr v-for="i in 5" :key="i" aria-hidden="true">
<td><gl-skeleton-loading :lines="1" class="h-auto" /></td>
<td><gl-skeleton-loading :lines="1" class="h-auto" /></td>
diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue
new file mode 100644
index 00000000000..949e653fc8f
--- /dev/null
+++ b/app/assets/javascripts/repository/components/tree_content.vue
@@ -0,0 +1,115 @@
+<script>
+import createFlash from '~/flash';
+import { __ } from '../../locale';
+import FileTable from './table/index.vue';
+import getRefMixin from '../mixins/get_ref';
+import getFiles from '../queries/getFiles.query.graphql';
+import getProjectPath from '../queries/getProjectPath.query.graphql';
+import FilePreview from './preview/index.vue';
+import { readmeFile } from '../utils/readme';
+
+const PAGE_SIZE = 100;
+
+export default {
+ components: {
+ FileTable,
+ FilePreview,
+ },
+ mixins: [getRefMixin],
+ apollo: {
+ projectPath: {
+ query: getProjectPath,
+ },
+ },
+ props: {
+ path: {
+ type: String,
+ required: false,
+ default: '/',
+ },
+ },
+ data() {
+ return {
+ projectPath: '',
+ nextPageCursor: '',
+ entries: {
+ trees: [],
+ submodules: [],
+ blobs: [],
+ },
+ isLoadingFiles: false,
+ };
+ },
+ computed: {
+ readme() {
+ return readmeFile(this.entries.blobs);
+ },
+ },
+
+ watch: {
+ $route: function routeChange() {
+ this.entries.trees = [];
+ this.entries.submodules = [];
+ this.entries.blobs = [];
+ this.nextPageCursor = '';
+ this.fetchFiles();
+ },
+ },
+ mounted() {
+ // We need to wait for `ref` and `projectPath` to be set
+ this.$nextTick(() => this.fetchFiles());
+ },
+ methods: {
+ fetchFiles() {
+ this.isLoadingFiles = true;
+
+ return this.$apollo
+ .query({
+ query: getFiles,
+ variables: {
+ projectPath: this.projectPath,
+ ref: this.ref,
+ path: this.path || '/',
+ nextPageCursor: this.nextPageCursor,
+ pageSize: PAGE_SIZE,
+ },
+ })
+ .then(({ data }) => {
+ if (!data) return;
+
+ const pageInfo = this.hasNextPage(data.project.repository.tree);
+
+ this.isLoadingFiles = false;
+ this.entries = Object.keys(this.entries).reduce(
+ (acc, key) => ({
+ ...acc,
+ [key]: this.normalizeData(key, data.project.repository.tree[key].edges),
+ }),
+ {},
+ );
+
+ if (pageInfo && pageInfo.hasNextPage) {
+ this.nextPageCursor = pageInfo.endCursor;
+ this.fetchFiles();
+ }
+ })
+ .catch(() => createFlash(__('An error occurred while fetching folder content.')));
+ },
+ normalizeData(key, data) {
+ return this.entries[key].concat(data.map(({ node }) => node));
+ },
+ hasNextPage(data) {
+ return []
+ .concat(data.trees.pageInfo, data.submodules.pageInfo, data.blobs.pageInfo)
+ .find(({ hasNextPage }) => hasNextPage);
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <file-table :path="path" :entries="entries" :is-loading="isLoadingFiles" />
+ <file-preview v-if="readme" :blob="readme" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/repository/graphql.js b/app/assets/javascripts/repository/graphql.js
index 6cb253c8169..6936c08d852 100644
--- a/app/assets/javascripts/repository/graphql.js
+++ b/app/assets/javascripts/repository/graphql.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
+import axios from '~/lib/utils/axios_utils';
import createDefaultClient from '~/lib/graphql';
import introspectionQueryResultData from './fragmentTypes.json';
import { fetchLogsTree } from './log_tree';
@@ -27,6 +28,11 @@ const defaultClient = createDefaultClient(
});
});
},
+ readme(_, { url }) {
+ return axios
+ .get(url, { params: { viewer: 'rich', format: 'json' } })
+ .then(({ data }) => ({ ...data, __typename: 'ReadmeFile' }));
+ },
},
},
{
diff --git a/app/assets/javascripts/repository/pages/index.vue b/app/assets/javascripts/repository/pages/index.vue
index 2d92e9174ca..967f4a99281 100644
--- a/app/assets/javascripts/repository/pages/index.vue
+++ b/app/assets/javascripts/repository/pages/index.vue
@@ -1,18 +1,13 @@
<script>
-import FileTable from '../components/table/index.vue';
+import TreeContent from '../components/tree_content.vue';
export default {
components: {
- FileTable,
- },
- data() {
- return {
- ref: '',
- };
+ TreeContent,
},
};
</script>
<template>
- <file-table path="/" />
+ <tree-content />
</template>
diff --git a/app/assets/javascripts/repository/pages/tree.vue b/app/assets/javascripts/repository/pages/tree.vue
index 3b898d1aa91..19300099449 100644
--- a/app/assets/javascripts/repository/pages/tree.vue
+++ b/app/assets/javascripts/repository/pages/tree.vue
@@ -1,9 +1,9 @@
<script>
-import FileTable from '../components/table/index.vue';
+import TreeContent from '../components/tree_content.vue';
export default {
components: {
- FileTable,
+ TreeContent,
},
props: {
path: {
@@ -16,5 +16,5 @@ export default {
</script>
<template>
- <file-table :path="path" />
+ <tree-content :path="path" />
</template>
diff --git a/app/assets/javascripts/repository/queries/getReadme.query.graphql b/app/assets/javascripts/repository/queries/getReadme.query.graphql
new file mode 100644
index 00000000000..cf056330133
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/getReadme.query.graphql
@@ -0,0 +1,5 @@
+query getReadme($url: String!) {
+ readme(url: $url) @client {
+ html
+ }
+}
diff --git a/app/assets/javascripts/repository/utils/readme.js b/app/assets/javascripts/repository/utils/readme.js
new file mode 100644
index 00000000000..b219b857c66
--- /dev/null
+++ b/app/assets/javascripts/repository/utils/readme.js
@@ -0,0 +1,17 @@
+const MARKDOWN_EXTENSIONS = ['mdown', 'mkd', 'mkdn', 'md', 'markdown'];
+const ASCIIDOC_EXTENSIONS = ['adoc', 'ad', 'asciidoc'];
+const OTHER_EXTENSIONS = ['textile', 'rdoc', 'org', 'creole', 'wiki', 'mediawiki', 'rst'];
+const EXTENSIONS = [...MARKDOWN_EXTENSIONS, ...ASCIIDOC_EXTENSIONS, ...OTHER_EXTENSIONS];
+const PLAIN_FILENAMES = ['readme', 'index'];
+const FILE_REGEXP = new RegExp(`^(${PLAIN_FILENAMES.join('|')})`, 'i');
+const EXTENSIONS_REGEXP = new RegExp(`.(${EXTENSIONS.join('|')})$`, 'i');
+
+// eslint-disable-next-line import/prefer-default-export
+export const readmeFile = blobs => {
+ const readMeFiles = blobs.filter(f => f.name.search(FILE_REGEXP) !== -1);
+
+ const previewableReadme = readMeFiles.find(f => f.name.search(EXTENSIONS_REGEXP) !== -1);
+ const plainReadme = readMeFiles.find(f => f.name.search(FILE_REGEXP) !== -1);
+
+ return previewableReadme || plainReadme;
+};
diff --git a/app/assets/stylesheets/framework/snippets.scss b/app/assets/stylesheets/framework/snippets.scss
index f57b1d9f351..404f60f17ee 100644
--- a/app/assets/stylesheets/framework/snippets.scss
+++ b/app/assets/stylesheets/framework/snippets.scss
@@ -4,7 +4,12 @@
}
.snippet-filename {
- padding: 0 2px;
+ color: $gl-text-color-secondary;
+ font-weight: normal;
+ }
+
+ .snippet-info {
+ color: $gl-text-color-secondary;
}
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index af2869ddba7..1311c745da3 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -17,14 +17,14 @@ class ApplicationController < ActionController::Base
include Gitlab::Tracking::ControllerConcern
include Gitlab::Experimentation::ControllerConcern
- before_action :authenticate_user!, except: [:route_not_found]
+ before_action :authenticate_user!
before_action :enforce_terms!, if: :should_enforce_terms?
before_action :validate_user_service_ticket!
- before_action :check_password_expiration
+ before_action :check_password_expiration, if: :html_request?
before_action :ldap_security_check
before_action :sentry_context
before_action :default_headers
- before_action :add_gon_variables, unless: [:peek_request?, :json_request?]
+ before_action :add_gon_variables, if: :html_request?
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller?
before_action :active_user_check, unless: :devise_controller?
@@ -95,13 +95,11 @@ class ApplicationController < ActionController::Base
end
def route_not_found
- if current_user
- not_found
- else
- store_location_for(:user, request.fullpath) unless request.xhr?
+ # We need to call #authenticate_user! here because sometimes this is called from another action
+ # and not from our wildcard fallback route
+ authenticate_user!
- redirect_to new_user_session_path, alert: I18n.t('devise.failure.unauthenticated')
- end
+ not_found
end
def render(*args)
@@ -451,8 +449,8 @@ class ApplicationController < ActionController::Base
response.headers['Page-Title'] = URI.escape(page_title('GitLab'))
end
- def peek_request?
- request.path.start_with?('/-/peek')
+ def html_request?
+ request.format.html?
end
def json_request?
@@ -462,7 +460,7 @@ class ApplicationController < ActionController::Base
def should_enforce_terms?
return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms
- !(peek_request? || devise_controller?)
+ html_request? && !devise_controller?
end
def set_usage_stats_consent_flag
diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb
index 5a4b5897a4f..874deeb4702 100644
--- a/app/controllers/concerns/confirm_email_warning.rb
+++ b/app/controllers/concerns/confirm_email_warning.rb
@@ -4,15 +4,18 @@ module ConfirmEmailWarning
extend ActiveSupport::Concern
included do
- before_action :set_confirm_warning, if: -> { Feature.enabled?(:soft_email_confirmation) }
+ before_action :set_confirm_warning, if: :show_confirm_warning?
end
protected
+ def show_confirm_warning?
+ html_request? && request.get? && Feature.enabled?(:soft_email_confirmation)
+ end
+
def set_confirm_warning
return unless current_user
return if current_user.confirmed?
- return if peek_request? || json_request? || !request.get?
email = current_user.unconfirmed_email || current_user.email
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index b87779c22d3..023c41821da 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -1,11 +1,16 @@
# frozen_string_literal: true
module UploadsActions
+ extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
include SendFileUpload
UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo favicon).freeze
+ included do
+ prepend_before_action :set_request_format_from_path_extension
+ end
+
def create
uploader = UploadService.new(model, params[:file], uploader_class).execute
@@ -64,6 +69,18 @@ module UploadsActions
private
+ # From ActionDispatch::Http::MimeNegotiation. We have an initializer that
+ # monkey-patches this method out (so that repository paths don't guess a
+ # format based on extension), but we do want this behaviour when serving
+ # uploads.
+ def set_request_format_from_path_extension
+ path = request.headers['action_dispatch.original_path'] || request.headers['PATH_INFO']
+
+ if match = path&.match(/\.(\w+)\z/)
+ request.format = match.captures.first
+ end
+ end
+
def uploader_class
raise NotImplementedError
end
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 635db386792..f39a2b81b54 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -20,7 +20,7 @@ class UploadsController < ApplicationController
skip_before_action :authenticate_user!
before_action :upload_mount_satisfied?
- before_action :find_model
+ before_action :model
before_action :authorize_access!, only: [:show]
before_action :authorize_create_access!, only: [:create, :authorize]
before_action :verify_workhorse_api!, only: [:authorize]
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 852576dbbc2..796e6438a2c 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -137,6 +137,26 @@ module Issuable
strip_attributes :title
+ # The state_machine gem will reset the value of state_id unless it
+ # is a raw attribute passed in here:
+ # https://gitlab.com/gitlab-org/gitlab/issues/35746#note_241148787
+ #
+ # This assumes another initialize isn't defined. Otherwise this
+ # method may need to be prepended.
+ def initialize(attributes = nil)
+ if attributes.is_a?(Hash)
+ attr = attributes.symbolize_keys
+
+ if attr.key?(:state) && !attr.key?(:state_id)
+ value = attr.delete(:state)
+ state_id = self.class.available_states[value]
+ attributes[:state_id] = state_id if state_id
+ end
+ end
+
+ super(attributes)
+ end
+
# We want to use optimistic lock for cases when only title or description are involved
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
def locking_enabled?
diff --git a/app/views/import/manifest/_form.html.haml b/app/views/import/manifest/_form.html.haml
index 78c7fadb019..b515ce084e4 100644
--- a/app/views/import/manifest/_form.html.haml
+++ b/app/views/import/manifest/_form.html.haml
@@ -13,7 +13,7 @@
.form-group
= label_tag :manifest, class: 'label-bold' do
= _('Manifest')
- = file_field_tag :manifest, class: 'form-control-file', required: true
+ = file_field_tag :manifest, class: 'form-control-file w-auto', required: true
.form-text.text-muted
= _('Import multiple repositories by uploading a manifest file.')
= link_to icon('question-circle'), help_page_path('user/project/import/manifest')
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index 3c0dfd4c029..6681bb4d094 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -23,7 +23,5 @@
- if can_edit_tree?
= render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
= render 'projects/blob/new_dir'
- - if @tree.readme
- = render "projects/tree/readme", readme: @tree.readme
- else
= render 'projects/tree/tree_content', tree: @tree, content_url: content_url
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index 0ef626868a2..5602ea37b5c 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -7,8 +7,9 @@
.title
= link_to reliable_snippet_path(snippet) do
= snippet.title
- - if snippet.file_name
- %span.snippet-filename.monospace.d-none.d-sm-inline-block
+ - if snippet.file_name.present?
+ %span.snippet-filename.d-none.d-sm-inline-block.ml-2
+ = sprite_icon('doc-code', size: 16, css_class: 'file-icon align-text-bottom')
= snippet.file_name
%ul.controls
diff --git a/changelogs/unreleased/28350-manifest-error-file-attach.yml b/changelogs/unreleased/28350-manifest-error-file-attach.yml
new file mode 100644
index 00000000000..f1c11d5f1bd
--- /dev/null
+++ b/changelogs/unreleased/28350-manifest-error-file-attach.yml
@@ -0,0 +1,5 @@
+---
+title: Add max width on manifest file attachment input
+merge_request: 19028
+author:
+type: fixed
diff --git a/changelogs/unreleased/31964-make-snippet-list-easier-to-scan.yml b/changelogs/unreleased/31964-make-snippet-list-easier-to-scan.yml
new file mode 100644
index 00000000000..d10165438d4
--- /dev/null
+++ b/changelogs/unreleased/31964-make-snippet-list-easier-to-scan.yml
@@ -0,0 +1,5 @@
+---
+title: Make snippet list easier to scan
+merge_request: 19490
+author:
+type: other
diff --git a/changelogs/unreleased/35289-remove-existence-check-in-url-constrainer.yml b/changelogs/unreleased/35289-remove-existence-check-in-url-constrainer.yml
new file mode 100644
index 00000000000..d33803cfdec
--- /dev/null
+++ b/changelogs/unreleased/35289-remove-existence-check-in-url-constrainer.yml
@@ -0,0 +1,5 @@
+---
+title: Fix JSON responses returning 302 instead of 401
+merge_request: 19412
+author:
+type: fixed
diff --git a/changelogs/unreleased/allow-container-scanning-to-run-offline.yml b/changelogs/unreleased/allow-container-scanning-to-run-offline.yml
new file mode 100644
index 00000000000..5cce0565d28
--- /dev/null
+++ b/changelogs/unreleased/allow-container-scanning-to-run-offline.yml
@@ -0,0 +1,5 @@
+---
+title: Allow container scanning to run offline by specifying the Clair DB image to use.
+merge_request: 19161
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-fix-bitbucket-importer-pr-state.yml b/changelogs/unreleased/sh-fix-bitbucket-importer-pr-state.yml
new file mode 100644
index 00000000000..f5b90a296c1
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-bitbucket-importer-pr-state.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Bitbucket Cloud importer pull request state
+merge_request: 19734
+author:
+type: fixed
diff --git a/config/routes/git_http.rb b/config/routes/git_http.rb
index aac6d418a92..2e70fd9f1b6 100644
--- a/config/routes/git_http.rb
+++ b/config/routes/git_http.rb
@@ -52,7 +52,7 @@ scope(path: '*namespace_id/:project_id',
# /info/refs?service=git-receive-pack, but nothing else.
#
git_http_handshake = lambda do |request|
- ::Constraints::ProjectUrlConstrainer.new.matches?(request, existence_check: false) &&
+ ::Constraints::ProjectUrlConstrainer.new.matches?(request) &&
(request.query_string.blank? ||
request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/))
end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index d49ba20ce84..62a70b4655e 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -245,12 +245,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
post :validate_query, on: :collection
end
end
-
- Gitlab.ee do
- resources :alerts, constraints: { id: /\d+/ }, only: [:index, :create, :show, :update, :destroy] do
- post :notify, on: :collection
- end
- end
end
resources :merge_requests, concerns: :awardable, except: [:new, :create, :show], constraints: { id: /\d+/ } do
@@ -353,17 +347,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- Gitlab.ee do
- resources :path_locks, only: [:index, :destroy] do
- collection do
- post :toggle
- end
- end
-
- get '/service_desk' => 'service_desk#show', as: :service_desk
- put '/service_desk' => 'service_desk#update', as: :service_desk_refresh
- end
-
resource :variables, only: [:show, :update]
resources :triggers, only: [:index, :create, :edit, :update, :destroy]
@@ -397,11 +380,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :failures
get :status
get :test_report
-
- Gitlab.ee do
- get :security
- get :licenses
- end
end
member do
@@ -536,24 +514,11 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :realtime_changes
post :create_merge_request
get :discussions, format: :json
-
- Gitlab.ee do
- get 'designs(/*vueroute)', to: 'issues#designs', as: :designs, format: false
- end
end
collection do
post :bulk_update
post :import_csv
-
- Gitlab.ee do
- post :export_csv
- get :service_desk
- end
- end
-
- Gitlab.ee do
- resources :issue_links, only: [:index, :create, :destroy], as: 'links', path: 'links'
end
end
@@ -629,6 +594,15 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
Gitlab.ee do
resources :managed_licenses, only: [:index, :show, :new, :create, :edit, :update, :destroy]
end
+
+ # Legacy routes.
+ # Introduced in 12.0.
+ # Should be removed after 12.1
+ Gitlab::Routing.redirect_legacy_paths(self, :settings, :branches, :tags,
+ :network, :graphs, :autocomplete_sources,
+ :project_members, :deploy_keys, :deploy_tokens,
+ :labels, :milestones, :services, :boards, :releases,
+ :forks, :group_links, :import, :avatar)
end
resources(:projects,
@@ -653,22 +627,4 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
end
-
- # Legacy routes.
- # Introduced in 12.0.
- # Should be removed after 12.1
- scope(path: '*namespace_id',
- as: :namespace,
- namespace_id: Gitlab::PathRegex.full_namespace_route_regex) do
- scope(path: ':project_id',
- constraints: { project_id: Gitlab::PathRegex.project_route_regex },
- module: :projects,
- as: :project) do
- Gitlab::Routing.redirect_legacy_paths(self, :settings, :branches, :tags,
- :network, :graphs, :autocomplete_sources,
- :project_members, :deploy_keys, :deploy_tokens,
- :labels, :milestones, :services, :boards, :releases,
- :forks, :group_links, :import, :avatar)
- end
- end
end
diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb
index d41490d2ebd..64eefd67d81 100644
--- a/lib/constraints/project_url_constrainer.rb
+++ b/lib/constraints/project_url_constrainer.rb
@@ -2,17 +2,12 @@
module Constraints
class ProjectUrlConstrainer
- def matches?(request, existence_check: true)
+ def matches?(request)
namespace_path = request.params[:namespace_id]
project_path = request.params[:project_id] || request.params[:id]
full_path = [namespace_path, project_path].join('/')
- return false unless ProjectPathValidator.valid_path?(full_path)
- return true unless existence_check
-
- # We intentionally allow SELECT(*) here so result of this query can be used
- # as cache for further Project.find_by_full_path calls within request
- Project.find_by_full_path(full_path, follow_redirects: request.get?).present?
+ ProjectPathValidator.valid_path?(full_path)
end
end
end
diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
index f058468ed8e..ef2fc561201 100644
--- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
@@ -9,16 +9,17 @@ container_scanning:
name: registry.gitlab.com/gitlab-org/security-products/analyzers/klar:$CS_MAJOR_VERSION
entrypoint: []
variables:
- # By default, use the latest clair vulnerabilities database, however, allow it to be overridden here
- # with a specific version to provide consistency for integration testing purposes
- CLAIR_DB_IMAGE_TAG: latest
- # Override this variable in your `.gitlab-ci.yml` file and set it to `fetch` if you want to provide a `clair-whitelist.yaml` file.
- # See https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#overriding-the-container-scanning-template
+ # By default, use the latest clair vulnerabilities database, however, allow it to be overridden here with a specific image
+ # to enable container scanning to run offline, or to provide a consistent list of vulnerabilities for integration testing purposes
+ CLAIR_DB_IMAGE_TAG: "latest"
+ CLAIR_DB_IMAGE: "arminc/clair-db:$CLAIR_DB_IMAGE_TAG"
+ # Override the GIT_STRATEGY variable in your `.gitlab-ci.yml` file and set it to `fetch` if you want to provide a `clair-whitelist.yml`
+ # file. See https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#overriding-the-container-scanning-template
# for details
GIT_STRATEGY: none
allow_failure: true
services:
- - name: arminc/clair-db:$CLAIR_DB_IMAGE_TAG
+ - name: $CLAIR_DB_IMAGE
alias: clair-vulnerabilities-db
script:
# the kubernetes executor currently ignores the Docker image entrypoint value, so the start.sh script must
diff --git a/lib/gitlab/patch/draw_route.rb b/lib/gitlab/patch/draw_route.rb
index 4c8ca015974..4d1b57fbbbb 100644
--- a/lib/gitlab/patch/draw_route.rb
+++ b/lib/gitlab/patch/draw_route.rb
@@ -10,7 +10,7 @@ module Gitlab
RoutesNotFound = Class.new(StandardError)
def draw(routes_name)
- drawn_any = draw_ce(routes_name) | draw_ee(routes_name)
+ drawn_any = draw_ee(routes_name) | draw_ce(routes_name)
drawn_any || raise(RoutesNotFound.new("Cannot find #{routes_name}"))
end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index bb7f5ec2b28..481883c50fa 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -90,14 +90,6 @@ describe ApplicationController do
let(:format) { :html }
it_behaves_like 'setting gon variables'
-
- context 'for peek requests' do
- before do
- request.path = '/-/peek'
- end
-
- it_behaves_like 'not setting gon variables'
- end
end
context 'with json format' do
@@ -105,6 +97,12 @@ describe ApplicationController do
it_behaves_like 'not setting gon variables'
end
+
+ context 'with atom format' do
+ let(:format) { :atom }
+
+ it_behaves_like 'not setting gon variables'
+ end
end
describe 'session expiration' do
@@ -186,7 +184,7 @@ describe ApplicationController do
expect(response).to have_gitlab_http_status(404)
end
- it 'redirects to login page if not authenticated' do
+ it 'redirects to login page via authenticate_user! if not authenticated' do
get :index
expect(response).to redirect_to new_user_session_path
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 1977e92e42b..9c4d6fdcb2a 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -142,7 +142,7 @@ describe Projects::CommitsController do
context 'token authentication' do
context 'public project' do
- it_behaves_like 'authenticates sessionless user', :show, :atom, { public: true, ignore_incrementing: true } do
+ it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
before do
public_project = create(:project, :repository, :public)
@@ -152,7 +152,7 @@ describe Projects::CommitsController do
end
context 'private project' do
- it_behaves_like 'authenticates sessionless user', :show, :atom, { public: false, ignore_incrementing: true } do
+ it_behaves_like 'authenticates sessionless user', :show, :atom, public: false do
before do
private_project = create(:project, :repository, :private)
private_project.add_maintainer(user)
diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb
index 31868f5f717..4c224e960a6 100644
--- a/spec/controllers/projects/error_tracking_controller_spec.rb
+++ b/spec/controllers/projects/error_tracking_controller_spec.rb
@@ -146,7 +146,7 @@ describe Projects::ErrorTrackingController do
it 'redirects to sign-in page' do
post :list_projects, params: list_projects_params
- expect(response).to have_gitlab_http_status(:redirect)
+ expect(response).to have_gitlab_http_status(:unauthorized)
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 8770a5ee303..4c2b58551bf 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1441,7 +1441,7 @@ describe Projects::IssuesController do
context 'private project with token authentication' do
let(:private_project) { create(:project, :private) }
- it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do
+ it_behaves_like 'authenticates sessionless user', :index, :atom do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
@@ -1449,7 +1449,7 @@ describe Projects::IssuesController do
end
end
- it_behaves_like 'authenticates sessionless user', :calendar, :ics, ignore_incrementing: true do
+ it_behaves_like 'authenticates sessionless user', :calendar, :ics do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index 562119d967f..28ca20d7dab 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -111,8 +111,8 @@ describe Projects::ReleasesController do
context 'when the project is private and the user is not logged in' do
let(:project) { private_project }
- it 'returns a redirect' do
- expect(response).to have_gitlab_http_status(:redirect)
+ it 'returns a 401' do
+ expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
index f077b4c99fc..b99b5d611fc 100644
--- a/spec/controllers/projects/tags_controller_spec.rb
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -41,7 +41,7 @@ describe Projects::TagsController do
context 'private project with token authentication' do
let(:private_project) { create(:project, :repository, :private) }
- it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do
+ it_behaves_like 'authenticates sessionless user', :index, :atom do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 22538565698..321f5ecdbc9 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -1149,7 +1149,7 @@ describe ProjectsController do
context 'private project with token authentication' do
let(:private_project) { create(:project, :private) }
- it_behaves_like 'authenticates sessionless user', :show, :atom, ignore_incrementing: true do
+ it_behaves_like 'authenticates sessionless user', :show, :atom do
before do
default_params.merge!(id: private_project, namespace_id: private_project.namespace)
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 1bcf3bb106b..f35babc1b56 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -228,10 +228,10 @@ describe UploadsController do
user.block
end
- it "redirects to the sign in page" do
+ it "responds with status 401" do
get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" }
- expect(response).to redirect_to(new_user_session_path)
+ expect(response).to have_gitlab_http_status(401)
end
end
@@ -320,10 +320,10 @@ describe UploadsController do
end
context "when not signed in" do
- it "redirects to the sign in page" do
+ it "responds with status 401" do
get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
- expect(response).to redirect_to(new_user_session_path)
+ expect(response).to have_gitlab_http_status(401)
end
end
@@ -343,10 +343,10 @@ describe UploadsController do
project.add_maintainer(user)
end
- it "redirects to the sign in page" do
+ it "responds with status 401" do
get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
- expect(response).to redirect_to(new_user_session_path)
+ expect(response).to have_gitlab_http_status(401)
end
end
@@ -439,10 +439,10 @@ describe UploadsController do
user.block
end
- it "redirects to the sign in page" do
+ it "responds with status 401" do
get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" }
- expect(response).to redirect_to(new_user_session_path)
+ expect(response).to have_gitlab_http_status(401)
end
end
@@ -526,10 +526,10 @@ describe UploadsController do
end
context "when not signed in" do
- it "redirects to the sign in page" do
+ it "responds with status 401" do
get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
- expect(response).to redirect_to(new_user_session_path)
+ expect(response).to have_gitlab_http_status(401)
end
end
@@ -549,10 +549,10 @@ describe UploadsController do
project.add_maintainer(user)
end
- it "redirects to the sign in page" do
+ it "responds with status 401" do
get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
- expect(response).to redirect_to(new_user_session_path)
+ expect(response).to have_gitlab_http_status(401)
end
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index f6eeb8d7065..a9a127da56f 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -819,10 +819,7 @@ describe 'Pipelines', :js do
context 'when project is private' do
let(:project) { create(:project, :private, :repository) }
- it 'redirects the user to sign_in and displays the flash alert' do
- expect(page).to have_content 'You need to sign in'
- expect(page.current_path).to eq("/users/sign_in")
- end
+ it { expect(page).to have_content 'You need to sign in' }
end
end
diff --git a/spec/features/projects/tags/user_views_tags_spec.rb b/spec/features/projects/tags/user_views_tags_spec.rb
index bc570f502bf..f344b682715 100644
--- a/spec/features/projects/tags/user_views_tags_spec.rb
+++ b/spec/features/projects/tags/user_views_tags_spec.rb
@@ -15,7 +15,7 @@ describe 'User views tags', :feature do
it do
visit project_tags_path(project, format: :atom)
- expect(page.current_path).to eq("/users/sign_in")
+ expect(page).to have_gitlab_http_status(401)
end
end
diff --git a/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap b/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
new file mode 100644
index 00000000000..3d5ec3fd411
--- /dev/null
+++ b/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
@@ -0,0 +1,36 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Repository file preview component renders file HTML 1`] = `
+<article
+ class="file-holder js-hide-on-navigation limited-width-container readme-holder"
+>
+ <div
+ class="file-title"
+ >
+ <i
+ aria-hidden="true"
+ class="fa fa-file-text-o fa-fw"
+ />
+
+ <gllink-stub
+ href="http://test.com"
+ >
+ <strong>
+ README.md
+ </strong>
+ </gllink-stub>
+ </div>
+
+ <div
+ class="blob-viewer"
+ >
+ <div>
+ <div
+ class="blob"
+ >
+ test
+ </div>
+ </div>
+ </div>
+</article>
+`;
diff --git a/spec/frontend/repository/components/preview/index_spec.js b/spec/frontend/repository/components/preview/index_spec.js
new file mode 100644
index 00000000000..0112e6310f4
--- /dev/null
+++ b/spec/frontend/repository/components/preview/index_spec.js
@@ -0,0 +1,49 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import Preview from '~/repository/components/preview/index.vue';
+
+let vm;
+let $apollo;
+
+function factory(blob) {
+ $apollo = {
+ query: jest.fn().mockReturnValue(Promise.resolve({})),
+ };
+
+ vm = shallowMount(Preview, {
+ propsData: {
+ blob,
+ },
+ mocks: {
+ $apollo,
+ },
+ });
+}
+
+describe('Repository file preview component', () => {
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('renders file HTML', () => {
+ factory({
+ webUrl: 'http://test.com',
+ name: 'README.md',
+ });
+
+ vm.setData({ readme: { html: '<div class="blob">test</div>' } });
+
+ expect(vm.element).toMatchSnapshot();
+ });
+
+ it('renders loading icon', () => {
+ factory({
+ webUrl: 'http://test.com',
+ name: 'README.md',
+ });
+
+ vm.setData({ loading: 1 });
+
+ expect(vm.find(GlLoadingIcon).exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js
index 0c5a7370fca..1e2f3501c8c 100644
--- a/spec/frontend/repository/components/table/index_spec.js
+++ b/spec/frontend/repository/components/table/index_spec.js
@@ -1,18 +1,34 @@
import { shallowMount } from '@vue/test-utils';
import { GlSkeletonLoading } from '@gitlab/ui';
import Table from '~/repository/components/table/index.vue';
+import TableRow from '~/repository/components/table/row.vue';
let vm;
let $apollo;
-function factory(path, data = () => ({})) {
- $apollo = {
- query: jest.fn().mockReturnValue(Promise.resolve({ data: data() })),
- };
-
+const MOCK_BLOBS = [
+ {
+ id: '123abc',
+ flatPath: 'blob',
+ name: 'blob.md',
+ type: 'blob',
+ webUrl: 'http://test.com',
+ },
+ {
+ id: '124abc',
+ flatPath: 'blob2',
+ name: 'blob2.md',
+ type: 'blob',
+ webUrl: 'http://test.com',
+ },
+];
+
+function factory({ path, isLoading = false, entries = {} }) {
vm = shallowMount(Table, {
propsData: {
path,
+ isLoading,
+ entries,
},
mocks: {
$apollo,
@@ -31,7 +47,7 @@ describe('Repository table component', () => {
${'app/assets'} | ${'master'}
${'/'} | ${'test'}
`('renders table caption for $ref in $path', ({ path, ref }) => {
- factory(path);
+ factory({ path });
vm.setData({ ref });
@@ -41,40 +57,20 @@ describe('Repository table component', () => {
});
it('shows loading icon', () => {
- factory('/');
-
- vm.setData({ isLoadingFiles: true });
+ factory({ path: '/', isLoading: true });
expect(vm.find(GlSkeletonLoading).exists()).toBe(true);
});
- describe('normalizeData', () => {
- it('normalizes edge nodes', () => {
- const output = vm.vm.normalizeData('blobs', [{ node: '1' }, { node: '2' }]);
-
- expect(output).toEqual(['1', '2']);
+ it('renders table rows', () => {
+ factory({
+ path: '/',
+ entries: {
+ blobs: MOCK_BLOBS,
+ },
});
- });
-
- describe('hasNextPage', () => {
- it('returns undefined when hasNextPage is false', () => {
- const output = vm.vm.hasNextPage({
- trees: { pageInfo: { hasNextPage: false } },
- submodules: { pageInfo: { hasNextPage: false } },
- blobs: { pageInfo: { hasNextPage: false } },
- });
- expect(output).toBe(undefined);
- });
-
- it('returns pageInfo object when hasNextPage is true', () => {
- const output = vm.vm.hasNextPage({
- trees: { pageInfo: { hasNextPage: false } },
- submodules: { pageInfo: { hasNextPage: false } },
- blobs: { pageInfo: { hasNextPage: true, nextCursor: 'test' } },
- });
-
- expect(output).toEqual({ hasNextPage: true, nextCursor: 'test' });
- });
+ expect(vm.find(TableRow).exists()).toBe(true);
+ expect(vm.findAll(TableRow).length).toBe(2);
});
});
diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js
new file mode 100644
index 00000000000..954c4791c04
--- /dev/null
+++ b/spec/frontend/repository/components/tree_content_spec.js
@@ -0,0 +1,71 @@
+import { shallowMount } from '@vue/test-utils';
+import TreeContent from '~/repository/components/tree_content.vue';
+import FilePreview from '~/repository/components/preview/index.vue';
+
+let vm;
+let $apollo;
+
+function factory(path, data = () => ({})) {
+ $apollo = {
+ query: jest.fn().mockReturnValue(Promise.resolve({ data: data() })),
+ };
+
+ vm = shallowMount(TreeContent, {
+ propsData: {
+ path,
+ },
+ mocks: {
+ $apollo,
+ },
+ });
+}
+
+describe('Repository table component', () => {
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('renders file preview', () => {
+ factory('/');
+
+ vm.setData({ entries: { blobs: [{ name: 'README.md ' }] } });
+
+ expect(vm.find(FilePreview).exists()).toBe(true);
+ });
+
+ describe('normalizeData', () => {
+ it('normalizes edge nodes', () => {
+ factory('/');
+
+ const output = vm.vm.normalizeData('blobs', [{ node: '1' }, { node: '2' }]);
+
+ expect(output).toEqual(['1', '2']);
+ });
+ });
+
+ describe('hasNextPage', () => {
+ it('returns undefined when hasNextPage is false', () => {
+ factory('/');
+
+ const output = vm.vm.hasNextPage({
+ trees: { pageInfo: { hasNextPage: false } },
+ submodules: { pageInfo: { hasNextPage: false } },
+ blobs: { pageInfo: { hasNextPage: false } },
+ });
+
+ expect(output).toBe(undefined);
+ });
+
+ it('returns pageInfo object when hasNextPage is true', () => {
+ factory('/');
+
+ const output = vm.vm.hasNextPage({
+ trees: { pageInfo: { hasNextPage: false } },
+ submodules: { pageInfo: { hasNextPage: false } },
+ blobs: { pageInfo: { hasNextPage: true, nextCursor: 'test' } },
+ });
+
+ expect(output).toEqual({ hasNextPage: true, nextCursor: 'test' });
+ });
+ });
+});
diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb
index ac3221ecab7..27d70d562c1 100644
--- a/spec/lib/constraints/project_url_constrainer_spec.rb
+++ b/spec/lib/constraints/project_url_constrainer_spec.rb
@@ -14,42 +14,15 @@ describe Constraints::ProjectUrlConstrainer do
end
context 'invalid request' do
- context "non-existing project" do
- let(:request) { build_request('foo', 'bar') }
-
- it { expect(subject.matches?(request)).to be_falsey }
-
- context 'existence_check is false' do
- it { expect(subject.matches?(request, existence_check: false)).to be_truthy }
- end
- end
-
context "project id ending with .git" do
let(:request) { build_request(namespace.full_path, project.path + '.git') }
it { expect(subject.matches?(request)).to be_falsey }
end
end
-
- context 'when the request matches a redirect route' do
- let(:old_project_path) { 'old_project_path' }
- let!(:redirect_route) { project.redirect_routes.create!(path: "#{namespace.full_path}/#{old_project_path}") }
-
- context 'and is a GET request' do
- let(:request) { build_request(namespace.full_path, old_project_path) }
- it { expect(subject.matches?(request)).to be_truthy }
- end
-
- context 'and is NOT a GET request' do
- let(:request) { build_request(namespace.full_path, old_project_path, 'POST') }
- it { expect(subject.matches?(request)).to be_falsey }
- end
- end
end
- def build_request(namespace, project, method = 'GET')
- double(:request,
- 'get?': (method == 'GET'),
- params: { namespace_id: namespace, id: project })
+ def build_request(namespace, project)
+ double(:request, params: { namespace_id: namespace, id: project })
end
end
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 7f7a285c453..b0d07c6e0b0 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -158,6 +158,7 @@ describe Gitlab::BitbucketImport::Importer do
expect { subject.execute }.to change { MergeRequest.count }.by(1)
merge_request = MergeRequest.first
+ expect(merge_request.state).to eq('merged')
expect(merge_request.notes.count).to eq(2)
expect(merge_request.notes.map(&:discussion_id).uniq.count).to eq(1)
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index e8116f0a301..f7bef9e71e2 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -111,6 +111,34 @@ describe Issuable do
end
end
+ describe '.initialize' do
+ it 'maps the state to the right state_id' do
+ described_class::STATE_ID_MAP.each do |key, value|
+ issuable = MergeRequest.new(state: key)
+
+ expect(issuable.state).to eq(key)
+ expect(issuable.state_id).to eq(value)
+ end
+ end
+
+ it 'maps a string version of the state to the right state_id' do
+ described_class::STATE_ID_MAP.each do |key, value|
+ issuable = MergeRequest.new('state' => key)
+
+ expect(issuable.state).to eq(key)
+ expect(issuable.state_id).to eq(value)
+ end
+ end
+
+ it 'gives preference to state_id if present' do
+ issuable = MergeRequest.new('state' => 'opened',
+ 'state_id' => described_class::STATE_ID_MAP['merged'])
+
+ expect(issuable.state).to eq('merged')
+ expect(issuable.state_id).to eq(described_class::STATE_ID_MAP['merged'])
+ end
+ end
+
describe '#milestone_available?' do
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
diff --git a/spec/requests/projects/blob_controller_spec.rb b/spec/requests/projects/blob_controller_spec.rb
new file mode 100644
index 00000000000..b3321375ccc
--- /dev/null
+++ b/spec/requests/projects/blob_controller_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::BlobController do
+ let(:project) { create(:project, :private, :repository) }
+ let(:namespace) { project.namespace }
+
+ context 'anonymous user views blob in inaccessible project' do
+ context 'with default HTML format' do
+ before do
+ get namespace_project_blob_path(namespace_id: namespace, project_id: project, id: 'master/README.md')
+ end
+
+ context 'when project is private' do
+ it { expect(response).to have_gitlab_http_status(:redirect) }
+ end
+
+ context 'when project does not exist' do
+ let(:namespace) { 'non_existent_namespace' }
+ let(:project) { 'non_existent_project' }
+
+ it { expect(response).to have_gitlab_http_status(:redirect) }
+ end
+ end
+
+ context 'with JSON format' do
+ before do
+ get namespace_project_blob_path(namespace_id: namespace, project_id: project, id: 'master/README.md', format: :json)
+ end
+
+ context 'when project is private' do
+ it { expect(response).to have_gitlab_http_status(:unauthorized) }
+ end
+
+ context 'when project does not exist' do
+ let(:namespace) { 'non_existent_namespace' }
+ let(:project) { 'non_existent_project' }
+
+ it { expect(response).to have_gitlab_http_status(:unauthorized) }
+ end
+ end
+ end
+end
diff --git a/spec/requests/user_avatar_spec.rb b/spec/requests/user_avatar_spec.rb
new file mode 100644
index 00000000000..9451674161c
--- /dev/null
+++ b/spec/requests/user_avatar_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Loading a user avatar' do
+ let(:user) { create(:user, :with_avatar) }
+
+ context 'when logged in' do
+ # The exact query count will vary depending on the 2FA settings of the
+ # instance, group, and user. Removing those extra 2FA queries in this case
+ # may not be a good idea, so we just set up the ideal case.
+ before do
+ stub_application_setting(require_two_factor_authentication: true)
+
+ login_as(create(:user, :two_factor))
+ end
+
+ # One each for: current user, avatar user, and upload record
+ it 'only performs three SQL queries' do
+ get user.avatar_url # Skip queries on first application load
+
+ expect(response).to have_gitlab_http_status(200)
+ expect { get user.avatar_url }.not_to exceed_query_limit(3)
+ end
+ end
+
+ context 'when logged out' do
+ # One each for avatar user and upload record
+ it 'only performs two SQL queries' do
+ get user.avatar_url # Skip queries on first application load
+
+ expect(response).to have_gitlab_http_status(200)
+ expect { get user.avatar_url }.not_to exceed_query_limit(2)
+ end
+ end
+end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 561c2b572ec..61110790a43 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -776,10 +776,6 @@ describe 'project routing' do
it 'routes when :template_type is `issue`' do
expect(get(show_with_template_type('issue'))).to route_to('projects/templates#show', namespace_id: 'gitlab', project_id: 'gitlabhq', template_type: 'issue', key: 'template_name', format: 'json')
end
-
- it 'routes to application#route_not_found when :template_type is unknown' do
- expect(get(show_with_template_type('invalid'))).to route_to('application#route_not_found', unmatched_route: 'gitlab/gitlabhq/templates/invalid/template_name')
- end
end
end
diff --git a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
index bc95fcd6b88..b5149a0fcb1 100644
--- a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
+++ b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
@@ -34,15 +34,8 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
context 'when the personal access token has no api scope', unless: params[:public] do
it 'does not log the user in' do
- # Several instances of where these specs are shared route the request
- # through ApplicationController#route_not_found which does not involve
- # the usual auth code from Devise, so does not increment the
- # :user_unauthenticated_counter
- #
- unless params[:ignore_incrementing]
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
- end
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
personal_access_token.update(scopes: [:read_user])
@@ -91,15 +84,8 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
end
it "doesn't log the user in otherwise", unless: params[:public] do
- # Several instances of where these specs are shared route the request
- # through ApplicationController#route_not_found which does not involve
- # the usual auth code from Devise, so does not increment the
- # :user_unauthenticated_counter
- #
- unless params[:ignore_incrementing]
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
- end
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
get path, params: default_params.merge(private_token: 'token')
diff --git a/spec/support/shared_examples/controllers/todos_shared_examples.rb b/spec/support/shared_examples/controllers/todos_shared_examples.rb
index 914bf506320..f3f9abb7da2 100644
--- a/spec/support/shared_examples/controllers/todos_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/todos_shared_examples.rb
@@ -39,7 +39,7 @@ shared_examples 'todos actions' do
post_create
end.to change { user.todos.count }.by(0)
- expect(response).to have_gitlab_http_status(302)
+ expect(response).to have_gitlab_http_status(parent.is_a?(Group) ? 401 : 302)
end
end
end