Commit 81b9b6f4 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 48960-namespace-diff-module

* master: (29 commits)
  Update the dependencies license list for 11.1.0
  Update .gitignore, .gitlab-ci.yml, and Dockerfile templates for 11.1.0
  This updates only the actual new discussion and not the full tree , which leads to a very costly full rerender
  Resolve "MR Refactor: Improve performance by setting v-once"
  Changed Inline + Parallel Views to v-if instead of v-show
  add basic export to fix timeout problem on import_file_spec.rb
  Add changelog entry for !20465
  Improve render performance of large wiki pages
  Refactor rspec matchers in read_only_spec.rb
  add CHANGELOG.md entry for !20461
  resolve node 6 compatibility issues
  Add missing foreign key in import_export_uploads
  Redesign for mr widget info and pipelines section
  Use proper markdown rendering for previews
  remove extra tick for eks docs
  Make it clear that we need to enable omniauth for SAML and Bitbucket
  Add GPL Commitment language
  Add ExclusiveLease guards for RepositoryCheck::{DispatchWorker,BatchWorker}
  Ability to check if underlying database is read only
  fix spec
  ...
parents 156a9d39 dc71b400
......@@ -39,12 +39,12 @@ export default {
<div class="diff-viewer">
<template v-if="isTextFile">
<inline-diff-view
v-show="isInlineView"
v-if="isInlineView"
:diff-file="diffFile"
:diff-lines="diffFile.highlightedDiffLines || []"
/>
<parallel-diff-view
v-show="isParallelView"
v-if="isParallelView"
:diff-file="diffFile"
:diff-lines="diffFile.parallelDiffLines || []"
/>
......
......@@ -145,6 +145,7 @@ export default {
@click.stop="handleToggle"
/>
<a
v-once
ref="titleWrapper"
:href="titleLink"
class="append-right-4"
......
......@@ -189,6 +189,7 @@ export default {
</button>
<a
v-if="lineNumber"
v-once
:data-linenumber="lineNumber"
:href="lineHref"
>
......
......@@ -60,7 +60,7 @@ export default {
},
methods: {
...mapActions('diffs', ['cancelCommentForm']),
...mapActions(['saveNote', 'fetchDiscussions']),
...mapActions(['saveNote', 'refetchDiscussionById']),
handleCancelCommentForm() {
this.autosave.reset();
this.cancelCommentForm({
......@@ -79,10 +79,10 @@ export default {
});
this.saveNote(postData)
.then(() => {
.then(result => {
const endpoint = this.getNotesDataByProp('discussionsPath');
this.fetchDiscussions(endpoint)
this.refetchDiscussionById({ path: endpoint, discussionId: result.discussion_id })
.then(() => {
this.handleCancelCommentForm();
})
......
......@@ -117,14 +117,6 @@ export default {
<template>
<td
v-if="isContentLine"
:class="lineType"
class="line_content"
v-html="normalizedLine.richText"
>
</td>
<td
v-else
:class="classNameMap"
>
<diff-line-gutter-content
......
......@@ -94,11 +94,12 @@ export default {
:is-hover="isHover"
class="diff-line-num new_line"
/>
<diff-table-cell
<td
v-once
:class="line.type"
:diff-file="diffFile"
:line="line"
:is-content-line="true"
/>
class="line_content"
v-html="line.richText"
>
</td>
</tr>
</template>
......@@ -113,17 +113,15 @@ export default {
:diff-view-type="parallelDiffViewType"
class="diff-line-num old_line"
/>
<diff-table-cell
<td
v-once
:id="line.left.lineCode"
:diff-file="diffFile"
:line="line"
:is-content-line="true"
:line-position="linePositionLeft"
:line-type="parallelViewLeftLineType"
:diff-view-type="parallelDiffViewType"
:class="parallelViewLeftLineType"
class="line_content parallel left-side"
@mousedown.native="handleParallelLineMouseDown"
/>
v-html="line.left.richText"
>
</td>
<diff-table-cell
:diff-file="diffFile"
:line="line"
......@@ -135,16 +133,14 @@ export default {
:diff-view-type="parallelDiffViewType"
class="diff-line-num new_line"
/>
<diff-table-cell
<td
v-once
:id="line.right.lineCode"
:diff-file="diffFile"
:line="line"
:is-content-line="true"
:line-position="linePositionRight"
:line-type="line.right.type"
:diff-view-type="parallelDiffViewType"
:class="line.right.type"
class="line_content parallel right-side"
@mousedown.native="handleParallelLineMouseDown"
/>
v-html="line.right.richText"
>
</td>
</tr>
</template>
......@@ -108,6 +108,11 @@
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
projectPath: {
type: String,
required: true,
......@@ -282,6 +287,7 @@
:issuable-templates="issuableTemplates"
:markdown-docs-path="markdownDocsPath"
:markdown-preview-path="markdownPreviewPath"
:markdown-version="markdownVersion"
:project-path="projectPath"
:project-namespace="projectNamespace"
:show-delete-button="showDeleteButton"
......
......@@ -20,6 +20,11 @@
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
canAttachFile: {
type: Boolean,
required: false,
......@@ -47,6 +52,7 @@
<markdown-field
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:markdown-version="markdownVersion"
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
>
......
......@@ -35,6 +35,11 @@
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
projectPath: {
type: String,
required: true,
......@@ -97,6 +102,7 @@
:form-state="formState"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:markdown-version="markdownVersion"
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
/>
......
......@@ -1251,13 +1251,15 @@ export default class Notes {
var postUrl = $originalContentEl.data('postUrl');
var targetId = $originalContentEl.data('targetId');
var targetType = $originalContentEl.data('targetType');
var markdownVersion = $originalContentEl.data('markdownVersion');
this.glForm = new GLForm($editForm.find('form'), this.enableGFM);
$editForm
.find('form')
.attr('action', `${postUrl}?html=true`)
.attr('data-remote', 'true');
.attr('data-remote', 'true')
.attr('data-markdown-version', markdownVersion);
$editForm.find('.js-form-target-id').val(targetId);
$editForm.find('.js-form-target-type').val(targetType);
$editForm
......
......@@ -34,6 +34,11 @@ export default {
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
},
data() {
return {
......@@ -344,6 +349,7 @@ Please check your network connection and try again.`;
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath"
:markdown-version="markdownVersion"
:add-spacing-classes="false">
<textarea
id="note-body"
......
......@@ -92,6 +92,7 @@ export default {
:is-editing="isEditing"
:note-body="noteBody"
:note-id="note.id"
:markdown-version="note.cached_markdown_version"
@handleFormUpdate="handleFormUpdate"
@cancelForm="formCancelHandler"
/>
......
......@@ -24,6 +24,11 @@ export default {
required: false,
default: 0,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
saveButtonTitle: {
type: String,
required: false,
......@@ -156,6 +161,7 @@ export default {
<markdown-field
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:markdown-version="markdownVersion"
:quick-actions-docs-path="quickActionsDocsPath"
:add-spacing-classes="false">
<textarea
......
......@@ -43,6 +43,11 @@ export default {
required: false,
default: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
},
data() {
return {
......@@ -192,6 +197,7 @@ export default {
<comment-form
:noteable-type="noteableType"
:markdown-version="markdownVersion"
/>
</div>
</template>
......@@ -15,6 +15,7 @@ document.addEventListener('DOMContentLoaded', () => {
const notesDataset = document.getElementById('js-vue-notes').dataset;
const parsedUserData = JSON.parse(notesDataset.currentUserData);
const noteableData = JSON.parse(notesDataset.noteableData);
const { markdownVersion } = notesDataset;
let currentUserData = {};
noteableData.noteableType = notesDataset.noteableType;
......@@ -33,6 +34,7 @@ document.addEventListener('DOMContentLoaded', () => {
return {
noteableData,
currentUserData,
markdownVersion,
notesData: JSON.parse(notesDataset.notesData),
};
},
......@@ -42,6 +44,7 @@ document.addEventListener('DOMContentLoaded', () => {
noteableData: this.noteableData,
notesData: this.notesData,
userData: this.currentUserData,
markdownVersion: this.markdownVersion,
},
});
},
......
......@@ -41,6 +41,15 @@ export const fetchDiscussions = ({ commit }, path) =>
commit(types.SET_INITIAL_DISCUSSIONS, discussions);
});
export const refetchDiscussionById = ({ commit }, { path, discussionId }) =>
service
.fetchDiscussions(path)
.then(res => res.json())
.then(discussions => {
const selectedDiscussion = discussions.find(discussion => discussion.id === discussionId);
if (selectedDiscussion) commit(types.UPDATE_DISCUSSION, selectedDiscussion);
});
export const deleteNote = ({ commit }, note) =>
service.deleteNote(note.path).then(() => {
commit(types.DELETE_NOTE, note);
......
......@@ -114,7 +114,6 @@ export default {
Object.assign(state, { discussions });
},
[types.SET_LAST_FETCHED_AT](state, fetchedAt) {
Object.assign(state, { lastFetchedAt: fetchedAt });
},
......
......@@ -28,12 +28,16 @@ MarkdownPreview.prototype.ajaxCache = {};
MarkdownPreview.prototype.showPreview = function ($form) {
var mdText;
var markdownVersion;
var url;
var preview = $form.find('.js-md-preview');
var url = preview.data('url');
if (preview.hasClass('md-preview-loading')) {
return;
}
mdText = $form.find('textarea.markdown-area').val();
markdownVersion = $form.attr('data-markdown-version');
url = this.versionedPreviewPath(preview.data('url'), markdownVersion);
if (mdText.trim().length === 0) {
preview.text(this.emptyMessage);
......@@ -59,6 +63,14 @@ MarkdownPreview.prototype.showPreview = function ($form) {
}
};
MarkdownPreview.prototype.versionedPreviewPath = function (markdownPreviewPath, markdownVersion) {
if (typeof markdownVersion === 'undefined') {
return markdownPreviewPath;
}
return `${markdownPreviewPath}${markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'}markdown_version=${markdownVersion}`;
};
MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
if (!url) {
return;
......
......@@ -79,66 +79,62 @@ export default {
</script>
<template>
<div class="mr-widget-heading deploy-heading">
<div class="mr-widget-heading deploy-heading append-bottom-default">
<div class="ci-widget media">
<div class="ci-status-icon ci-status-icon-success">
<span class="js-icon-link icon-link">
<status-icon status="success" />
</span>
</div>
<div class="media-body">
<div class="deploy-body">
<template v-if="hasDeploymentMeta">
<span>
Deployed to
</span>
<a
:href="deployment.url"
target="_blank"
rel="noopener noreferrer nofollow"
class="deploy-link js-deploy-meta"
<div class="deployment-info">
<template v-if="hasDeploymentMeta">
<span>
Deployed to
</span>
<a
:href="deployment.url"
target="_blank"
rel="noopener noreferrer nofollow"
class="deploy-link js-deploy-meta"
>
{{ deployment.name }}
</a>
</template>
<span
v-tooltip
v-if="hasDeploymentTime"
:title="deployment.deployed_at_formatted"
class="js-deploy-time"
>
{{ deployment.name }}
</a>
</template>
<template v-if="hasExternalUrls">
<span>
on
{{ deployTimeago }}
</span>
<memory-usage
v-if="hasMetrics"
:metrics-url="deployment.metrics_url"
:metrics-monitoring-url="deployment.metrics_monitoring_url"
/>
</div>
<div>
<a
v-if="hasExternalUrls"
:href="deployment.external_url"
target="_blank"
rel="noopener noreferrer nofollow"
class="deploy-link js-deploy-url"
class="deploy-link js-deploy-url btn btn-default btn-sm inline"
>
{{ deployment.external_url_formatted }}
<icon
:size="16"
name="external-link"
/>
<span>
View app
<icon name="external-link" />
</span>
</a>
</template>
<span
v-tooltip
v-if="hasDeploymentTime"
:title="deployment.deployed_at_formatted"
class="js-deploy-time"
>
{{ deployTimeago }}
</span>
<loading-button
v-if="deployment.stop_url"
:loading="isStopping"
container-class="btn btn-default btn-sm prepend-left-default"
label="Stop environment"
@click="stopEnvironment"
/>
<loading-button
v-if="deployment.stop_url"
:loading="isStopping"
container-class="btn btn-default btn-sm inline prepend-left-4"
title="Stop environment"
@click="stopEnvironment"
>
<icon name="stop" />
</loading-button>
</div>
</div>
<memory-usage
v-if="hasMetrics"
:metrics-url="deployment.metrics_url"
:metrics-monitoring-url="deployment.metrics_monitoring_url"
/>
</div>
</div>
</div>
......
......@@ -2,7 +2,7 @@
import tooltip from '~/vue_shared/directives/tooltip';
import { n__ } from '~/locale';
import { webIDEUrl } from '~/lib/utils/url_utility';
import icon from '~/vue_shared/components/icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
export default {
......@@ -11,7 +11,7 @@ export default {
tooltip,
},
components: {
icon,
Icon,
clipboardButton,
},
props: {
......@@ -54,104 +54,114 @@ export default {
};
</script>
<template>
<div class="mr-source-target">
<div class="normal">
<strong>
{{ s__("mrWidget|Request to merge") }}
<span
:class="{ 'label-truncated': isSourceBranchLong }"
:title="isSourceBranchLong ? mr.sourceBranch : ''"
:v-tooltip="isSourceBranchLong"
class="label-branch js-source-branch"
data-placement="bottom"
v-html="mr.sourceBranchLink"
>
</span>
<div class="mr-source-target append-bottom-default">
<div class="git-merge-icon-container append-right-default">
<icon name="git-merge" />
</div>
<div class="git-merge-container d-flex">
<div class="normal">
<strong>
{{ s__("mrWidget|Request to merge") }}
<span
:class="{ 'label-truncated': isSourceBranchLong }"
:title="isSourceBranchLong ? mr.sourceBranch : ''"
:v-tooltip="isSourceBranchLong"
class="label-branch js-source-branch"
data-placement="bottom"
v-html="mr.sourceBranchLink"
>
</span>
<clipboard-button
:text="branchNameClipboardData"
:title="__('Copy branch name to clipboard')"
css-class="btn-default btn-transparent btn-clipboard"
/>
<clipboard-button
:text="branchNameClipboardData"
:title="__('Copy branch name to clipboard')"
css-class="btn-default btn-transparent btn-clipboard"
/>
{{ s__("mrWidget|into") }}
{{ s__("mrWidget|into") }}
<span
:v-tooltip="isTargetBranchLong"
:class="{ 'label-truncatedtooltip': isTargetBranchLong }"
:title="isTargetBranchLong ? mr.targetBranch : ''"
class="label-branch"
data-placement="bottom"
>
<a
:href="mr.targetBranchTreePath"
class="js-target-branch"
<span
:v-tooltip="isTargetBranchLong"
:class="{ 'label-truncatedtooltip': isTargetBranchLong }"
:title="isTargetBranchLong ? mr.targetBranch : ''"
class="label-branch"
data-placement="bottom"
>
{{ mr.targetBranch }}
</a>
</span>
</strong>
<span
v-if="shouldShowCommitsBehindText"
class="diverged-commits-count"
>
(<a :href="mr.targetBranchPath">{{ commitsText }}</a>)
</span>
</div>
<a
:href="mr.targetBranchTreePath"
class="js-target-branch"
>
{{ mr.targetBranch }}
</a>
</span>
</strong>
<div
v-if="shouldShowCommitsBehindText"
class="diverged-commits-count"
>
<span class="monospace">{{ mr.sourceBranch }}</span>
is {{ commitsText }}
<span class="monospace">{{ mr.targetBranch }}</span>
</div>
</div>
<div v-if="mr.isOpen">
<a
v-if="!mr.sourceBranchRemoved"
:href="webIdePath"
class="btn btn-sm btn-default inline js-web-ide"
>
{{ s__("mrWidget|Web IDE") }}
</a>
<button
:disabled="mr.sourceBranchRemoved"
data-target="#modal_merge_info"
data-toggle="modal"
class="btn btn-sm btn-default inline js-check-out-branch"
type="button"
<div
v-if="mr.isOpen"
class="branch-actions"
>
{{ s__("mrWidget|Check out branch") }}
</button>
<span class="dropdown prepend-left-10">
<a
v-if="!mr.sourceBranchRemoved"
:href="webIdePath"
class="btn btn-default inline js-web-ide d-none d-md-inline-block"
>
{{ s__("mrWidget|Open in Web IDE") }}
</a>
<button
:disabled="mr.sourceBranchRemoved"
data-target="#modal_merge_info"
data-toggle="modal"
class="btn btn-default inline js-check-out-branch"
type="button"
class="btn btn-sm inline dropdown-toggle"
data-toggle="dropdown"
aria-label="Download as"
aria-haspopup="true"
aria-expanded="false"
>
<icon name="download" />
<i
class="fa fa-caret-down"
aria-hidden="true">
</i>
{{ s__("mrWidget|Check out branch") }}
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li>
<a
:href="mr.emailPatchesPath"
class="js-download-email-patches"
download
>
{{ s__("mrWidget|Email patches") }}
</a>
</li>
<li>
<a
:href="mr.plainDiffPath"
class="js-download-plain-diff"
download
>
{{ s__("mrWidget|Plain diff") }}
</a>
</li>
</ul>
</span>
<span class="dropdown prepend-left-10">
<button
type="button"
class="btn inline dropdown-toggle"
data-toggle="dropdown"
aria-label="Download as"
aria-haspopup="true"
aria-expanded="false"
>
<icon name="download" />
<i
class="fa fa-caret-down"
aria-hidden="true">
</i>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li>
<a
:href="mr.emailPatchesPath"
class="js-download-email-patches"
download
>
{{ s__("mrWidget|Email patches") }}
</a>
</li>
<li>
<a
:href="mr.plainDiffPath"
class="js-download-plain-diff"
download
>
{{ s__("mrWidget|Plain diff") }}
</a>
</li>
</ul>
</span>
</div>
</div>
</div>
</template>
......@@ -26,6 +26,10 @@ export default {
type: String,
required: false,
},
sourceBranchLink: {
type: String,
required: false,
},
},
computed: {
hasPipeline() {
......@@ -54,12 +58,18 @@ export default {
<template>
<div
v-if="hasPipeline || hasCIError"
class="mr-widget-heading"
class="mr-widget-heading append-bottom-default"
>
<div class="ci-widget media">
<template v-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10">
<icon name="status_failed" />
<div
class="add-border ci-status-icon ci-status-icon-failed ci-error
js-ci-error append-right-default"
>
<icon
:size="32"
name="status_failed_borderless"
/>
</div>
<div class="media-body">
Could not connect to the CI server. Please check your settings and try again
......@@ -68,50 +78,66 @@ export default {
<template v-else-if="hasPipeline">
<a
:href="status.details_path"
class="append-right-10"
class="align-self-start append-right-default"
>
<ci-icon :status="status" />
<ci-icon
:status="status"
:size="32"
:borderless="true"
class="add-border"
/>
</a>
<div class="ci-widget-container d-flex">
<div class="ci-widget-content">
<div class="media-body">
<div class="font-weight-bold">
Pipeline
<a
:href="pipeline.path"
class="pipeline-id font-weight-normal pipeline-number"
>#{{ pipeline.id }}</a>
<div class="media-body">
Pipeline
<a
:href="pipeline.path"
class="pipeline-id"
>
#{{ pipeline.id }}
</a>
{{ pipeline.details.status.label }}
{{ pipeline.details.status.label }}
<template v-if="hasCommitInfo">
for
<a
:href="pipeline.commit.commit_path"
class="commit-sha js-commit-link"
>
{{ pipeline.commit.short_id }}</a>.
</template>
<span class="mr-widget-pipeline-graph">
<span
v-if="hasStages"
class="stage-cell"
>
<template v-if="hasCommitInfo">
for
<a
:href="pipeline.commit.commit_path"
class="commit-sha js-commit-link font-weight-normal"
>
{{ pipeline.commit.short_id }}</a>
on
<span
class="label-branch"
v-html="sourceBranchLink"
>
</span>
</template>
</div>
<div
v-for="(stage, i) in pipeline.details.stages"
:key="i"
class="stage-container dropdown js-mini-pipeline-graph"
v-if="pipeline.coverage"
class="coverage"
>
<pipeline-stage :stage="stage" />
Coverage {{ pipeline.coverage }}%
</div>
</div>
</div>
<div>
<span class="mr-widget-pipeline-graph">
<span
v-if="hasStages"
class="stage-cell"
>
<div
v-for="(stage, i) in pipeline.details.stages"
:key="i"
class="stage-container dropdown js-mini-pipeline-graph mr-widget-pipeline-stages"
>
<pipeline-stage :stage="stage" />
</div>
</span>
</span>
</span>
<template v-if="pipeline.coverage">
Coverage {{ pipeline.coverage }}%
</template>
</div>
</div>
</template>
</div>
......
......@@ -43,6 +43,7 @@
<ci-icon
v-else
:status="statusObj"
:size="24"
/>
<button
......
......@@ -252,41 +252,44 @@ export default {
:pipeline="mr.pipeline"
:ci-status="mr.ciStatus"
:has-ci="mr.hasCI"
:source-branch-link="mr.sourceBranchLink"
/>
<deployment
v-for="deployment in mr.deployments"
:key="deployment.id"
:deployment="deployment"
/>
<div class="mr-widget-section">
<component
:is="componentName"
:mr="mr"
:service="service"
/>
<div class="mr-section-container">
<div class="mr-widget-section">
<component
:is="componentName"
:mr="mr"
:service="service"
/>
<section
v-if="mr.allowCollaboration"
class="mr-info-list mr-links"
>
{{ s__("mrWidget|Allows commits from members who can merge to the target branch") }}
</section>
<section
v-if="mr.allowCollaboration"
class="mr-info-list mr-links"
>
{{ s__("mrWidget|Allows commits from members who can merge to the target branch") }}
</section>
<mr-widget-related-links
v-if="shouldRenderRelatedLinks"
:state="mr.state"
:related-links="mr.relatedLinks"
/>
<mr-widget-related-links
v-if="shouldRenderRelatedLinks"
:state="mr.state"
:related-links="mr.relatedLinks"
/>
<source-branch-removal-status
v-if="shouldRenderSourceBranchRemovalStatus"
/>
</div>
<div
v-if="shouldRenderMergeHelp"
class="mr-widget-footer"
>
<mr-widget-merge-help />
<source-branch-removal-status
v-if="shouldRenderSourceBranchRemovalStatus"
/>
</div>
<div
v-if="shouldRenderMergeHelp"
class="mr-widget-footer"
>
<mr-widget-merge-help />
</div>
</div>
</div>
</template>
<script>
import $ from 'jquery';
import { s__ } from '~/locale';
import Flash from '../../../flash';
import GLForm from '../../../gl_form';
import markdownHeader from './header.vue';
......@@ -22,6 +23,11 @@
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
addSpacingClasses: {
type: Boolean,
required: false,
......@@ -92,10 +98,11 @@
if (text) {
this.markdownPreviewLoading = true;
this.$http.post(this.markdownPreviewPath, { text })
.then(resp => resp.json())
.then(data => this.renderMarkdown(data))
.catch(() => new Flash('Error loading markdown preview'));
this.$http
.post(this.versionedPreviewPath(), { text })
.then(resp => resp.json())
.then(data => this.renderMarkdown(data))
.catch(() => new Flash(s__('Error loading markdown preview')));
} else {
this.renderMarkdown();
}
......@@ -119,6 +126,13 @@
$(this.$refs['markdown-preview']).renderGFM();
});
},
versionedPreviewPath() {
const { markdownPreviewPath, markdownVersion } = this;
return `${markdownPreviewPath}${
markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'
}markdown_version=${markdownVersion}`;
},
},
};
</script>
......
......@@ -3,12 +3,20 @@
svg {
fill: $green-500;
}
&.add-border {
@include borderless-status-icon($green-500);
}
}
.ci-status-icon-failed {
svg {
fill: $gl-danger;
}
&.add-border {
@include borderless-status-icon($red-500);
}
}
.ci-status-icon-pending,
......@@ -17,12 +25,20 @@
svg {
fill: $orange-500;
}
&.add-border {
@include borderless-status-icon($orange-500);
}
}
.ci-status-icon-running {
svg {
fill: $blue-400;
}
&.add-border {
@include borderless-status-icon($blue-400);
}
}
.ci-status-icon-canceled,
......@@ -30,6 +46,10 @@
svg {
fill: $gl-text-color;
}
&.add-border {
@include borderless-status-icon($gl-text-color);
}
}
.ci-status-icon-created,
......@@ -38,6 +58,10 @@
svg {
fill: $gray-darkest;
}
&.add-border {
@include borderless-status-icon($gray-darkest);
}
}
.ci-status-icon-manual {
......
......@@ -232,3 +232,10 @@
word-break: break-word;
max-width: 100%;
}
@mixin borderless-status-icon($color) {
svg {
border: 1px solid $color;
border-radius: 50%;
}
}
......@@ -350,7 +350,8 @@ code {
}
.commit-sha,
.ref-name {
.ref-name,
.pipeline-number {
@extend .monospace;
font-size: 95%;
}
......
......@@ -743,6 +743,7 @@ Pipeline Graph
*/
$stage-hover-bg: $gray-darker;
$ci-action-icon-size: 22px;
$ci-action-icon-size-lg: 24px;
$pipeline-dropdown-line-height: 20px;
$pipeline-dropdown-status-icon-size: 18px;
$ci-action-dropdown-button-size: 24px;
......
......@@ -15,16 +15,38 @@
}
}
.mr-widget-heading {
position: relative;
border: 1px solid $border-color;
border-radius: 4px;
&:not(.deploy-heading)::before {
content: '';
border-left: 1px solid $theme-gray-200;
position: absolute;
left: 32px;
top: -17px;
height: 16px;
}
}
.mr-section-container {
border: 1px solid $border-color;
border-radius: $border-radius-default;
border-top: 0;
}
.mr-widget-heading,
.mr-widget-section,
.mr-widget-footer {
padding: $gl-padding;
}
.mr-state-widget {
color: $gl-text-color;
border: 1px solid $border-color;
border-radius: 2px;
line-height: 28px;
.mr-widget-heading,
.mr-widget-section,
.mr-widget-footer {
padding: $gl-padding;
border-top: solid 1px $border-color;
}
......@@ -124,10 +146,17 @@
.ci-widget {
color: $gl-text-color;
display: flex;
align-items: center;
justify-content: space-between;
@include media-breakpoint-down(xs) {
flex-wrap: wrap;
}
.ci-widget-content {
display: flex;
align-items: center;
}
}
.mr-widget-icon {
......@@ -136,8 +165,6 @@
}
.ci-status-icon svg {
width: $status-icon-size;
height: $status-icon-size;
margin: 3px 0;
position: relative;
overflow: visible;
......@@ -145,8 +172,6 @@
}
.mr-widget-pipeline-graph {
padding: 0 4px;
.dropdown-menu {
z-index: 300;
}
......@@ -157,7 +182,7 @@
}
.normal {
line-height: 28px;
flex: 1;
}
.capitalize {
......@@ -168,7 +193,7 @@
@extend .ref-name;
color: $gl-text-color;
font-weight: $gl-font-weight-bold;
font-weight: normal;
overflow: hidden;
word-break: break-all;
......@@ -192,6 +217,8 @@
}
.mr-widget-body {
line-height: 28px;
@include clearfix;
&.media > *:first-child {
......@@ -474,18 +501,66 @@
}
}
.merge-request-details .content-block {
border-bottom: 0;
}
.mr-source-target {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
background-color: $gray-light;
border-radius: $border-radius-default $border-radius-default 0 0;
padding: $gl-padding / 2 $gl-padding;
border-radius: $border-radius-default;
padding: $gl-padding;
border: 1px solid $border-color;
min-height: 69px;
@include media-breakpoint-up(md) {
align-items: center;
}
.dropdown-toggle .fa {
color: $gl-text-color;
}
.git-merge-icon-container {
border: 1px solid $theme-gray-400;
border-radius: 50%;
height: 32px;
width: 32px;
color: $theme-gray-700;
line-height: 28px;
.ic-git-merge {
vertical-align: middle;
width: 31px;
}
}
.git-merge-container {
justify-content: space-between;
flex: 1;
flex-direction: row;
align-items: center;
@include media-breakpoint-down(md) {
flex-direction: column;
align-items: flex-start;
.branch-actions {
margin-top: 16px;
}
}
@include media-breakpoint-up(lg) {
.branch-actions {
align-self: center;
}
}
}
.diverged-commits-count {
color: $gl-text-color-secondary;
font-size: 12px;
}
}
.card-new-merge-request {
......@@ -720,13 +795,25 @@
}
.deploy-heading {
margin-top: -19px;
border-top-left-radius: 0;
border-top-right-radius: 0;
background-color: $gray-light;
@include media-breakpoint-up(md) {
padding: $gl-padding-8 $gl-padding;
}
.media-body {
min-width: 0;
font-size: 12px;
margin-left: 48px;
}
}
.deploy-body {
display: flex;
align-items: center;
flex-wrap: wrap;
@include media-breakpoint-up(xs) {
......@@ -734,6 +821,15 @@
white-space: nowrap;
}
@include media-breakpoint-down(md) {
flex-direction: column;
align-items: flex-start;
.deployment-info {
margin-bottom: $gl-padding;
}
}
> *:not(:last-child) {
margin-right: .3em;
}
......@@ -741,18 +837,22 @@
svg {
vertical-align: text-top;
}
}
.deploy-link {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 100px;
max-width: 150px;
.deployment-info {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 100px;
@include media-breakpoint-up(xs) {
min-width: 0;
max-width: 100%;
@include media-breakpoint-up(xs) {
min-width: 0;
max-width: 100%;
}
}
.btn svg {
fill: $theme-gray-700;
}
}
......@@ -772,3 +872,33 @@
}
}
}
.ci-widget-container {
justify-content: space-between;
flex: 1;
flex-direction: row;
@include media-breakpoint-down(md) {
flex-direction: column;
.stage-cell .stage-container {
margin-top: 16px;
}
.dropdown .mini-pipeline-graph-dropdown-menu.dropdown-menu {
transform: initial;
}
}
.coverage {
font-size: 12px;
color: $theme-gray-700;
line-height: initial;
}
.mini-pipeline-graph-dropdown-toggle,
.stage-cell .mini-pipeline-graph-dropdown-toggle svg {
height: $ci-action-icon-size-lg;
width: $ci-action-icon-size-lg;
}
}
......@@ -301,6 +301,21 @@
border-bottom: 2px solid $border-color;
}
}
//delete when all pipelines are updated to new size
&.mr-widget-pipeline-stages {
+ .stage-container {
margin-left: 4px;
}
&:not(:last-child) {
&::after {
width: 4px;
right: -4px;
top: 11px;
}
}
}
}
}
......
......@@ -14,6 +14,8 @@ module PreviewMarkdown
else {}
end
markdown_params[:markdown_engine] = result[:markdown_engine]
render json: {
body: view_context.markdown(result[:text], markdown_params),
references: {
......
......@@ -2,6 +2,7 @@ class ProjectsController < Projects::ApplicationController
include IssuableCollections
include ExtractsPath
include PreviewMarkdown
include SendFileUpload
before_action :whitelist_query_limiting, only: [:create]
before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
......@@ -188,9 +189,9 @@ class ProjectsController < Projects::ApplicationController
end
def download_export
export_project_path = @project.export_project_path
if export_project_path
if export_project_object_storage?
send_upload(@project.import_export_upload.export_file)
elsif export_project_path
send_file export_project_path, disposition: 'attachment'
else
redirect_to(
......@@ -265,8 +266,6 @@ class ProjectsController < Projects::ApplicationController
render json: options.to_json
end
private
# Render project landing depending of which features are available
# So if page is not availble in the list it renders the next page
#
......@@ -424,4 +423,12 @@ class ProjectsController < Projects::ApplicationController
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42440')
end
def export_project_path
@export_project_path ||= @project.export_project_path
end
def export_project_object_storage?
@project.export_project_object_exists?
end
end
......@@ -249,6 +249,7 @@ module IssuablesHelper
issuableRef: issuable.to_reference,
markdownPreviewPath: preview_markdown_path(parent),
markdownDocsPath: help_page_path('user/markdown'),
markdownVersion: issuable.cached_markdown_version,
issuableTemplates: issuable_templates(issuable),
initialTitleHtml: markdown_field(issuable, :title),
initialTitleText: issuable.title,
......
......@@ -107,6 +107,7 @@ module MarkupHelper
def markup(file_name, text, context = {})
context[:project] ||= @project
context[:markdown_engine] ||= :redcarpet
html = context.delete(:rendered) || markup_unsafe(file_name, text, context)
prepare_for_rendering(html, context)
end
......@@ -120,7 +121,8 @@ module MarkupHelper
project: @project,
project_wiki: @project_wiki,
page_slug: wiki_page.slug,
issuable_state_filter_enabled: true
issuable_state_filter_enabled: true,
markdown_engine: :redcarpet
}
html =
......
......@@ -169,6 +169,7 @@ module NotesHelper
registerPath: new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'),
newSessionPath: new_session_path(:user, redirect_to_referer: 'yes'),
markdownDocsPath: help_page_path('user/markdown'),
markdownVersion: issuable.cached_markdown_version,
quickActionsDocsPath: help_page_path('user/project/quick_actions'),
closePath: close_issuable_path(issuable),
reopenPath: reopen_issuable_path(issuable),
......
......@@ -153,7 +153,7 @@ class NotifyPreview < ActionMailer::Preview
cleanup do
note = yield
Notify.public_send(method, user.id, note)
Notify.public_send(method, user.id, note) # rubocop:disable GitlabSecurity/PublicSend
end
end
......
......@@ -40,6 +40,18 @@ module CacheMarkdownField
end
end
class MarkdownEngine
def self.from_version(version = nil)
return :common_mark if version.nil? || version == 0
if version < CacheMarkdownField::CACHE_COMMONMARK_VERSION_START
:redcarpet
else
:common_mark
end
end
end
def skip_project_check?
false
end
......@@ -57,7 +69,7 @@ module CacheMarkdownField
# Banzai is less strict about authors, so don't always have an author key
context[:author] = self.author if self.respond_to?(:author)
context[:markdown_engine] = markdown_engine
context[:markdown_engine] = MarkdownEngine.from_version(latest_cached_markdown_version)
context
end
......@@ -123,14 +135,6 @@ module CacheMarkdownField
end
end
def markdown_engine
if latest_cached_markdown_version < CacheMarkdownField::CACHE_COMMONMARK_VERSION_START
:redcarpet
else
:common_mark
end
end
included do
cattr_reader :cached_markdown_fields do
FieldData.new
......
......@@ -7,7 +7,7 @@ module CacheableAttributes
class_methods do
def cache_key
"#{name}:#{Gitlab::VERSION}:#{Gitlab.migrations_hash}:#{Rails.version}".freeze
"#{name}:#{Gitlab::VERSION}:#{Rails.version}".freeze
end
# Can be overriden
......@@ -69,6 +69,6 @@ module CacheableAttributes
end
def cache!
Rails.cache.write(self.class.cache_key, self)
Rails.cache.write(self.class.cache_key, self, expires_in: 1.minute)
end
end
class ImportExportUpload < ActiveRecord::Base
include WithUploads
include ObjectStorage::BackgroundMove
belongs_to :project
mount_uploader :import_file, ImportExportUploader
mount_uploader :export_file, ImportExportUploader
def retrieve_upload(_identifier, paths)
Upload.find_by(model: self, path: paths)
end
end
......@@ -171,6 +171,7 @@ class Project < ActiveRecord::Base
has_one :fork_network, through: :fork_network_member
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id'
......@@ -1712,7 +1713,7 @@ class Project < ActiveRecord::Base
:started
elsif after_export_in_progress?
:after_export_action
elsif export_project_path
elsif export_project_path || export_project_object_exists?
:finished
else
:none
......@@ -1727,16 +1728,21 @@ class Project < ActiveRecord::Base
import_export_shared.after_export_in_progress?
end
def remove_exports
return nil unless export_path.present?
FileUtils.rm_rf(export_path)
def remove_exports(path = export_path)
if path.present?
FileUtils.rm_rf(path)
elsif export_project_object_exists?
import_export_upload.remove_export_file!
import_export_upload.save
end
end
def remove_exported_project_file
return unless export_project_path.present?
remove_exports(export_project_path)
end
FileUtils.rm_f(export_project_path)
def export_project_object_exists?
Gitlab::ImportExport.object_storage? && import_export_upload&.export_file&.file
end
def full_path_slug
......
......@@ -83,7 +83,7 @@ class Repository
@raw_repository&.cleanup
end
# Return absolute path to repository
# Don't use this! It's going away. Use Gitaly to read or write from repos.
def path_to_repo
@path_to_repo ||=
begin
......@@ -250,7 +250,7 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes)
raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false)
rescue Gitlab::Git::CommandError => ex
Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}"
end
def kept_around?(sha)
......@@ -564,7 +564,7 @@ class Repository
end
def rendered_readme
MarkupHelper.markup_unsafe(readme.name, readme.data, project: project) if readme
MarkupHelper.markup_unsafe(readme.name, readme.data, project: project, markdown_engine: :redcarpet) if readme
end
cache_method :rendered_readme
......
......@@ -62,6 +62,8 @@ class NoteEntity < API::Entities::Note
expose :attachment, using: NoteAttachmentEntity, if: -> (note, _) { note.attachment? }
expose :cached_markdown_version
private
def current_user
......
......@@ -10,7 +10,9 @@ class ImportExportCleanUpService
def execute
Gitlab::Metrics.measure(:import_export_clean_up) do
next unless File.directory?(path)
clean_up_export_object_files
break unless File.directory?(path)
clean_up_export_files
end
......@@ -21,4 +23,11 @@ class ImportExportCleanUpService
def clean_up_export_files
Gitlab::Popen.popen(%W(find #{path} -not -path #{path} -mmin +#{mmin} -delete))
end
def clean_up_export_object_files
ImportExportUpload.where('updated_at < ?', mmin.minutes.ago).each do |upload|
upload.remove_export_file!
upload.save!
end
end
end
......@@ -6,7 +6,8 @@ class PreviewMarkdownService < BaseService
success(
text: text,
users: users,
commands: commands.join(' ')
commands: commands.join(' '),
markdown_engine: markdown_engine
)
end
......@@ -42,4 +43,8 @@ class PreviewMarkdownService < BaseService
def commands_target_id
params[:quick_actions_target_id]
end
def markdown_engine
CacheMarkdownField::MarkdownEngine.from_version(params[:markdown_version].to_i)
end
end
class ImportExportUploader < AttachmentUploader
EXTENSION_WHITELIST = %w[tar.gz].freeze
def extension_whitelist
EXTENSION_WHITELIST
end
def move_to_store
true
end
def move_to_cache
false
end
end
......@@ -31,7 +31,7 @@
%li Any encrypted tokens
%p
Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.
- if project.export_project_path
- if project.export_status == :finished
= link_to 'Download export', download_export_project_path(project),
rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
= link_to 'Generate new export', generate_new_export_project_path(project),
......
= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'issue-form common-note-form js-quick-submit js-requires-input' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @issue],
html: { class: 'issue-form common-note-form js-quick-submit js-requires-input' },
data: { markdown_version: @issue.cached_markdown_version } do |f|
= render 'shared/issuable/form', f: f, issuable: @issue
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form common-note-form js-requires-input js-quick-submit' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request],
html: { class: 'merge-request-form common-note-form js-requires-input js-quick-submit' },
data: { markdown_version: @merge_request.cached_markdown_version } do |f|
= render 'shared/issuable/form', f: f, issuable: @merge_request
= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'milestone-form common-note-form js-quick-submit js-requires-input'} do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @milestone],
html: {class: 'milestone-form common-note-form js-quick-submit js-requires-input'},
data: { markdown_version: @milestone.cached_markdown_version } do |f|
= form_errors(@milestone)
.row
.col-md-6
......
......@@ -11,7 +11,9 @@
%strong= @tag.name
= form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name), html: { class: 'common-note-form release-form js-quick-submit' }) do |f|
= form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name),
html: { class: 'common-note-form release-form js-quick-submit' },
data: { markdown_version: @release.cached_markdown_version }) do |f|
= render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do
= render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here…"
= render 'shared/notes/hints'
......
- commit_message = @page.persisted? ? s_("WikiPageEdit|Update %{page_title}") : s_("WikiPageCreate|Create %{page_title}")
- commit_message = commit_message % { page_title: @page.title }
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'wiki-form common-note-form prepend-top-default js-quick-submit' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post,
html: { class: 'wiki-form common-note-form prepend-top-default js-quick-submit' },
data: { markdown_version: CacheMarkdownField::CACHE_REDCARPET_VERSION } do |f|
= form_errors(@page)
- if @page.persisted?
......
......@@ -52,7 +52,7 @@
.note-text.md
= markdown_field(note, :note)
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago')
.original-note-content.hidden{ data: { post_url: note_url(note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
.original-note-content.hidden{ data: { post_url: note_url(note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore, markdown_version: note.cached_markdown_version } }
#{note.note}
- if note_editable
= render 'shared/notes/edit', note: note
......
......@@ -2,7 +2,9 @@
= page_specific_javascript_tag('lib/ace.js')
.snippet-form-holder
= form_for @snippet, url: url, html: { class: "snippet-form js-requires-input js-quick-submit common-note-form" } do |f|
= form_for @snippet, url: url,
html: { class: "snippet-form js-requires-input js-quick-submit common-note-form" },
data: { markdown_version: @snippet.cached_markdown_version } do |f|
= form_errors(@snippet)
.form-group.row
......
......@@ -4,9 +4,11 @@ module RepositoryCheck
class BatchWorker
include ApplicationWorker
include RepositoryCheckQueue
include ExclusiveLeaseGuard
RUN_TIME = 3600
BATCH_SIZE = 10_000
LEASE_TIMEOUT = 1.hour
attr_reader :shard_name
......@@ -16,6 +18,20 @@ module RepositoryCheck
return unless Gitlab::CurrentSettings.repository_checks_enabled
return unless Gitlab::ShardHealthCache.healthy_shard?(shard_name)
try_obtain_lease do
perform_repository_checks
end
end
def lease_timeout
LEASE_TIMEOUT
end
def lease_key
"repository_check_batch_worker:#{shard_name}"
end
def perform_repository_checks
start = Time.now
# This loop will break after a little more than one hour ('a little
......@@ -26,7 +42,7 @@ module RepositoryCheck
project_ids.each do |project_id|
break if Time.now - start >= RUN_TIME
next unless try_obtain_lease(project_id)
next unless try_obtain_lease_for_project(project_id)
SingleRepositoryWorker.new.perform(project_id)
end
......@@ -60,7 +76,7 @@ module RepositoryCheck
Project.where(repository_storage: shard_name)
end
def try_obtain_lease(id)
def try_obtain_lease_for_project(id)
# Use a 24-hour timeout because on servers/projects where 'git fsck' is
# super slow we definitely do not want to run it twice in parallel.
Gitlab::ExclusiveLease.new(
......
......@@ -3,13 +3,22 @@ module RepositoryCheck
include ApplicationWorker
include CronjobQueue
include ::EachShardWorker
include ExclusiveLeaseGuard
LEASE_TIMEOUT = 1.hour
def perform
return unless Gitlab::CurrentSettings.repository_checks_enabled
each_eligible_shard do |shard_name|
RepositoryCheck::BatchWorker.perform_async(shard_name)
try_obtain_lease do
each_eligible_shard do |shard_name|
RepositoryCheck::BatchWorker.perform_async(shard_name)
end
end
end
def lease_timeout
LEASE_TIMEOUT
end
end
end
---
title: Add Object Storage to project export
merge_request: 20105
author:
type: added
---
title: Resolve compatibility issues with node 6
merge_request: 20461
author:
type: fixed
---
title: Stop relying on migrations in the CacheableAttributes cache key and cache attributes
for 1 minute instead
merge_request: 20389
author:
type: fixed
---
title: Improve render performance of large wiki pages
merge_request: 20465
author: Peter Leitzen
type: performance
......@@ -39,7 +39,7 @@ Rails.application.configure do
config.action_mailer.delivery_method = :letter_opener_web
# Don't make a mess when bootstrapping a development environment
config.action_mailer.perform_deliveries = (ENV['BOOTSTRAP'] != '1')
config.action_mailer.preview_path = 'spec/mailers/previews'
config.action_mailer.preview_path = 'app/mailers/previews'
config.eager_load = false
......
......@@ -160,6 +160,9 @@ production: &base
# aws_access_key_id: AWS_ACCESS_KEY_ID
# aws_secret_access_key: AWS_SECRET_ACCESS_KEY
# region: us-east-1
# aws_signature_version: 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
# endpoint: 'https://s3.amazonaws.com' # default: nil - Useful for S3 compliant services such as DigitalOcean Spaces
## Git LFS
lfs:
......@@ -180,6 +183,7 @@ production: &base
# Use the following options to configure an AWS compatible host
# host: 'localhost' # default: s3.amazonaws.com
# endpoint: 'http://127.0.0.1:9000' # default: nil
# aws_signature_version: 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
# path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object'
## Uploads (attachments, avatars, etc...)
......@@ -197,6 +201,7 @@ production: &base
provider: AWS
aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
aws_signature_version: 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
region: us-east-1
# host: 'localhost' # default: s3.amazonaws.com
# endpoint: 'http://127.0.0.1:9000' # default: nil
......
class CreateImportExportUploads < ActiveRecord::Migration
DOWNTIME = false
def change
create_table :import_export_uploads do |t|
t.datetime_with_timezone :updated_at, null: false
t.references :project, index: true, foreign_key: { on_delete: :cascade }, unique: true
t.text :import_file
t.text :export_file
end
add_index :import_export_uploads, :updated_at
end
end
......@@ -949,6 +949,16 @@ ActiveRecord::Schema.define(version: 20180702120647) do
add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
create_table "import_export_uploads", force: :cascade do |t|
t.datetime_with_timezone "updated_at", null: false
t.integer "project_id"
t.text "import_file"
t.text "export_file"
end
add_index "import_export_uploads", ["project_id"], name: "index_import_export_uploads_on_project_id", using: :btree
add_index "import_export_uploads", ["updated_at"], name: "index_import_export_uploads_on_updated_at", using: :btree
create_table "internal_ids", id: :bigserial, force: :cascade do |t|
t.integer "project_id"
t.integer "usage", null: false
......@@ -2252,6 +2262,7 @@ ActiveRecord::Schema.define(version: 20180702120647) do
add_foreign_key "gpg_signatures", "gpg_keys", on_delete: :nullify
add_foreign_key "gpg_signatures", "projects", on_delete: :cascade
add_foreign_key "group_custom_attributes", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "import_export_uploads", "projects", on_delete: :cascade
add_foreign_key "internal_ids", "namespaces", name: "fk_162941d509", on_delete: :cascade
add_foreign_key "internal_ids", "projects", on_delete: :cascade
add_foreign_key "issue_assignees", "issues", name: "fk_b7d881734a", on_delete: :cascade
......
......@@ -30,5 +30,12 @@ sudo gitlab-rake gitlab:import_export:data
bundle exec rake gitlab:import_export:data RAILS_ENV=production
```
In order to enable Object Storage on the Export, you can use the [feature flag][feature-flags]:
```
import_export_object_storage
```
[ce-3050]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3050
[feature-flags]: https://docs.gitlab.com/ee/api/features.html
[tmp]: ../../development/shared_files.md
......@@ -10,12 +10,12 @@ To view rendered emails "sent" in your development instance, visit
Rails provides a way to preview our mailer templates in HTML and plaintext using
dummy data.
The previews live in [`spec/mailers/previews`][previews] and can be viewed at
The previews live in [`app/mailers/previews`][previews] and can be viewed at
[`/rails/mailers`](http://localhost:3000/rails/mailers).
See the [Rails guides] for more info.
[previews]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/spec/mailers/previews
[previews]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/mailers/previews
[Rails guides]: http://guides.rubyonrails.org/action_mailer_basics.html#previewing-emails
## Incoming email
......
......@@ -60,7 +60,7 @@ Libraries with the following licenses are acceptable for use:
## Unacceptable Licenses
Libraries with the following licenses are unacceptable for use:
Libraries with the following licenses require legal approval for use:
- [GNU GPL][GPL] (version 1, [version 2][GPLv2], [version 3][GPLv3], or any future versions): GPL-licensed libraries cannot be linked to from non-GPL projects.
- [GNU AGPLv3][AGPLv3]: AGPL-licensed libraries cannot be linked to from non-GPL projects.
......@@ -68,6 +68,26 @@ Libraries with the following licenses are unacceptable for use:
- [Facebook BSD + PATENTS][Facebook]: is a 3-clause BSD license with a patent grant that has been deemed [Category X][x-list] by the Apache foundation.
- [WTFPL][WTFPL]: is a public domain dedication [rejected by the OSI (3.2)][WTFPL-OSI]. Also has a strong language which is not in accordance with our diversity policy.
## GPL Cooperation Commitment
Before filing or continuing to prosecute any legal proceeding or claim (other than a Defensive Action) arising from termination of a Covered License, GitLab commits to extend to the person or entity (“you”) accused of violating the Covered License the following provisions regarding cure and reinstatement, taken from GPL version 3. As used here, the term ‘this License’ refers to the specific Covered License being enforced.
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
GitLab intends this Commitment to be irrevocable, and binding and enforceable against GitLab and assignees of or successors to GitLab’s copyrights.
GitLab may modify this Commitment by publishing a new edition on this page or a successor location.
Definitions
‘Covered License’ means the GNU General Public License, version 2 (GPLv2), the GNU Lesser General Public License, version 2.1 (LGPLv2.1), or the GNU Library General Public License, version 2 (LGPLv2), all as published by the Free Software Foundation.
‘Defensive Action’ means a legal proceeding or claim that GitLab brings against you in response to a prior proceeding or claim initiated by you or your affiliate.
GitLab means GitLab Inc. and its affiliates and subsidiaries.
## Requesting Approval for Licenses
Libraries that are not listed in the [Acceptable Licenses][Acceptable-Licenses] or [Unacceptable Licenses][Unacceptable-Licenses] list can be submitted to the legal team for review. Please email `legal@gitlab.com` with the details. After a decision has been made, the original requestor is responsible for updating this document.
......
# Integrate your GitLab server with Bitbucket
NOTE: **Note:**
You need to [enable OmniAuth](omniauth.md) in order to use this.
Import projects from Bitbucket.org and login to your GitLab instance with your
Bitbucket.org account.
......@@ -76,13 +79,13 @@ you to use.
sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
```
1. Follow the [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
for initial settings.
1. Add the Bitbucket provider configuration:
For Omnibus packages:
```ruby
gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_providers'] = [
{
"name" => "bitbucket",
......@@ -96,10 +99,13 @@ you to use.
For installations from source:
```yaml
- { name: 'bitbucket',
app_id: 'BITBUCKET_APP_KEY',
app_secret: 'BITBUCKET_APP_SECRET',
url: 'https://bitbucket.org/' }
omniauth:
enabled: true
providers:
- { name: 'bitbucket',
app_id: 'BITBUCKET_APP_KEY',
app_secret: 'BITBUCKET_APP_SECRET',
url: 'https://bitbucket.org/' }
```
---
......@@ -121,6 +127,9 @@ well, the user will be returned to GitLab and will be signed in.
Once the above configuration is set up, you can use Bitbucket to sign into
GitLab and [start importing your projects][bb-import].
If you don't want to enable signing in with Bitbucket but just want to import
projects from Bitbucket, you could [disable it in the admin panel](omniauth.md#enable-or-disable-sign-in-with-an-omniauth-provider-without-disabling-import-sources).
[init-oauth]: omniauth.md#initial-omniauth-configuration
[bb-import]: ../workflow/importing/import_projects_from_bitbucket.md
[bb-old]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-14-stable/doc/integration/bitbucket.md
......
# SAML OmniAuth Provider
NOTE: **Note:**
You need to [enable OmniAuth](omniauth.md) in order to use this.
GitLab can be configured to act as a SAML 2.0 Service Provider (SP). This allows
GitLab to consume assertions from a SAML 2.0 Identity Provider (IdP) such as
Microsoft ADFS to authenticate users.
......@@ -15,33 +18,33 @@ in your SAML IdP:
For omnibus package:
```sh
sudo editor /etc/gitlab/gitlab.rb
sudo editor /etc/gitlab/gitlab.rb
```
For installations from source:
```sh
cd /home/git/gitlab
cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml
sudo -u git -H editor config/gitlab.yml
```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
for initial settings.
1. To allow your users to use SAML to sign up without having to manually create
an account first, don't forget to add the following values to your configuration:
For omnibus package:
```ruby
gitlab_rails['omniauth_allow_single_sign_on'] = ['saml']
gitlab_rails['omniauth_block_auto_created_users'] = false
gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_allow_single_sign_on'] = ['saml']
gitlab_rails['omniauth_block_auto_created_users'] = false
```
For installations from source:
```yaml
omniauth:
enabled: true
allow_single_sign_on: ["saml"]
block_auto_created_users: false
```
......@@ -52,13 +55,13 @@ in your SAML IdP:
For omnibus package:
```ruby
gitlab_rails['omniauth_auto_link_saml_user'] = true
gitlab_rails['omniauth_auto_link_saml_user'] = true
```
For installations from source:
```yaml
auto_link_saml_user: true
auto_link_saml_user: true
```
1. Add the provider configuration:
......@@ -66,35 +69,37 @@ in your SAML IdP:
For omnibus package:
```ruby
gitlab_rails['omniauth_providers'] = [
{
name: 'saml',
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
},
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
]
```
For installations from source:
```yaml
- {
name: 'saml',
args: {
gitlab_rails['omniauth_providers'] = [
{
name: 'saml',
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
},
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
]
```
For installations from source:
```yaml
omniauth:
providers:
- {
name: 'saml',
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
},
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
```
1. Change the value for `assertion_consumer_service_url` to match the HTTPS endpoint
......@@ -140,8 +145,8 @@ This setting is only available on GitLab 8.7 and above.
SAML login includes support for automatically identifying whether a user should
be considered an [external](../user/permissions.md) user based on the user's group
membership in the SAML identity provider. This feature **does not** allow you to
automatically add users to GitLab [Groups](../user/group/index.md), it simply
allows you to mark users as External if they are members of certain groups in the
automatically add users to GitLab [Groups](../user/group/index.md), it simply
allows you to mark users as External if they are members of certain groups in the
Identity Provider.
### Requirements
......@@ -189,28 +194,28 @@ If you want some SAML authentication methods to count as 2FA on a per session ba
1. Edit `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_rails['omniauth_providers'] = [
{
name: 'saml',
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
upstream_two_factor_authn_contexts:
%w(
urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport
urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS
urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN
)
},
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
]
gitlab_rails['omniauth_providers'] = [
{
name: 'saml',
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
upstream_two_factor_authn_contexts:
%w(
urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport
urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS
urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN
)
},
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
]
```
1. Save the file and [reconfigure][] GitLab for the changes to take effect.
---
......@@ -218,40 +223,41 @@ If you want some SAML authentication methods to count as 2FA on a per session ba
**For installations from source:**
1. Edit `config/gitlab.yml`:
```yaml
- {
name: 'saml',
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
upstream_two_factor_authn_contexts:
[
'urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport',
'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS',
'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN'
]
},
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
```yaml
omniauth:
providers:
- {
name: 'saml',
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
upstream_two_factor_authn_contexts:
[
'urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport',
'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS',
'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN'
]
},
label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
```
1. Save the file and [restart GitLab][] for the changes ot take effect
In addition to the changes in GitLab, make sure that your Idp is returning the
`AuthnContext`. For example:
```xml
<saml:AuthnStatement>
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:MediumStrongCertificateProtectedTransport</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AuthnStatement>
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:MediumStrongCertificateProtectedTransport</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
```
## Customization
......
......@@ -206,7 +206,7 @@ kubectl get svc --all-namespaces -o jsonpath='{range.items[?(@.status.loadBalanc
> **Note**: Some Kubernetes clusters return a hostname instead, like [Amazon EKS](https://aws.amazon.com/eks/). For these platforms, run:
> ```bash
> kubectl get service ingress-nginx-ingress-controller -n gitlab-managed-apps -o jsonpath="{.status.loadBalancer.ingress[0].hostname}"`.
> kubectl get service ingress-nginx-ingress-controller -n gitlab-managed-apps -o jsonpath="{.status.loadBalancer.ingress[0].hostname}".
> ```
The output is the external IP address of your cluster. This information can then
......
......@@ -23,9 +23,13 @@ module API
get ':id/export/download' do
path = user_project.export_project_path
render_api_error!('404 Not found or has expired', 404) unless path
present_disk_file!(path, File.basename(path), 'application/gzip')
if path
present_disk_file!(path, File.basename(path), 'application/gzip')
elsif user_project.export_project_object_exists?
present_carrierwave_file!(user_project.import_export_upload.export_file)
else
render_api_error!('404 Not found or has expired', 404)
end
end
desc 'Start export' do
......
......@@ -100,6 +100,11 @@ module Banzai
ref_pattern = object_class.reference_pattern
link_pattern = object_class.link_reference_pattern
# Compile often used regexps only once outside of the loop
ref_pattern_anchor = /\A#{ref_pattern}\z/
link_pattern_start = /\A#{link_pattern}/
link_pattern_anchor = /\A#{link_pattern}\z/
nodes.each do |node|
if text_node?(node) && ref_pattern
replace_text_when_pattern_matches(node, ref_pattern) do |content|
......@@ -108,7 +113,7 @@ module Banzai
elsif element_node?(node)
yield_valid_link(node) do |link, inner_html|
if ref_pattern && link =~ /\A#{ref_pattern}\z/
if ref_pattern && link =~ ref_pattern_anchor
replace_link_node_with_href(node, link) do
object_link_filter(link, ref_pattern, link_content: inner_html)
end
......@@ -118,7 +123,7 @@ module Banzai
next unless link_pattern
if link == inner_html && inner_html =~ /\A#{link_pattern}/
if link == inner_html && inner_html =~ link_pattern_start
replace_link_node_with_text(node, link) do
object_link_filter(inner_html, link_pattern, link_reference: true)
end
......@@ -126,7 +131,7 @@ module Banzai
next
end
if link =~ /\A#{link_pattern}\z/
if link =~ link_pattern_anchor
replace_link_node_with_href(node, link) do
object_link_filter(link, link_pattern, link_content: inner_html, link_reference: true)
end
......
......@@ -9,10 +9,6 @@ module Gitlab
Settings
end
def self.migrations_hash
@_migrations_hash ||= Digest::MD5.hexdigest(ActiveRecord::Migrator.get_all_versions.to_s)
end
def self.revision
@_revision ||= begin
if File.exist?(root.join("REVISION"))
......
......@@ -42,6 +42,21 @@ module Gitlab
!self.read_only?
end
# check whether the underlying database is in read-only mode
def self.db_read_only?
if postgresql?
ActiveRecord::Base.connection.execute('SELECT pg_is_in_recovery()')
.first
.fetch('pg_is_in_recovery') == 't'
else
false
end
end
def self.db_read_write?
!self.db_read_only?
end
def self.version
@version ||= database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
end
......
......@@ -40,6 +40,10 @@ module Gitlab
"#{basename[0..FILENAME_LIMIT]}_export.tar.gz"
end
def object_storage?
Feature.enabled?(:import_export_object_storage)
end
def version
VERSION
end
......
......@@ -2,6 +2,7 @@ module Gitlab
module ImportExport
module AfterExportStrategies
class BaseAfterExportStrategy
extend Gitlab::ImportExport::CommandLineUtil
include ActiveModel::Validations
extend Forwardable
......@@ -24,9 +25,10 @@ module Gitlab
end
def execute(current_user, project)
return unless project&.export_project_path
@project = project
return unless @project.export_status == :finished
@current_user = current_user
if invalid?
......@@ -51,9 +53,12 @@ module Gitlab
end
def self.lock_file_path(project)
return unless project&.export_path
return unless project.export_path || object_storage?
File.join(project.export_path, AFTER_EXPORT_LOCK_FILE_NAME)
lock_path = project.import_export_shared.archive_path
mkdir_p(lock_path)
File.join(lock_path, AFTER_EXPORT_LOCK_FILE_NAME)
end
protected
......@@ -77,6 +82,10 @@ module Gitlab
def log_validation_errors
errors.full_messages.each { |msg| project.import_export_shared.add_error_message(msg) }
end
def object_storage?
project.export_project_object_exists?
end
end
end
end
......
......@@ -38,14 +38,20 @@ module Gitlab
private
def send_file
export_file = File.open(project.export_project_path)
Gitlab::HTTP.public_send(http_method.downcase, url, send_file_options(export_file)) # rubocop:disable GitlabSecurity/PublicSend
Gitlab::HTTP.public_send(http_method.downcase, url, send_file_options) # rubocop:disable GitlabSecurity/PublicSend
ensure
export_file.close if export_file
export_file.close if export_file && !object_storage?
end
def export_file
if object_storage?
project.import_export_upload.export_file.file.open
else
File.open(project.export_project_path)
end
end
def send_file_options(export_file)
def send_file_options
{
body_stream: export_file,
headers: headers
......@@ -53,7 +59,15 @@ module Gitlab
end
def headers
{ 'Content-Length' => File.size(project.export_project_path).to_s }
{ 'Content-Length' => export_size.to_s }
end
def export_size
if object_storage?
project.import_export_upload.export_file.file.size
else
File.size(project.export_project_path)
end
end
end
end
......
......@@ -15,15 +15,22 @@ module Gitlab
def save
if compress_and_save
remove_export_path
Rails.logger.info("Saved project export #{archive_file}")
archive_file
save_on_object_storage if use_object_storage?
else
@shared.error(Gitlab::ImportExport::Error.new("Unable to save #{archive_file} into #{@shared.export_path}"))
@shared.error(Gitlab::ImportExport::Error.new(error_message))
false
end
rescue => e
@shared.error(e)
false
ensure
if use_object_storage?
remove_archive
remove_export_path
end
end
private
......@@ -36,9 +43,29 @@ module Gitlab
FileUtils.rm_rf(@shared.export_path)
end
def remove_archive
FileUtils.rm_rf(@shared.archive_path)
end
def archive_file
@archive_file ||= File.join(@shared.archive_path, Gitlab::ImportExport.export_filename(project: @project))
end
def save_on_object_storage
upload = ImportExportUpload.find_or_initialize_by(project: @project)
File.open(archive_file) { |file| upload.export_file = file }
upload.save!
end
def use_object_storage?
Gitlab::ImportExport.object_storage?
end
def error_message
"Unable to save #{archive_file} into #{@shared.export_path}. Object storage enabled: #{use_object_storage?}"
end
end
end
end
......@@ -69,6 +69,7 @@ module Gitlab
@route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {}
end
# Overridden in EE module
def whitelisted_routes
grack_route || ReadOnly.internal_routes.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route
end
......
......@@ -790,23 +790,55 @@ describe ProjectsController do
project.add_master(user)
end
context 'when project export is enabled' do
it 'returns 302' do
get :download_export, namespace_id: project.namespace, id: project
context 'object storage disabled' do
before do
stub_feature_flags(import_export_object_storage: false)
end
expect(response).to have_gitlab_http_status(302)
context 'when project export is enabled' do
it 'returns 302' do
get :download_export, namespace_id: project.namespace, id: project
expect(response).to have_gitlab_http_status(302)
end
end
context 'when project export is disabled' do
before do
stub_application_setting(project_export_enabled?: false)
end
it 'returns 404' do
get :download_export, namespace_id: project.namespace, id: project
expect(response).to have_gitlab_http_status(404)
end
end
end
context 'when project export is disabled' do
context 'object storage enabled' do
before do
stub_application_setting(project_export_enabled?: false)
stub_feature_flags(import_export_object_storage: true)
end
it 'returns 404' do
get :download_export, namespace_id: project.namespace, id: project
context 'when project export is enabled' do
it 'returns 302' do
get :download_export, namespace_id: project.namespace, id: project
expect(response).to have_gitlab_http_status(404)
expect(response).to have_gitlab_http_status(302)
end
end
context 'when project export is disabled' do
before do
stub_application_setting(project_export_enabled?: false)
end
it 'returns 404' do
get :download_export, namespace_id: project.namespace, id: project
expect(response).to have_gitlab_http_status(404)
end
end
end
end
......
FactoryBot.define do
factory :import_export_upload do
project { create(:project) }
end
end
......@@ -103,6 +103,22 @@ FactoryBot.define do
end
trait :with_export do
before(:create) do |_project, _evaluator|
allow(Feature).to receive(:enabled?).with(:import_export_object_storage) { false }
allow(Feature).to receive(:enabled?).with('import_export_object_storage') { false }
end
after(:create) do |project, _evaluator|
ProjectExportWorker.new.perform(project.creator.id, project.id)
end
end
trait :with_object_export do
before(:create) do |_project, _evaluator|
allow(Feature).to receive(:enabled?).with(:import_export_object_storage) { true }
allow(Feature).to receive(:enabled?).with('import_export_object_storage') { true }
end
after(:create) do |project, evaluator|
ProjectExportWorker.new.perform(project.creator.id, project.id)
end
......
......@@ -25,6 +25,7 @@ describe 'Import/Export - project export integration test', :js do
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
stub_feature_flags(import_export_object_storage: false)
end
after do
......
......@@ -5,6 +5,7 @@ describe 'Import/Export - Namespace export file cleanup', :js do
before do
allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
stub_feature_flags(import_export_object_storage: false)
end
after do
......
......@@ -9,6 +9,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
[relative link 1](../relative)
[relative link 2](./relative)
[relative link 3](./e/f/relative)
[spaced link](title with spaces)
HEREDOC
end
......@@ -42,6 +43,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/relative\">relative link 1</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/c/relative\">relative link 2</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/c/e/f/relative\">relative link 3</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/title%20with%20spaces\">spaced link</a>")
end
end
......@@ -64,6 +66,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/relative\">relative link 1</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/title%20with%20spaces\">spaced link</a>")
end
end
......@@ -86,6 +89,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/relative\">relative link 1</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/title%20with%20spaces\">spaced link</a>")
end
end
end
......@@ -119,6 +123,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/relative\">relative link 1</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/c/relative\">relative link 2</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a/b/c/e/f/relative\">relative link 3</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/title%20with%20spaces\">spaced link</a>")
end
end
......@@ -136,6 +141,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/relative\">relative link 1</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/title%20with%20spaces\">spaced link</a>")
end
end
......@@ -153,6 +159,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/relative\">relative link 1</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
expect(page.html).to include("<a href=\"/#{project.full_path}/wikis/title%20with%20spaces\">spaced link</a>")
end
end
end
......
......@@ -68,6 +68,26 @@ describe 'Snippet', :js do
end
end
context 'with cached Redcarpet html' do
let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, cached_markdown_version: CacheMarkdownField::CACHE_REDCARPET_VERSION) }
let(:file_name) { 'test.md' }
let(:content) { "1. one\n - sublist\n" }
it 'renders correctly' do
expect(page).to have_xpath("//ol//li//ul")
end
end
context 'with cached CommonMark html' do
let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION) }
let(:file_name) { 'test.md' }
let(:content) { "1. one\n - sublist\n" }
it 'renders correctly' do
expect(page).not_to have_xpath("//ol//li//ul")
end
end
context 'switching to the simple viewer' do
before do
find('.js-blob-viewer-switch-btn[data-viewer=simple]').click
......
......@@ -184,6 +184,7 @@ describe IssuablesHelper do
issuableRef: "##{issue.iid}",
markdownPreviewPath: "/#{@project.full_path}/preview_markdown",
markdownDocsPath: '/help/user/markdown',
markdownVersion: 11,
issuableTemplates: [],
projectPath: @project.path,
projectNamespace: @project.namespace.path,
......
......@@ -205,7 +205,9 @@ describe MarkupHelper do
it "uses Wiki pipeline for markdown files" do
allow(@wiki).to receive(:format).and_return(:markdown)
expect(helper).to receive(:markdown_unsafe).with('wiki content', pipeline: :wiki, project: project, project_wiki: @wiki, page_slug: "nested/page", issuable_state_filter_enabled: true)
expect(helper).to receive(:markdown_unsafe).with('wiki content',
pipeline: :wiki, project: project, project_wiki: @wiki, page_slug: "nested/page",
issuable_state_filter_enabled: true, markdown_engine: :redcarpet)
helper.render_wiki_content(@wiki)
end
......@@ -236,19 +238,32 @@ describe MarkupHelper do
expect(helper.markup('foo.rst', content).encoding.name).to eq('UTF-8')
end
it "delegates to #markdown_unsafe when file name corresponds to Markdown" do
it 'delegates to #markdown_unsafe when file name corresponds to Markdown' do
expect(helper).to receive(:gitlab_markdown?).with('foo.md').and_return(true)
expect(helper).to receive(:markdown_unsafe).and_return('NOEL')
expect(helper.markup('foo.md', content)).to eq('NOEL')
end
it "delegates to #asciidoc_unsafe when file name corresponds to AsciiDoc" do
it 'delegates to #asciidoc_unsafe when file name corresponds to AsciiDoc' do
expect(helper).to receive(:asciidoc?).with('foo.adoc').and_return(true)
expect(helper).to receive(:asciidoc_unsafe).and_return('NOEL')
expect(helper.markup('foo.adoc', content)).to eq('NOEL')
end
it 'uses passed in rendered content' do
expect(helper).not_to receive(:gitlab_markdown?)
expect(helper).not_to receive(:markdown_unsafe)
expect(helper.markup('foo.md', content, rendered: '<p>NOEL</p>')).to eq('<p>NOEL</p>')
end
it 'defaults to Redcarpet' do
expect(helper).to receive(:markdown_unsafe).with(content, hash_including(markdown_engine: :redcarpet)).and_return('NOEL')
expect(helper.markup('foo.md', content)).to eq('NOEL')
end
end
describe '#first_line_in_markdown' do
......
......@@ -165,6 +165,7 @@ export const note = {
report_abuse_path:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_546&user_id=1',
path: '/gitlab-org/gitlab-ce/notes/546',
cached_markdown_version: 11,
};
export const discussionMock = {
......
......@@ -153,7 +153,7 @@ describe('Deployment component', () => {
it('renders external URL', () => {
expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(deploymentMockData.external_url);
expect(el.querySelector('.js-deploy-url').innerText).toContain(deploymentMockData.external_url_formatted);
expect(el.querySelector('.js-deploy-url').innerText).toContain('View app');
});
it('renders stop button', () => {
......
......@@ -145,7 +145,7 @@ describe('MRWidgetHeader', () => {
it('renders web ide button', () => {
const button = vm.$el.querySelector('.js-web-ide');
expect(button.textContent.trim()).toEqual('Web IDE');
expect(button.textContent.trim()).toEqual('Open in Web IDE');
expect(button.getAttribute('href')).toEqual('/-/ide/projectabc');
});
......@@ -154,7 +154,7 @@ describe('MRWidgetHeader', () => {
const button = vm.$el.querySelector('.js-web-ide');
expect(button.textContent.trim()).toEqual('Web IDE');
expect(button.textContent.trim()).toEqual('Open in Web IDE');
expect(button.getAttribute('href')).toEqual('/-/ide/projectabc');
});
......@@ -253,8 +253,8 @@ describe('MRWidgetHeader', () => {
});
it('renders diverged commits info', () => {
expect(vm.$el.querySelector('.diverged-commits-count').textContent.trim()).toEqual(
'(12 commits behind)',
expect(vm.$el.querySelector('.diverged-commits-count').textContent).toMatch(
/(mr-widget-refactor[\s\S]+?is 12 commits behind[\s\S]+?master)/,
);
});
});
......
......@@ -3,17 +3,61 @@ require 'spec_helper'
describe Banzai::Filter::MarkdownFilter do
include FilterSpecHelper
context 'code block' do
it 'adds language to lang attribute when specified' do
result = filter("```html\nsome code\n```")
describe 'markdown engine from context' do
it 'defaults to CommonMark' do
expect_any_instance_of(Banzai::Filter::MarkdownEngines::CommonMark).to receive(:render).and_return('test')
expect(result).to start_with("<pre><code lang=\"html\">")
filter('test')
end
it 'does not add language to lang attribute when not specified' do
result = filter("```\nsome code\n```")
it 'uses Redcarpet' do
expect_any_instance_of(Banzai::Filter::MarkdownEngines::Redcarpet).to receive(:render).and_return('test')
expect(result).to start_with("<pre><code>")
filter('test', { markdown_engine: :redcarpet })
end
it 'uses CommonMark' do
expect_any_instance_of(Banzai::Filter::MarkdownEngines::CommonMark).to receive(:render).and_return('test')
filter('test', { markdown_engine: :common_mark })
end
end
describe 'code block' do
context 'using CommonMark' do
before do
stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark)
end
it 'adds language to lang attribute when specified' do
result = filter("```html\nsome code\n```")
expect(result).to start_with("<pre><code lang=\"html\">")
end
it 'does not add language to lang attribute when not specified' do
result = filter("```\nsome code\n```")
expect(result).to start_with("<pre><code>")
end
end
context 'using Redcarpet' do
before do
stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :redcarpet)
end
it 'adds language to lang attribute when specified' do
result = filter("```html\nsome code\n```")
expect(result).to start_with("\n<pre><code lang=\"html\">")
end
it 'does not add language to lang attribute when not specified' do
result = filter("```\nsome code\n```")
expect(result).to start_with("\n<pre><code>")
end
end
end
end
......@@ -357,6 +357,35 @@ describe Gitlab::Database do
end
end
describe '.db_read_only?' do
context 'when using PostgreSQL' do
before do
allow(ActiveRecord::Base.connection).to receive(:execute).and_call_original
expect(described_class).to receive(:postgresql?).and_return(true)
end
it 'detects a read only database' do
allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "t" }])
expect(described_class.db_read_only?).to be_truthy
end
it 'detects a read write database' do
allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "f" }])
expect(described_class.db_read_only?).to be_falsey
end
end
context 'when using MySQL' do
before do
expect(described_class).to receive(:postgresql?).and_return(false)
end
it { expect(described_class.db_read_only?).to be_falsey }
end
end
describe '#sanitize_timestamp' do
let(:max_timestamp) { Time.at((1 << 31) - 1) }
......
require 'spec_helper'
describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
let!(:service) { described_class.new }
let!(:project) { create(:project, :with_object_export) }
let(:shared) { project.import_export_shared }
let!(:user) { create(:user) }
describe '#execute' do
before do
allow(service).to receive(:strategy_execute)
stub_feature_flags(import_export_object_storage: true)
end
it 'returns if project exported file is not found' do
allow(project).to receive(:export_project_object_exists?).and_return(false)
expect(service).not_to receive(:strategy_execute)
service.execute(user, project)
end
it 'creates a lock file in the export dir' do
allow(service).to receive(:delete_after_export_lock)
service.execute(user, project)
expect(lock_path_exist?).to be_truthy
end
context 'when the method succeeds' do
it 'removes the lock file' do
service.execute(user, project)
expect(lock_path_exist?).to be_falsey
end
end
context 'when the method fails' do
before do
allow(service).to receive(:strategy_execute).and_call_original
end
context 'when validation fails' do
before do
allow(service).to receive(:invalid?).and_return(true)
end
it 'does not create the lock file' do
expect(service).not_to receive(:create_or_update_after_export_lock)
service.execute(user, project)
end
it 'does not execute main logic' do
expect(service).not_to receive(:strategy_execute)
service.execute(user, project)
end
it 'logs validation errors in shared context' do
expect(service).to receive(:log_validation_errors)
service.execute(user, project)
end
end
context 'when an exception is raised' do
it 'removes the lock' do
expect { service.execute(user, project) }.to raise_error(NotImplementedError)
expect(lock_path_exist?).to be_falsey
end
end
end
end
describe '#log_validation_errors' do
it 'add the message to the shared context' do
errors = %w(test_message test_message2)
allow(service).to receive(:invalid?).and_return(true)
allow(service.errors).to receive(:full_messages).and_return(errors)
expect(shared).to receive(:add_error_message).twice.and_call_original
service.execute(user, project)
expect(shared.errors).to eq errors
end
end
describe '#to_json' do
it 'adds the current strategy class to the serialized attributes' do
params = { param1: 1 }
result = params.merge(klass: described_class.to_s).to_json
expect(described_class.new(params).to_json).to eq result
end
end
def lock_path_exist?
File.exist?(described_class.lock_file_path(project))
end
end
......@@ -9,6 +9,7 @@ describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
describe '#execute' do
before do
allow(service).to receive(:strategy_execute)
stub_feature_flags(import_export_object_storage: false)
end
it 'returns if project exported file is not found' do
......
......@@ -24,13 +24,34 @@ describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
end
describe '#execute' do
it 'removes the exported project file after the upload' do
allow(strategy).to receive(:send_file)
allow(strategy).to receive(:handle_response_error)
context 'without object storage' do
before do
stub_feature_flags(import_export_object_storage: false)
end
it 'removes the exported project file after the upload' do
allow(strategy).to receive(:send_file)
allow(strategy).to receive(:handle_response_error)
expect(project).to receive(:remove_exported_project_file)
strategy.execute(user, project)
end
end
context 'with object storage' do
before do
stub_feature_flags(import_export_object_storage: true)
end
expect(project).to receive(:remove_exported_project_file)
it 'removes the exported project file after the upload' do
allow(strategy).to receive(:send_file)
allow(strategy).to receive(:handle_response_error)
strategy.execute(user, project)
expect(project).to receive(:remove_exported_project_file)
strategy.execute(user, project)
end
end
end
end
......@@ -293,6 +293,7 @@ project:
- deploy_tokens
- settings
- ci_cd_settings
- import_export_upload
award_emoji:
- awardable
- user
......
require 'spec_helper'
require 'fileutils'
describe Gitlab::ImportExport::Saver do
let!(:project) { create(:project, :public, name: 'project') }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
subject { described_class.new(project: project, shared: shared) }
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
FileUtils.mkdir_p(shared.export_path)
FileUtils.touch("#{shared.export_path}/tmp.bundle")
end
after do
FileUtils.rm_rf(export_path)
end
context 'local archive' do
it 'saves the repo to disk' do
stub_feature_flags(import_export_object_storage: false)
subject.save
expect(shared.errors).to be_empty
expect(Dir.empty?(shared.archive_path)).to be false
end
end
context 'object storage' do
it 'saves the repo using object storage' do
stub_feature_flags(import_export_object_storage: true)
stub_uploads_object_storage(ImportExportUploader)
subject.save
expect(ImportExportUpload.find_by(project: project).export_file.url)
.to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*])
end
end
end
......@@ -4,28 +4,6 @@ describe Gitlab::Middleware::ReadOnly do
include Rack::Test::Methods
using RSpec::Parameterized::TableSyntax
RSpec::Matchers.define :be_a_redirect do
match do |response|
response.status == 301
end
end
RSpec::Matchers.define :disallow_request do
match do |middleware|
alert = middleware.env['rack.session'].to_hash
.dig('flash', 'flashes', 'alert')
alert&.include?('You cannot perform write operations')
end
end
RSpec::Matchers.define :disallow_request_in_json do
match do |response|
json_response = JSON.parse(response.body)
response.body.include?('You cannot perform write operations') && json_response.key?('message')
end
end
let(:rack_stack) do
rack = Rack::Builder.new do
use ActionDispatch::Session::CacheStore
......@@ -66,38 +44,38 @@ describe Gitlab::Middleware::ReadOnly do
it 'expects PATCH requests to be disallowed' do
response = request.patch('/test_request')
expect(response).to be_a_redirect
expect(response).to be_redirect
expect(subject).to disallow_request
end
it 'expects PUT requests to be disallowed' do
response = request.put('/test_request')
expect(response).to be_a_redirect
expect(response).to be_redirect
expect(subject).to disallow_request
end
it 'expects POST requests to be disallowed' do
response = request.post('/test_request')
expect(response).to be_a_redirect
expect(response).to be_redirect
expect(subject).to disallow_request
end
it 'expects a internal POST request to be allowed after a disallowed request' do
response = request.post('/test_request')
expect(response).to be_a_redirect
expect(response).to be_redirect
response = request.post("/api/#{API::API.version}/internal")
expect(response).not_to be_a_redirect
expect(response).not_to be_redirect
end
it 'expects DELETE requests to be disallowed' do
response = request.delete('/test_request')
expect(response).to be_a_redirect
expect(response).to be_redirect
expect(subject).to disallow_request
end
......@@ -105,7 +83,7 @@ describe Gitlab::Middleware::ReadOnly do
expect(Rails.application.routes).to receive(:recognize_path).and_call_original
response = request.post('/root/gitlab-ce/new/master/app/info/lfs/objects/batch')
expect(response).to be_a_redirect
expect(response).to be_redirect
expect(subject).to disallow_request
end
......@@ -120,19 +98,19 @@ describe Gitlab::Middleware::ReadOnly do
expect(Rails.application.routes).not_to receive(:recognize_path)
response = request.post("/api/#{API::API.version}/internal")
expect(response).not_to be_a_redirect
expect(response).not_to be_redirect
expect(subject).not_to disallow_request
end
it 'expects requests to sidekiq admin to be allowed' do
response = request.post('/admin/sidekiq')
expect(response).not_to be_a_redirect
expect(response).not_to be_redirect
expect(subject).not_to disallow_request
response = request.get('/admin/sidekiq')
expect(response).not_to be_a_redirect
expect(response).not_to be_redirect
expect(subject).not_to disallow_request
end
......@@ -150,7 +128,7 @@ describe Gitlab::Middleware::ReadOnly do
expect(Rails.application.routes).to receive(:recognize_path).and_call_original
response = request.post(path)
expect(response).not_to be_a_redirect
expect(response).not_to be_redirect
expect(subject).not_to disallow_request
end
end
......
......@@ -370,4 +370,20 @@ describe CacheMarkdownField do
end
end
end
describe CacheMarkdownField::MarkdownEngine do
subject { lambda { |version| CacheMarkdownField::MarkdownEngine.from_version(version) } }
it 'returns :common_mark as a default' do
expect(subject.call(nil)).to eq :common_mark
end
it 'returns :common_mark' do
expect(subject.call(CacheMarkdownField::CACHE_COMMONMARK_VERSION)).to eq :common_mark
end
it 'returns :redcarpet' do
expect(subject.call(CacheMarkdownField::CACHE_REDCARPET_VERSION)).to eq :redcarpet
end
end
end
......@@ -52,7 +52,7 @@ describe CacheableAttributes do
describe '.cache_key' do
it 'excludes cache attributes' do
expect(minimal_test_class.cache_key).to eq("TestClass:#{Gitlab::VERSION}:#{Gitlab.migrations_hash}:#{Rails.version}")
expect(minimal_test_class.cache_key).to eq("TestClass:#{Gitlab::VERSION}:#{Rails.version}")
end
end
......
require 'spec_helper'
describe ImportExportUpload do
subject { described_class.new(project: create(:project)) }
shared_examples 'stores the Import/Export file' do |method|
it 'stores the import file' do
subject.public_send("#{method}=", fixture_file_upload('spec/fixtures/project_export.tar.gz'))
subject.save!
url = "/uploads/-/system/import_export_upload/#{method}/#{subject.id}/project_export.tar.gz"
expect(subject.public_send(method).url).to eq(url)
end
end
context 'import' do
it_behaves_like 'stores the Import/Export file', :import_file
end
context 'export' do
it_behaves_like 'stores the Import/Export file', :export_file
end
end
......@@ -2782,6 +2782,10 @@ describe Project do
let(:legacy_project) { create(:project, :legacy_storage, :with_export) }
let(:project) { create(:project, :with_export) }
before do
stub_feature_flags(import_export_object_storage: false)
end
it 'removes the exports directory for the project' do
expect(File.exist?(project.export_path)).to be_truthy
......@@ -2830,12 +2834,14 @@ describe Project do
let(:project) { create(:project, :with_export) }
it 'removes the exported project file' do
stub_feature_flags(import_export_object_storage: false)
exported_file = project.export_project_path
expect(File.exist?(exported_file)).to be_truthy
allow(FileUtils).to receive(:rm_f).and_call_original
expect(FileUtils).to receive(:rm_f).with(exported_file).and_call_original
allow(FileUtils).to receive(:rm_rf).and_call_original
expect(FileUtils).to receive(:rm_rf).with(exported_file).and_call_original
project.remove_exported_project_file
......
......@@ -192,6 +192,13 @@ describe API::ProjectExport do
context 'when upload complete' do
before do
FileUtils.rm_rf(project_after_export.export_path)
if project_after_export.export_project_object_exists?
upload = project_after_export.import_export_upload
upload.remove_export_file!
upload.save
end
end
it_behaves_like '404 response' do
......@@ -261,6 +268,22 @@ describe API::ProjectExport do
it_behaves_like 'get project export download not found'
end
end
context 'when an uploader is used' do
before do
stub_uploads_object_storage(ImportExportUploader)
[project, project_finished, project_after_export].each do |p|
p.add_master(user)
upload = ImportExportUpload.new(project: p)
upload.export_file = fixture_file_upload('spec/fixtures/project_export.tar.gz', "`/tar.gz")
upload.save!
end
end
it_behaves_like 'get project download by strategy'
end
end
describe 'POST /projects/:project_id/export' do
......
......@@ -11,7 +11,6 @@ describe ImportExportCleanUpService do
path = '/invalid/path/'
stub_repository_downloads_path(path)
expect(File).to receive(:directory?).with(path + tmp_import_export_folder).and_return(false).at_least(:once)
expect(service).not_to receive(:clean_up_export_files)
service.execute
......@@ -38,6 +37,24 @@ describe ImportExportCleanUpService do
end
end
context 'with uploader exports' do
it 'removes old files' do
upload = create(:import_export_upload,
updated_at: 2.days.ago,
export_file: fixture_file_upload('spec/fixtures/project_export.tar.gz'))
expect { service.execute }.to change { upload.reload.export_file.file.nil? }.to(true)
end
it 'does not remove new files' do
upload = create(:import_export_upload,
updated_at: 1.hour.ago,
export_file: fixture_file_upload('spec/fixtures/project_export.tar.gz'))
expect { service.execute }.not_to change { upload.reload.export_file.file.nil? }
end
end
def in_directory_with_files(mtime:)
Dir.mktmpdir do |tmpdir|
stub_repository_downloads_path(tmpdir)
......
......@@ -64,4 +64,16 @@ describe PreviewMarkdownService do
expect(result[:commands]).to eq 'Sets time estimate to 2y.'
end
end
it 'sets correct markdown engine' do
service = described_class.new(project, user, { markdown_version: CacheMarkdownField::CACHE_REDCARPET_VERSION })
result = service.execute
expect(result[:markdown_engine]).to eq :redcarpet
service = described_class.new(project, user, { markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION })
result = service.execute
expect(result[:markdown_engine]).to eq :common_mark
end
end
RSpec::Matchers.define :disallow_request do
match do |middleware|
alert = middleware.env['rack.session'].to_hash
.dig('flash', 'flashes', 'alert')
alert&.include?('You cannot perform write operations')
end
end
RSpec::Matchers.define :disallow_request_in_json do
match do |response|
json_response = JSON.parse(response.body)
response.body.include?('You cannot perform write operations') && json_response.key?('message')
end
end
require 'spec_helper'
describe ImportExportUploader do
let(:model) { build_stubbed(:import_export_upload) }
let(:upload) { create(:upload, model: model) }
subject { described_class.new(model, :import_file) }
context "object_store is REMOTE" do
before do
stub_uploads_object_storage
end
include_context 'with storage', described_class::Store::REMOTE
it_behaves_like 'builds correct paths',
store_dir: %r[import_export_upload/import_file/],
upload_path: %r[import_export_upload/import_file/]
end
end
......@@ -62,4 +62,12 @@ describe RepositoryCheck::BatchWorker do
expect(subject.perform(shard_name)).to eq([])
end
it 'does not run if the exclusive lease is taken' do
allow(subject).to receive(:try_obtain_lease).and_return(false)
expect(subject).not_to receive(:perform_repository_checks)
subject.perform(shard_name)
end
end
......@@ -11,6 +11,14 @@ describe RepositoryCheck::DispatchWorker do
subject.perform
end
it 'does nothing if the exclusive lease is taken' do
allow(subject).to receive(:try_obtain_lease).and_return(false)
expect(RepositoryCheck::BatchWorker).not_to receive(:perform_async)
subject.perform
end
it 'dispatches work to RepositoryCheck::BatchWorker' do
expect(RepositoryCheck::BatchWorker).to receive(:perform_async).at_least(:once)
......
......@@ -9,7 +9,7 @@ Makefile.in
# http://www.gnu.org/software/autoconf
/autom4te.cache
autom4te.cache
/autoscan.log
/autoscan-*.log
/aclocal.m4
......@@ -39,4 +39,3 @@ m4/ltoptions.m4
m4/ltsugar.m4
m4/ltversion.m4
m4/lt~obsolete.m4
autom4te.cache
# Craft Storage (cache) [http://buildwithcraft.com/help/craft-storage-gitignore]
# Craft 2 Storage (https://craftcms.com/support/craft-storage-gitignore)
# not necessary for Craft 3 (https://github.com/craftcms/craft/issues/26)
/craft/storage/*
!/craft/storage/logo/*
\ No newline at end of file
!/craft/storage/rebrand
......@@ -20,7 +20,7 @@
# Deployment Manager configuration file for your project. Added in Delphi XE2.
# Uncomment this if it is not mobile development and you do not use remote debug feature.
#*.deployproj
#
#
# C++ object files produced when C/C++ Output file generation is configured.
# Uncomment this if you are not using external objects (zlib library for example).
#*.obj
......
......@@ -35,7 +35,6 @@ eagle.epf
*.gpi
*.pls
*.ger
*.gpi
*.xln
*.drd
......
......@@ -18,9 +18,6 @@ war/WEB-INF/classes/
#compilation logs
.gwt/
#caching for already compiled files
gwt-unitCache/
#gwt junit compilation files
www-test/
......
*.bak
*.gho
*.ori
*.orig
*.tmp
# General CodeKit files to ignore
config.codekit
config.codekit3
/min
......@@ -23,7 +23,7 @@ local.properties
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
# CDT- autotools
.autotools
# Java annotation processor (APT)
......@@ -47,6 +47,9 @@ local.properties
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
......
......@@ -4,6 +4,7 @@
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
......@@ -20,9 +21,16 @@
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# CMake
cmake-build-debug/
cmake-build-release/
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
......
......@@ -7,17 +7,20 @@
# Compiled MEX binaries (all platforms)
*.mex*
# Packaged app and toolbox files
*.mlappinstall
*.mltbx
# Generated helpsearch folders
helpsearch*/
# Packaged app and toolbox files
*.mlappinstall
*.mltbx
# Generated helpsearch folders
helpsearch*/
# Simulink code generation folders
slprj/
sccprj/
# Matlab code generation folders
codegen/
# Simulink autosave extension
*.autosave
......
......@@ -4,8 +4,8 @@
*.evcd
*.fsdb
# Default name of the simulation executable. A different name can be
# specified with this switch (the associated daidir database name is
# Default name of the simulation executable. A different name can be
# specified with this switch (the associated daidir database name is
# also taken from here): -o <path>/<filename>
simv
......@@ -13,7 +13,7 @@ simv
simv.daidir/
simv.db.dir/
# Infrastructure necessary to co-simulate SystemC models with
# Infrastructure necessary to co-simulate SystemC models with
# Verilog/VHDL models. An alternate directory may be specified with this
# switch: -Mdir=<directory_path>
csrc/
......@@ -22,7 +22,7 @@ csrc/
# used to write all messages from simulation: -l <filename>
*.log
# Coverage results (generated with urg) and database location. The
# Coverage results (generated with urg) and database location. The
# following switch can also be used: urg -dir <coverage_directory>.vdb
simv.vdb/
urgReport/
......
# Swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-v][a-z]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
......
......@@ -14,3 +14,4 @@
# Metadata
*.aliases
*.lvlps
.cache/
......@@ -7,6 +7,4 @@ release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
!/.mvn/wrapper/maven-wrapper.jar
.mvn/wrapper/maven-wrapper.jar
......@@ -57,9 +57,15 @@ typings/
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
......
......@@ -35,6 +35,9 @@ xcuserdata/
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
#
......
# Gitignore for Perl 6 (http://www.perl6.org)
# As part of https://github.com/github/gitignore
# precompiled files
.precomp
lib/.precomp
......@@ -47,6 +47,9 @@ playground.xcworkspace
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
#
......
......@@ -226,6 +226,9 @@ TSWLatexianTemp*
# Texpad
.texpadtmp
# LyX
*.lyx~
# Kile
*.backup
......@@ -241,6 +244,3 @@ TSWLatexianTemp*
# standalone packages
*.sta
# generated if using elsarticle.cls
*.spl
......@@ -8,12 +8,15 @@
/typo3conf/temp_CACHED*
/typo3conf/temp_fieldInfo.php
/typo3conf/deprecation_*.log
/typo3conf/AdditionalConfiguration.php
/typo3conf/ENABLE_INSTALL_TOOL
/typo3conf/realurl_autoconf.php
/FIRST_INSTALL
# Ignore system folders, you should have them symlinked.
# If not comment out the following entries.
/typo3
/typo3_src
/typo3_src-*
/Packages
/.htaccess
/index.php
# Ignore temp directory.
......
......@@ -19,7 +19,7 @@
!**/App_Data/[Pp]ackages/*
!**/[Uu]mbraco/[Dd]eveloper/[Pp]ackages/*
# ImageProcessor DiskCache
# ImageProcessor DiskCache
**/App_Data/cache/
# Ignore the Models Builder models out of date flag
......
# Visual Studio 2015 user specific files
.vs/
# Visual Studio 2015 database file
*.VC.db
# Compiled Object files
*.slo
*.lo
......
......@@ -220,7 +220,7 @@ ClientBin/
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
......@@ -316,7 +316,7 @@ __pycache__/
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
......@@ -325,5 +325,5 @@ ASALocalRun/
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
# MFractors (Xamarin productivity tool) working folder
.mfractor/
......@@ -105,11 +105,14 @@ code_quality:
- code_quality
artifacts:
paths: [gl-code-quality-report.json]
only:
- branches
except:
variables:
- $CODE_QUALITY_DISABLED
license_management:
stage: test
image: docker:stable
variables:
DOCKER_DRIVER: overlay2
......@@ -121,6 +124,8 @@ license_management:
- license_management
artifacts:
paths: [gl-license-management-report.json]
only:
- branches
except:
variables:
- $LICENSE_MANAGEMENT_DISABLED
......@@ -161,6 +166,8 @@ sast:
- sast
artifacts:
paths: [gl-sast-report.json]
only:
- branches
except:
variables:
- $SAST_DISABLED
......@@ -178,6 +185,8 @@ dependency_scanning:
- dependency_scanning
artifacts:
paths: [gl-dependency-scanning-report.json]
only:
- branches
except:
variables:
- $DEPENDENCY_SCANNING_DISABLED
......@@ -195,6 +204,8 @@ container_scanning:
- container_scanning
artifacts:
paths: [gl-container-scanning-report.json]
only:
- branches
except:
variables:
- $CONTAINER_SCANNING_DISABLED
......@@ -365,6 +376,7 @@ production_manual:
kubernetes: active
variables:
- $STAGING_ENABLED
- $CANARY_ENABLED
except:
variables:
- $INCREMENTAL_ROLLOUT_ENABLED
......
......@@ -7,7 +7,7 @@
@babel/template,7.0.0-beta.44,MIT
@babel/traverse,7.0.0-beta.44,MIT
@babel/types,7.0.0-beta.44,MIT
@gitlab-org/gitlab-svgs,1.23.0,SEE LICENSE IN LICENSE
@gitlab-org/gitlab-svgs,1.25.0,SEE LICENSE IN LICENSE
@sindresorhus/is,0.7.0,MIT
@types/jquery,2.0.48,MIT
@vue/component-compiler-utils,1.2.1,MIT
......@@ -35,7 +35,7 @@ abbrev,1.1.1,ISC
accepts,1.3.4,MIT
ace-rails-ap,4.1.2,MIT
acorn,3.3.0,MIT
acorn,5.5.3,MIT
acorn,5.6.2,MIT
acorn-dynamic-import,3.0.0,MIT
acorn-jsx,3.0.1,MIT
actionmailer,4.2.10,MIT
......@@ -51,14 +51,12 @@ addressparser,1.0.1,MIT
aes_key_wrap,1.0.1,MIT
after,0.8.2,MIT
agent-base,2.1.1,MIT
ajv,4.11.8,MIT
ajv,5.5.2,MIT
ajv,6.1.1,MIT
ajv-keywords,2.1.1,MIT
ajv-keywords,3.1.0,MIT
akismet,2.0.0,MIT
align-text,0.1.4,MIT
allocations,1.0.5,MIT
alphanum-sort,1.0.2,MIT
amdefine,1.0.1,BSD-3-Clause OR MIT
amqplib,0.5.2,MIT
......@@ -208,7 +206,7 @@ base64-js,1.2.3,MIT
base64id,1.0.0,MIT
batch,0.6.1,MIT
batch-loader,1.2.1,MIT
bcrypt,3.1.11,MIT
bcrypt,3.1.12,MIT
bcrypt-pbkdf,1.0.1,New BSD
bcrypt_pbkdf,1.0.0,MIT
better-assert,1.0.2,MIT
......@@ -220,7 +218,6 @@ bitsyntax,0.0.4,Unknown
bl,1.1.2,MIT
blackst0ne-mermaid,7.1.0-fixed,MIT
blob,0.0.4,MIT*
block-stream,0.0.9,ISC
bluebird,3.5.1,MIT
bn.js,4.11.8,MIT
body-parser,1.18.2,MIT
......@@ -269,7 +266,7 @@ camelcase-keys,2.1.0,MIT
caniuse-api,1.6.1,MIT
caniuse-db,1.0.30000649,CC-BY-4.0
capture-stack-trace,1.0.0,MIT
carrierwave,1.2.1,MIT
carrierwave,1.2.3,MIT
caseless,0.11.0,Apache 2.0
caseless,0.12.0,Apache 2.0
cause,0.1,MIT
......@@ -399,6 +396,7 @@ dashdash,1.14.1,MIT
data-uri-to-buffer,1.2.0,MIT
date-format,1.2.0,MIT
date-now,0.1.4,MIT
dateformat,3.0.3,MIT
de-indent,1.0.2,MIT
debug,2.2.0,MIT
debug,2.6.8,MIT
......@@ -457,7 +455,7 @@ domelementtype,1.3.0,Simplified BSD
domhandler,2.4.1,Simplified BSD
domutils,1.6.2,Simplified BSD
doorkeeper,4.3.2,MIT
doorkeeper-openid_connect,1.4.0,MIT
doorkeeper-openid_connect,1.5.0,MIT
dot-prop,4.2.0,MIT
double-ended-queue,2.1.0-0,MIT
dropzone,4.2.0,MIT
......@@ -507,7 +505,7 @@ eslint-plugin-html,4.0.3,ISC
eslint-plugin-import,2.12.0,MIT
eslint-plugin-jasmine,2.2.0,MIT
eslint-plugin-promise,3.8.0,ISC
eslint-plugin-vue,4.0.1,MIT
eslint-plugin-vue,4.5.0,MIT
eslint-restricted-globals,0.1.1,MIT
eslint-scope,3.7.1,Simplified BSD
eslint-visitor-keys,1.0.0,Apache 2.0
......@@ -515,10 +513,9 @@ espree,3.5.4,Simplified BSD
esprima,2.7.3,Simplified BSD
esprima,3.1.3,Simplified BSD
esprima,4.0.0,Simplified BSD
esquery,1.0.0,New BSD
esrecurse,4.1.0,Simplified BSD
esquery,1.0.1,New BSD
esrecurse,4.2.1,Simplified BSD
estraverse,1.9.3,Simplified BSD
estraverse,4.1.1,Simplified BSD
estraverse,4.2.0,Simplified BSD
esutils,2.0.2,Simplified BSD
et-orbi,1.0.3,MIT
......@@ -607,11 +604,10 @@ fresh,0.5.2,MIT
from,0.1.7,MIT
from2,2.3.0,MIT
fs-access,1.0.1,MIT
fs-minipass,1.2.5,ISC
fs-write-stream-atomic,1.0.10,ISC
fs.realpath,1.0.0,ISC
fsevents,1.1.3,MIT
fstream,1.0.11,ISC
fstream-ignore,1.0.5,ISC
fsevents,1.2.4,MIT
ftp,0.3.10,MIT
function-bind,1.1.1,MIT
functional-red-black-tree,1.0.1,MIT
......@@ -630,14 +626,14 @@ get_process_mem,0.2.0,MIT
getpass,0.1.7,MIT
gettext_i18n_rails,1.8.0,MIT
gettext_i18n_rails_js,1.3.0,MIT
gitaly-proto,0.100.0,MIT
gitaly-proto,0.105.0,MIT
github-linguist,5.3.3,MIT
github-markup,1.7.0,MIT
gitlab-flowdock-git-hook,1.0.1,MIT
gitlab-gollum-lib,4.2.7.2,MIT
gitlab-gollum-rugged_adapter,0.4.4,MIT
gitlab-gollum-lib,4.2.7.5,MIT
gitlab-gollum-rugged_adapter,0.4.4.1,MIT
gitlab-grit,2.8.2,MIT
gitlab-markup,1.6.3,MIT
gitlab-markup,1.6.4,MIT
gitlab_omniauth-ldap,2.0.4,MIT
glob,5.0.15,ISC
glob,7.1.2,ISC
......@@ -664,7 +660,7 @@ gpgme,2.0.13,LGPL-2.1+
graceful-fs,4.1.11,ISC
grape,1.0.3,MIT
grape-entity,0.7.1,MIT
grape-path-helpers,1.0.4,MIT
grape-path-helpers,1.0.5,MIT
grape_logging,1.7.0,MIT
graphiql-rails,1.4.10,MIT
graphlib,2.1.1,MIT
......@@ -674,10 +670,8 @@ gzip-size,4.1.0,MIT
hamlit,2.6.1,MIT
handle-thing,1.2.5,MIT
handlebars,4.0.6,MIT
har-schema,1.0.5,ISC
har-schema,2.0.0,ISC
har-validator,2.0.6,ISC
har-validator,4.2.1,ISC
har-validator,5.0.3,ISC
has,1.0.1,MIT
has-ansi,2.0.0,MIT
......@@ -712,7 +706,7 @@ hosted-git-info,2.2.0,ISC
hpack.js,2.1.6,MIT
html-comment-regex,1.1.1,MIT
html-entities,1.2.0,MIT
html-pipeline,2.7.1,MIT
html-pipeline,2.8.3,MIT
html2text,0.2.0,MIT
htmlentities,4.3.4,MIT
htmlparser2,3.9.2,MIT
......@@ -739,13 +733,14 @@ icalendar,2.4.1,ruby
ice_nine,0.11.2,MIT
iconv-lite,0.4.15,MIT
iconv-lite,0.4.19,MIT
iconv-lite,0.4.23,MIT
icss-replace-symbols,1.1.0,ISC
icss-utils,2.1.0,ISC
ieee754,1.1.11,New BSD
ieee754,1.1.8,New BSD
iferr,0.1.5,MIT
ignore,3.3.8,MIT
ignore-by-default,1.0.1,ISC
ignore-walk,3.0.1,ISC
immediate,3.0.6,MIT
import-lazy,2.1.0,MIT
import-local,1.0.0,MIT
......@@ -865,16 +860,14 @@ jsesc,1.3.0,MIT
jsesc,2.5.1,MIT
json,1.8.6,ruby
json-buffer,3.0.0,MIT
json-jwt,1.9.2,MIT
json-jwt,1.9.4,MIT
json-parse-better-errors,1.0.2,MIT
json-schema,0.2.3,BSD
json-schema-traverse,0.3.1,MIT
json-stable-stringify,1.0.1,MIT
json-stable-stringify-without-jsonify,1.0.1,MIT
json-stringify-safe,5.0.1,ISC
json3,3.3.2,MIT
json5,0.5.1,MIT
jsonify,0.0.0,Public Domain
jsonpointer,4.0.1,MIT
jsprim,1.4.1,MIT
jszip,3.1.3,(MIT OR GPL-3.0)
......@@ -992,12 +985,14 @@ minimatch,3.0.4,ISC
minimist,0.0.10,MIT
minimist,0.0.8,MIT
minimist,1.2.0,MIT
minipass,2.3.3,ISC
minizlib,1.1.0,MIT
mississippi,2.0.0,Simplified BSD
mixin-deep,1.3.1,MIT
mkdirp,0.5.1,MIT
moment,2.19.2,MIT
monaco-editor,0.13.1,MIT
monaco-editor-webpack-plugin,1.2.1,MIT
monaco-editor-webpack-plugin,1.4.0,MIT
mousetrap,1.4.6,Apache 2.0
mousetrap-rails,1.4.6,"MIT,Apache"
move-concurrently,1.0.1,ISC
......@@ -1012,9 +1007,10 @@ mustermann,1.0.2,MIT
mustermann-grape,1.0.0,MIT
mute-stream,0.0.7,ISC
mysql2,0.4.10,MIT
nan,2.8.0,MIT
nan,2.10.0,MIT
nanomatch,1.2.9,MIT
natural-compare,1.4.0,MIT
needle,2.2.1,MIT
negotiator,0.6.1,MIT
neo-async,2.5.0,MIT
net-ldap,0.16.0,MIT
......@@ -1024,7 +1020,7 @@ netrc,0.11.0,MIT
nice-try,1.0.4,MIT
node-forge,0.6.33,New BSD
node-libs-browser,2.1.0,MIT
node-pre-gyp,0.6.39,New BSD
node-pre-gyp,0.10.0,New BSD
node-uuid,1.4.8,MIT
nodemailer,2.7.2,MIT
nodemailer-direct-transport,3.3.2,MIT
......@@ -1034,7 +1030,8 @@ nodemailer-smtp-pool,2.8.2,MIT
nodemailer-smtp-transport,2.7.2,MIT
nodemailer-wellknown,0.1.10,MIT
nodemon,1.17.3,MIT
nokogiri,1.8.2,MIT
nokogiri,1.8.3,MIT
nokogumbo,1.5.0,Apache 2.0
nopt,1.0.10,MIT
nopt,3.0.6,ISC
nopt,4.0.1,ISC
......@@ -1043,6 +1040,8 @@ normalize-path,2.1.1,MIT
normalize-range,0.1.2,MIT
normalize-url,1.9.1,MIT
normalize-url,2.0.1,MIT
npm-bundled,1.0.3,ISC
npm-packlist,1.1.10,ISC
npm-run-path,2.0.2,MIT
npmlog,4.1.2,ISC
null-check,1.0.0,MIT
......@@ -1076,7 +1075,7 @@ omniauth-oauth,1.1.0,MIT
omniauth-oauth2,1.5.0,MIT
omniauth-oauth2-generic,0.2.2,MIT
omniauth-saml,1.10.0,MIT
omniauth-shibboleth,1.2.1,MIT
omniauth-shibboleth,1.3.0,MIT
omniauth-twitter,1.4.0,MIT
omniauth_crowd,2.2.3,MIT
on-finished,2.3.0,MIT
......@@ -1137,7 +1136,6 @@ peek-pg,1.3.0,MIT
peek-rblineprof,0.2.0,MIT
peek-redis,1.2.0,MIT
peek-sidekiq,1.0.3,MIT
performance-now,0.2.0,MIT
performance-now,2.1.0,MIT
pg,0.18.4,"BSD,ruby,GPL"
pify,2.3.0,MIT
......@@ -1194,7 +1192,6 @@ premailer-rails,1.9.7,MIT
prepend-http,1.0.4,MIT
prepend-http,2.0.0,MIT
preserve,0.2.0,MIT
prettier,1.11.1,MIT
prettier,1.12.1,MIT
prismjs,1.6.0,MIT
private,0.1.8,MIT
......@@ -1222,7 +1219,6 @@ q,1.4.1,MIT
q,1.5.0,MIT
qjobs,1.2.0,MIT
qs,6.2.3,New BSD
qs,6.4.0,New BSD
qs,6.5.1,New BSD
query-string,4.3.2,MIT
query-string,5.1.1,MIT
......@@ -1303,11 +1299,9 @@ repeat-string,1.6.1,MIT
repeating,2.0.1,MIT
representable,3.0.4,MIT
request,2.75.0,Apache 2.0
request,2.81.0,Apache 2.0
request,2.83.0,Apache 2.0
request_store,1.3.1,MIT
requestretry,1.13.0,MIT
require-all,2.2.0,MIT
require-directory,2.1.1,MIT
require-main-filename,1.0.1,ISC
require-uncached,1.0.3,MIT
......@@ -1341,24 +1335,26 @@ ruby_parser,3.9.0,MIT
rubyntlm,0.6.2,MIT
rubypants,0.2.0,BSD
rufus-scheduler,3.4.0,MIT
rugged,0.27.1,MIT
rugged,0.27.2,MIT
run-async,2.3.0,MIT
run-queue,1.0.3,ISC
rx-lite,4.0.8,Apache 2.0
rx-lite-aggregates,4.0.8,Apache 2.0
rxjs,5.5.10,Apache 2.0
safe-buffer,5.1.1,MIT
safe-buffer,5.1.2,MIT
safe-regex,1.1.0,MIT
safe_yaml,1.0.4,MIT
sanitize,2.1.0,MIT
safer-buffer,2.1.2,MIT
sanitize,4.6.5,MIT
sanitize-html,1.16.3,MIT
sass,3.5.5,MIT
sass-listen,4.0.0,MIT
sass-rails,5.0.6,MIT
sawyer,0.8.1,MIT
sax,1.2.2,ISC
sax,1.2.4,ISC
schema-utils,0.4.5,MIT
securecompare,1.0.0,MIT
seed-fu,2.3.7,MIT
select,1.1.2,MIT
select-hose,2.0.0,MIT
......@@ -1433,7 +1429,7 @@ spdy-transport,2.0.20,MIT
split,0.3.3,MIT
split-string,3.1.0,MIT
sprintf-js,1.0.3,New BSD
sprockets,3.7.1,MIT
sprockets,3.7.2,MIT
sprockets-rails,3.2.1,MIT
sql.js,0.4.0,MIT
srcset,1.0.0,MIT
......@@ -1479,8 +1475,7 @@ sys-filesystem,1.1.6,Artistic 2.0
table,4.0.2,New BSD
tapable,0.1.10,MIT
tapable,1.0.0,MIT
tar,2.2.1,ISC
tar-pack,3.4.1,Simplified BSD
tar,4.4.4,ISC
temple,0.7.7,MIT
term-size,1.2.0,MIT
test-exclude,4.2.1,ISC
......@@ -1535,7 +1530,6 @@ uglify-es,3.3.9,Simplified BSD
uglify-js,2.8.29,Simplified BSD
uglify-to-browserify,1.0.2,MIT
uglifyjs-webpack-plugin,1.2.5,MIT
uid-number,0.0.6,ISC
ultron,1.1.1,MIT
undefsafe,2.0.2,MIT
underscore,1.7.0,MIT
......@@ -1566,7 +1560,6 @@ url-parse,1.1.9,MIT
url-parse-lax,1.0.0,MIT
url-parse-lax,3.0.0,MIT
url-to-options,1.0.1,MIT
url_safe_base64,0.2.2,MIT
use,2.0.2,MIT
useragent,2.2.1,MIT
util,0.10.3,MIT
......@@ -1640,6 +1633,7 @@ xtend,4.0.1,MIT
y18n,3.2.1,ISC
y18n,4.0.0,ISC
yallist,2.1.2,ISC
yallist,3.0.2,ISC
yargs,11.0.0,MIT
yargs,11.1.0,MIT
yargs,3.10.0,MIT
......
......@@ -5272,9 +5272,9 @@ moment@2.x, moment@^2.18.1:
version "2.19.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe"
monaco-editor-webpack-plugin@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.2.1.tgz#577ed091420f422bb8f0ff3a8899dd82344da56d"
monaco-editor-webpack-plugin@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.4.0.tgz#7324258ab3695464cfe3bc12edb2e8c55b80d92f"
monaco-editor@0.13.1:
version "0.13.1"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment