Commit 8479a297 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 68f3e439f79d
......@@ -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)
......
<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>
<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>
......
<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>
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' }));
},
},
},
{
......
<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>
<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>
query getReadme($url: String!) {
readme(url: $url) @client {
html
}
}
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;
};
......@@ -4,7 +4,12 @@
}
.snippet-filename {
padding: 0 2px;
color: $gl-text-color-secondary;
font-weight: normal;
}
.snippet-info {
color: $gl-text-color-secondary;
}
}
......
......@@ -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 @@ def not_found
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 @@ def set_page_title_header
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 @@ def json_request?
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
......
......@@ -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
......
# 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 @@ def authorize
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
......
......@@ -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]
......
......@@ -137,6 +137,26 @@ def award_emojis_loaded?
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?
......
......@@ -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')
......
......@@ -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
......@@ -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')